import Foundation import AsyncDisplayKit import Display import TelegramCore import Postbox import SwiftSignalKit private enum SubscriberAction { case join case kicked case muteNotifications case unmuteNotifications } private func titleAndColorForAction(_ action: SubscriberAction, theme: PresentationTheme, strings: PresentationStrings) -> (String, UIColor) { switch action { case .join: return (strings.Channel_JoinChannel, theme.chat.inputPanel.panelControlAccentColor) case .kicked: return (strings.Channel_JoinChannel, theme.chat.inputPanel.panelControlDisabledColor) case .muteNotifications: return (strings.Conversation_Mute, theme.chat.inputPanel.panelControlAccentColor) case .unmuteNotifications: return (strings.Conversation_Unmute, theme.chat.inputPanel.panelControlAccentColor) } } private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? { if let channel = peer as? TelegramChannel { switch channel.participationStatus { case .kicked: return .kicked case .left: return .join case .member: if isMuted { return .unmuteNotifications } else { return .muteNotifications } } } else { return nil } } private let badgeFont = Font.regular(14.0) final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { private let button: UIButton private let discussButton: UIButton private let badgeBackground: ASImageNode private let badgeText: ImmediateTextNode private let activityIndicator: UIActivityIndicatorView private var action: SubscriberAction? private let actionDisposable = MetaDisposable() private let badgeDisposable = MetaDisposable() private var presentationInterfaceState: ChatPresentationInterfaceState? private var layoutData: (CGFloat, CGFloat, CGFloat)? override init() { self.button = UIButton() self.discussButton = UIButton() self.activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) self.activityIndicator.isHidden = true self.badgeBackground = ASImageNode() self.badgeBackground.displaysAsynchronously = false self.badgeBackground.displayWithoutProcessing = true self.badgeBackground.isHidden = true self.badgeText = ImmediateTextNode() self.badgeText.displaysAsynchronously = false self.badgeText.isHidden = true self.discussButton.addSubnode(self.badgeBackground) self.discussButton.addSubnode(self.badgeText) super.init() self.view.addSubview(self.button) self.view.addSubview(self.discussButton) self.view.addSubview(self.activityIndicator) self.button.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside]) self.discussButton.addTarget(self, action: #selector(self.discussPressed), for: [.touchUpInside]) } deinit { self.actionDisposable.dispose() self.badgeDisposable.dispose() } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return super.hitTest(point, with: event) if self.bounds.contains(point) { return self.button } else { return nil } } @objc func buttonPressed() { guard let context = self.context, let action = self.action, let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer else { return } switch action { case .join: self.activityIndicator.isHidden = false self.activityIndicator.startAnimating() self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peer.id, memberId: context.account.peerId) |> afterDisposed { [weak self] in Queue.mainQueue().async { if let strongSelf = self { strongSelf.activityIndicator.isHidden = true strongSelf.activityIndicator.stopAnimating() } } }).start()) case .kicked: break case .muteNotifications, .unmuteNotifications: if let context = self.context, let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer { self.actionDisposable.set(togglePeerMuted(account: context.account, peerId: peer.id).start()) } } } @objc private func discussPressed() { if let presentationInterfaceState = self.presentationInterfaceState, let peerDiscussionId = presentationInterfaceState.peerDiscussionId { self.interfaceInteraction?.navigateToChat(peerDiscussionId) } } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { self.layoutData = (width, leftInset, rightInset) if self.presentationInterfaceState != interfaceState { let previousState = self.presentationInterfaceState self.presentationInterfaceState = interfaceState if previousState?.theme !== interfaceState.theme { self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme) } if previousState?.peerDiscussionId != interfaceState.peerDiscussionId { let signal: Signal if let peerDiscussionId = interfaceState.peerDiscussionId, let context = self.context { let key = PostboxViewKey.unreadCounts(items: [.peer(peerDiscussionId)]) let inclusionKey = PostboxViewKey.peerChatInclusion(peerDiscussionId) signal = context.account.postbox.combinedView(keys: [key, inclusionKey]) |> map { view -> Int? in guard let inclusionView = view.views[inclusionKey] as? PeerChatInclusionView, let countsView = view.views[key] as? UnreadMessageCountsView else { return nil } if !inclusionView.inclusion { return nil } if let count = countsView.count(for: .peer(peerDiscussionId)), count != 0 { return Int(count) } else { return nil } } |> distinctUntilChanged } else { signal = .single(nil) } self.badgeDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] value in guard let strongSelf = self, let interfaceState = strongSelf.presentationInterfaceState, let image = strongSelf.badgeBackground.image else { return } let text = "\(value ?? 0)" strongSelf.badgeText.attributedText = NSAttributedString(string: text, font: badgeFont, textColor: interfaceState.theme.chatList.unreadBadgeActiveTextColor) let textSize = strongSelf.badgeText.updateLayout(CGSize(width: 100.0, height: 100.0)) let badgeSize = CGSize(width: max(image.size.width, textSize.width + 4.0), height: image.size.height) let badgeFrame = CGRect(origin: CGPoint(x: strongSelf.discussButton.bounds.width + 5.0, y: floor((strongSelf.discussButton.bounds.height - badgeSize.height) / 2.0)), size: badgeSize) strongSelf.badgeBackground.frame = badgeFrame strongSelf.badgeText.frame = CGRect(origin: CGPoint(x: badgeFrame.minX + floor((badgeSize.width - textSize.width) / 2.0), y: badgeFrame.minY + floor((badgeSize.height - textSize.height) / 2.0)), size: textSize) if value == nil || value == 0 { strongSelf.badgeBackground.isHidden = true strongSelf.badgeText.isHidden = true } else { strongSelf.badgeBackground.isHidden = false strongSelf.badgeText.isHidden = false } })) } if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.peerDiscussionId != interfaceState.peerDiscussionId { if let action = actionForPeer(peer: peer, isMuted: interfaceState.peerIsMuted) { self.action = action let (title, color) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings) self.button.setTitle(title, for: []) self.button.setTitleColor(color, for: [.normal]) self.button.setTitleColor(color.withAlphaComponent(0.5), for: [.highlighted]) self.button.sizeToFit() } else { self.action = nil } if interfaceState.peerDiscussionId != nil { self.discussButton.setTitle(interfaceState.strings.Channel_DiscussionGroup_HeaderLabel, for: []) self.discussButton.setTitleColor(interfaceState.theme.chat.inputPanel.panelControlAccentColor, for: [.normal]) self.discussButton.setTitleColor(interfaceState.theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5), for: [.highlighted]) self.discussButton.sizeToFit() self.discussButton.isHidden = false } else { self.discussButton.isHidden = true } } } let panelHeight = defaultHeight(metrics: metrics) let buttonSize = self.button.bounds.size let discussSize = self.discussButton.bounds.size if self.discussButton.isHidden { self.button.frame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - buttonSize.width) / 2.0), y: floor((panelHeight - buttonSize.height) / 2.0)), size: buttonSize) } else { let availableWidth = floor((width - leftInset - rightInset) / 2.0) self.button.frame = CGRect(origin: CGPoint(x: leftInset + floor((availableWidth - buttonSize.width) / 2.0), y: floor((panelHeight - buttonSize.height) / 2.0)), size: buttonSize) self.discussButton.frame = CGRect(origin: CGPoint(x: leftInset + availableWidth + floor((availableWidth - discussSize.width) / 2.0), y: floor((panelHeight - discussSize.height) / 2.0)), size: discussSize) let badgeOffset = self.discussButton.bounds.width + 5.0 - self.badgeBackground.frame.minX self.badgeBackground.frame = self.badgeBackground.frame.offsetBy(dx: badgeOffset, dy: 0.0) self.badgeText.frame = self.badgeText.frame.offsetBy(dx: badgeOffset, dy: 0.0) } let indicatorSize = self.activityIndicator.bounds.size self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width - 12.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize) return panelHeight } override func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { return defaultHeight(metrics: metrics) } }