From fbc8212ee8adaffcaaf841165b98e8ea1c359fef Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 2 Mar 2021 22:22:42 +0400 Subject: [PATCH] Add credibility icon for voice chat participants Add segmented control in share menu --- submodules/ShareController/BUILD | 1 + .../Sources/ShareController.swift | 26 ++++----- .../Sources/ShareControllerNode.swift | 33 ++++++++---- .../Sources/SharePeersContainerNode.swift | 54 ++++++++++--------- .../GroupCallNavigationAccessoryPanel.swift | 2 +- .../Sources/VoiceChatActionButton.swift | 2 +- .../Sources/VoiceChatMicrophoneNode.swift | 17 ++++-- .../Sources/VoiceChatParticipantItem.swift | 42 ++++++++++++++- 8 files changed, 119 insertions(+), 58 deletions(-) diff --git a/submodules/ShareController/BUILD b/submodules/ShareController/BUILD index 8b8fa4ff0c..0ea81b3ebf 100644 --- a/submodules/ShareController/BUILD +++ b/submodules/ShareController/BUILD @@ -26,6 +26,7 @@ swift_library( "//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramIntents:TelegramIntents", "//submodules/AccountContext:AccountContext", + "//submodules/SegmentedControlNode:SegmentedControlNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 2cf7ced645..3c1dadb41c 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -39,6 +39,13 @@ public enum ShareControllerExternalStatus { case done } +public struct ShareControllerSegmentedValue { + let title: String + let subject: ShareControllerSubject + let actionTitle: String + let formatSendTitle: (Int) -> String +} + public enum ShareControllerSubject { case url(String) case text(String) @@ -293,8 +300,7 @@ public final class ShareController: ViewController { private let presetText: String? private let switchableAccounts: [AccountWithInfo] private let immediatePeerId: PeerId? - private let openStats: (() -> Void)? - private let shares: Int? + private let segmentedValues: [ShareControllerSegmentedValue]? private let fromForeignApp: Bool private let peers = Promise<([(RenderedPeer, PeerPresence?)], Peer)>() @@ -314,11 +320,11 @@ public final class ShareController: ViewController { } } - public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, fromForeignApp: Bool = false, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) { - self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, openStats: openStats, fromForeignApp: fromForeignApp, shares: shares, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId, forcedTheme: forcedTheme, forcedActionTitle: forcedActionTitle) + public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) { + self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, fromForeignApp: fromForeignApp, segmentedValues: segmentedValues, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId, forcedTheme: forcedTheme, forcedActionTitle: forcedActionTitle) } - public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, fromForeignApp: Bool = false, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) { + public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) { self.sharedContext = sharedContext self.currentContext = currentContext self.currentAccount = currentContext.account @@ -328,9 +334,8 @@ public final class ShareController: ViewController { self.immediateExternalShare = immediateExternalShare self.switchableAccounts = switchableAccounts self.immediatePeerId = immediatePeerId - self.openStats = openStats self.fromForeignApp = fromForeignApp - self.shares = shares + self.segmentedValues = segmentedValues self.forcedTheme = forcedTheme self.presentationData = self.sharedContext.currentPresentationData.with { $0 } @@ -468,7 +473,7 @@ public final class ShareController: ViewController { return } strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares, fromForeignApp: self.fromForeignApp, forcedTheme: self.forcedTheme) + }, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forcedTheme: self.forcedTheme, segmentedValues: self.segmentedValues) self.controllerNode.completed = self.completed self.controllerNode.dismiss = { [weak self] shared in self?.presentingViewController?.dismiss(animated: false, completion: nil) @@ -770,11 +775,6 @@ public final class ShareController: ViewController { strongSelf.view.endEditing(true) strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } - if case .messages = self.subject, let openStats = self.openStats { - self.controllerNode.openStats = { - openStats() - } - } self.displayNodeDidLoad() self.peersDisposable.set((self.peers.get() diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index e5846646d1..7958b09c76 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -29,8 +29,9 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate private let externalShare: Bool private let immediateExternalShare: Bool private var immediatePeerId: PeerId? - private let shares: Int? private let fromForeignApp: Bool + private let segmentedValues: [ShareControllerSegmentedValue]? + private var selectedSegmentedIndex: Int = 0 private let defaultAction: ShareControllerAction? private let requestLayout: (ContainedViewLayoutTransition) -> Void @@ -79,16 +80,16 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate private let presetText: String? - init(sharedContext: SharedAccountContext, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, shares: Int?, fromForeignApp: Bool, forcedTheme: PresentationTheme?) { + init(sharedContext: SharedAccountContext, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, fromForeignApp: Bool, forcedTheme: PresentationTheme?, segmentedValues: [ShareControllerSegmentedValue]?) { self.sharedContext = sharedContext self.presentationData = sharedContext.currentPresentationData.with { $0 } self.forcedTheme = forcedTheme self.externalShare = externalShare self.immediateExternalShare = immediateExternalShare self.immediatePeerId = immediatePeerId - self.shares = shares self.fromForeignApp = fromForeignApp self.presentError = presentError + self.segmentedValues = segmentedValues self.presetText = presetText @@ -700,7 +701,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate let animated = self.peersContentNode == nil let peersContentNode = SharePeersContainerNode(sharedContext: self.sharedContext, context: context, switchableAccounts: switchableAccounts, theme: self.presentationData.theme, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, peers: peers, accountPeer: accountPeer, controllerInteraction: self.controllerInteraction!, externalShare: self.externalShare, switchToAnotherAccount: { [weak self] in self?.switchToAnotherAccount?() - }, extendedInitialReveal: self.presetText != nil, statsCount: self.shares) + }, extendedInitialReveal: self.presetText != nil, segmentedValues: self.segmentedValues) self.peersContentNode = peersContentNode peersContentNode.openSearch = { [weak self] in let _ = (recentlySearchedPeers(postbox: context.account.postbox) @@ -766,12 +767,10 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate peersContentNode.openShare = { openShare(false) } - if let openStats = self.openStats { - peersContentNode.openStats = { [weak self] in - openStats() - self?.animateOut(shared: true, completion: { - self?.dismiss?(true) - }) + peersContentNode.segmentedSelectedIndexUpdated = { [weak self] index in + if let strongSelf = self, let segmentedValues = strongSelf.segmentedValues { + strongSelf.selectedSegmentedIndex = index + strongSelf.updateButton() } } if self.immediateExternalShare { @@ -837,6 +836,11 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate self.actionButtonNode.setTitle(self.presentationData.strings.ShareMenu_Send, with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.disabledActionTextColor, for: .normal) self.actionButtonNode.isEnabled = false self.actionButtonNode.badge = nil + } else if let segmentedValues = self.segmentedValues { + let value = segmentedValues[self.selectedSegmentedIndex] + self.actionButtonNode.setTitle(value.actionTitle, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal) + self.actionButtonNode.isEnabled = true + self.actionButtonNode.badge = nil } else if let defaultAction = self.defaultAction { self.actionButtonNode.setTitle(defaultAction.title, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal) self.actionButtonNode.isEnabled = true @@ -847,8 +851,15 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate self.actionButtonNode.badge = nil } } else { + let text: String + if let segmentedValues = self.segmentedValues { + let value = segmentedValues[self.selectedSegmentedIndex] + text = value.formatSendTitle(self.controllerInteraction!.selectedPeers.count) + } else { + text = self.presentationData.strings.ShareMenu_Send + } self.actionButtonNode.isEnabled = true - self.actionButtonNode.setTitle(self.presentationData.strings.ShareMenu_Send, with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal) + self.actionButtonNode.setTitle(text, with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal) self.actionButtonNode.badge = "\(self.controllerInteraction!.selectedPeers.count)" } } diff --git a/submodules/ShareController/Sources/SharePeersContainerNode.swift b/submodules/ShareController/Sources/SharePeersContainerNode.swift index e55b8e754d..6fc7f407e0 100644 --- a/submodules/ShareController/Sources/SharePeersContainerNode.swift +++ b/submodules/ShareController/Sources/SharePeersContainerNode.swift @@ -13,6 +13,7 @@ import AvatarNode import AccountContext import PeerPresenceStatusManager import AppBundle +import SegmentedControlNode private let subtitleFont = Font.regular(12.0) @@ -81,7 +82,6 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { private let controllerInteraction: ShareControllerInteraction private let switchToAnotherAccount: () -> Void private let extendedInitialReveal: Bool - private let statsCount: Int? let accountPeer: Peer private let foundPeers = Promise<[RenderedPeer]>([]) @@ -97,13 +97,15 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { private let contentSeparatorNode: ASDisplayNode private let searchButtonNode: HighlightableButtonNode private let shareButtonNode: HighlightableButtonNode - private let statsButtonNode: HighlightableButtonNode + private let segmentedNode: SegmentedControlNode + + private let segmentedValues: [ShareControllerSegmentedValue]? private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)? var openSearch: (() -> Void)? var openShare: (() -> Void)? - var openStats: (() -> Void)? + var segmentedSelectedIndexUpdated: ((Int) -> Void)? private var ensurePeerVisibleOnLayout: PeerId? private var validLayout: (CGSize, CGFloat)? @@ -111,7 +113,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { let peersValue = Promise<[(RenderedPeer, PeerPresence?)]>() - init(sharedContext: SharedAccountContext, context: AccountContext, switchableAccounts: [AccountWithInfo], theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peers: [(RenderedPeer, PeerPresence?)], accountPeer: Peer, controllerInteraction: ShareControllerInteraction, externalShare: Bool, switchToAnotherAccount: @escaping () -> Void, extendedInitialReveal: Bool, statsCount: Int?) { + init(sharedContext: SharedAccountContext, context: AccountContext, switchableAccounts: [AccountWithInfo], theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peers: [(RenderedPeer, PeerPresence?)], accountPeer: Peer, controllerInteraction: ShareControllerInteraction, externalShare: Bool, switchToAnotherAccount: @escaping () -> Void, extendedInitialReveal: Bool, segmentedValues: [ShareControllerSegmentedValue]?) { self.sharedContext = sharedContext self.context = context self.theme = theme @@ -121,7 +123,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.accountPeer = accountPeer self.switchToAnotherAccount = switchToAnotherAccount self.extendedInitialReveal = extendedInitialReveal - self.statsCount = statsCount + self.segmentedValues = segmentedValues self.peersValue.set(.single(peers)) @@ -176,14 +178,18 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.searchButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/SearchIcon"), color: self.theme.actionSheet.controlAccentColor), for: []) self.shareButtonNode = HighlightableButtonNode() - self.shareButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/ShareIcon"), color: self.theme.actionSheet.controlAccentColor), for: []) + self.shareButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/ShareIcon"), color: self.theme.actionSheet.controlAccentColor), for: [])  + + let segmentedItems: [SegmentedControlItem] + if let segmentedValues = segmentedValues { + segmentedItems = segmentedValues.map { SegmentedControlItem(title: $0.title) } + } else { + segmentedItems = [] + } + self.segmentedNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segmentedItems, selectedIndex: 0) - self.statsButtonNode = HighlightableButtonNode() - self.statsButtonNode.setAttributedTitle(NSAttributedString(string: "\(statsCount ?? 0) Shares", font: Font.regular(17.0), textColor: self.theme.actionSheet.controlAccentColor), for: .normal) - self.statsButtonNode.isHidden = statsCount == nil - - self.contentTitleNode.isHidden = !self.statsButtonNode.isHidden - self.contentSubtitleNode.isHidden = !self.statsButtonNode.isHidden + self.contentTitleNode.isHidden = self.segmentedValues != nil + self.contentSubtitleNode.isHidden = self.segmentedValues != nil self.contentSeparatorNode = ASDisplayNode() self.contentSeparatorNode.isLayerBacked = true @@ -201,11 +207,12 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.addSubnode(self.contentTitleNode) self.addSubnode(self.contentSubtitleNode) self.addSubnode(self.contentTitleAccountNode) + self.addSubnode(self.segmentedNode) self.addSubnode(self.searchButtonNode) self.addSubnode(self.shareButtonNode) - self.addSubnode(self.statsButtonNode) self.addSubnode(self.contentSeparatorNode) + let previousItems = Atomic<[SharePeerEntry]?>(value: []) self.disposable.set((items |> deliverOnMainQueue).start(next: { [weak self] entries in @@ -225,8 +232,11 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.searchButtonNode.addTarget(self, action: #selector(self.searchPressed), forControlEvents: .touchUpInside) self.shareButtonNode.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside) - self.statsButtonNode.addTarget(self, action: #selector(self.statsPressed), forControlEvents: .touchUpInside) self.contentTitleAccountNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.accountTapGesture(_:)))) + + self.segmentedNode.selectedIndexChanged = { [weak self] index in + self?.segmentedSelectedIndexUpdated?(index) + } } deinit { @@ -353,10 +363,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { originalSubtitleFrame.size = subtitleFrame.size self.contentSubtitleNode.frame = originalSubtitleFrame transition.updateFrame(node: self.contentSubtitleNode, frame: subtitleFrame) - - let statsSize = self.statsButtonNode.measure(CGSize(width: size.width - 44.0 * 2.0 - 8.0 * 2.0, height: titleAreaHeight)) - transition.updateFrame(node: self.statsButtonNode, frame: CGRect(origin: CGPoint(x: floor((size.width - statsSize.width) / 2.0), y: titleOffset + 22.0), size: statsSize)) - + let titleButtonSize = CGSize(width: 44.0, height: 44.0) let searchButtonFrame = CGRect(origin: CGPoint(x: 12.0, y: titleOffset + 12.0), size: titleButtonSize) transition.updateFrame(node: self.searchButtonNode, frame: searchButtonFrame) @@ -364,6 +371,9 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { let shareButtonFrame = CGRect(origin: CGPoint(x: size.width - titleButtonSize.width - 12.0, y: titleOffset + 12.0), size: titleButtonSize) transition.updateFrame(node: self.shareButtonNode, frame: shareButtonFrame) + let segmentedSize = self.segmentedNode.updateLayout(.sizeToFit(maximumWidth: size.width - titleButtonSize.width * 2.0, minimumWidth: 160.0, height: 32.0), transition: transition) + transition.updateFrame(node: self.segmentedNode, frame: CGRect(origin: CGPoint(x: floor((size.width - segmentedSize.width) / 2.0), y: titleOffset + 18.0), size: segmentedSize)) + let avatarButtonSize = CGSize(width: 36.0, height: 36.0) let avatarButtonFrame = CGRect(origin: CGPoint(x: size.width - avatarButtonSize.width - 20.0, y: titleOffset + 15.0), size: avatarButtonSize) transition.updateFrame(node: self.contentTitleAccountNode, frame: avatarButtonFrame) @@ -392,12 +402,10 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } func updateSelectedPeers() { - if let _ = self.openStats, self.controllerInteraction.selectedPeers.isEmpty { - self.statsButtonNode.isHidden = false + if self.segmentedValues != nil { self.contentTitleNode.isHidden = true self.contentSubtitleNode.isHidden = true } else { - self.statsButtonNode.isHidden = true self.contentTitleNode.isHidden = false self.contentSubtitleNode.isHidden = false @@ -435,10 +443,6 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.openShare?() } - @objc func statsPressed() { - self.openStats?() - } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let nodes: [ASDisplayNode] = [self.searchButtonNode, self.shareButtonNode, self.contentTitleAccountNode] for node in nodes { diff --git a/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift b/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift index 0455340834..0747e57a8c 100644 --- a/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift +++ b/submodules/TelegramCallsUI/Sources/GroupCallNavigationAccessoryPanel.swift @@ -484,7 +484,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode { } else { isMuted = false } - self.micButtonForegroundNode.update(state: VoiceChatMicrophoneNode.State(muted: isMuted, color: UIColor.white), animated: transition.isAnimated) + self.micButtonForegroundNode.update(state: VoiceChatMicrophoneNode.State(muted: isMuted, filled: false, color: UIColor.white), animated: transition.isAnimated) if isMuted != self.micButtonBackgroundNodeIsMuted { self.micButtonBackgroundNodeIsMuted = isMuted diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift index 98300a09a4..d4ef06540d 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatActionButton.swift @@ -254,7 +254,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode { case .connecting: break } - self.iconNode.update(state: VoiceChatMicrophoneNode.State(muted: iconMuted, color: iconColor), animated: true) + self.iconNode.update(state: VoiceChatMicrophoneNode.State(muted: iconMuted, filled: true, color: iconColor), animated: true) } func update(snap: Bool, animated: Bool) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift index 230a33fdeb..e8652d6160 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift @@ -5,11 +5,13 @@ import Display private final class VoiceChatMicrophoneNodeDrawingState: NSObject { let color: UIColor + let filled: Bool let transition: CGFloat let reverse: Bool - init(color: UIColor, transition: CGFloat, reverse: Bool) { + init(color: UIColor, filled: Bool, transition: CGFloat, reverse: Bool) { self.color = color + self.filled = filled self.transition = transition self.reverse = reverse @@ -21,9 +23,11 @@ final class VoiceChatMicrophoneNode: ASDisplayNode { class State: Equatable { let muted: Bool let color: UIColor + let filled: Bool - init(muted: Bool, color: UIColor) { + init(muted: Bool, filled: Bool, color: UIColor) { self.muted = muted + self.filled = filled self.color = color } @@ -34,6 +38,9 @@ final class VoiceChatMicrophoneNode: ASDisplayNode { if lhs.color.argb != rhs.color.argb { return false } + if lhs.filled != rhs.filled { + return false + } return true } } @@ -53,7 +60,7 @@ final class VoiceChatMicrophoneNode: ASDisplayNode { private var animator: ConstantDisplayLinkAnimator? private var hasState = false - private var state: State = State(muted: false, color: .black) + private var state: State = State(muted: false, filled: false, color: .black) private var transitionContext: TransitionContext? override init() { @@ -133,7 +140,7 @@ final class VoiceChatMicrophoneNode: ASDisplayNode { } } - return VoiceChatMicrophoneNodeDrawingState(color: color, transition: transitionFraction, reverse: reverse) + return VoiceChatMicrophoneNodeDrawingState(color: color, filled: self.state.filled, transition: transitionFraction, reverse: reverse) } @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -167,7 +174,7 @@ final class VoiceChatMicrophoneNode: ASDisplayNode { context.translateBy(x: 18.0, y: 18.0) let _ = try? drawSvgPath(context, path: "M-0.004000000189989805,-9.86400032043457 C2.2960000038146973,-9.86400032043457 4.165999889373779,-8.053999900817871 4.25600004196167,-5.77400016784668 C4.25600004196167,-5.77400016784668 4.265999794006348,-5.604000091552734 4.265999794006348,-5.604000091552734 C4.265999794006348,-5.604000091552734 4.265999794006348,-0.8040000200271606 4.265999794006348,-0.8040000200271606 C4.265999794006348,1.555999994277954 2.3559999465942383,3.4660000801086426 -0.004000000189989805,3.4660000801086426 C-2.2939999103546143,3.4660000801086426 -4.164000034332275,1.6460000276565552 -4.263999938964844,-0.6240000128746033 C-4.263999938964844,-0.6240000128746033 -4.263999938964844,-0.8040000200271606 -4.263999938964844,-0.8040000200271606 C-4.263999938964844,-0.8040000200271606 -4.263999938964844,-5.604000091552734 -4.263999938964844,-5.604000091552734 C-4.263999938964844,-7.953999996185303 -2.3540000915527344,-9.86400032043457 -0.004000000189989805,-9.86400032043457 Z ") } - if bounds.width > 30.0 { + if bounds.width > 30.0 && !parameters.filled { context.setBlendMode(.clear) let _ = try? drawSvgPath(context, path: "M0.004000000189989805,-8.53600025177002 C-1.565999984741211,-8.53600025177002 -2.8459999561309814,-7.306000232696533 -2.936000108718872,-5.75600004196167 C-2.936000108718872,-5.75600004196167 -2.936000108718872,-5.5960001945495605 -2.936000108718872,-5.5960001945495605 C-2.936000108718872,-5.5960001945495605 -2.936000108718872,-0.7960000038146973 -2.936000108718872,-0.7960000038146973 C-2.936000108718872,0.8240000009536743 -1.6260000467300415,2.134000062942505 0.004000000189989805,2.134000062942505 C1.5740000009536743,2.134000062942505 2.8540000915527344,0.9039999842643738 2.934000015258789,-0.6460000276565552 C2.934000015258789,-0.6460000276565552 2.934000015258789,-0.7960000038146973 2.934000015258789,-0.7960000038146973 C2.934000015258789,-0.7960000038146973 2.934000015258789,-5.5960001945495605 2.934000015258789,-5.5960001945495605 C2.934000015258789,-7.22599983215332 1.6239999532699585,-8.53600025177002 0.004000000189989805,-8.53600025177002 Z ") diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index 1927f9efbf..13343871f8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -159,6 +159,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { fileprivate let avatarNode: AvatarNode private let titleNode: TextNode private let statusNode: TextNode + private var credibilityIconNode: ASImageNode? private let actionContainerNode: ASDisplayNode private var animationNode: VoiceChatMicrophoneNode? @@ -390,8 +391,26 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let verticalInset: CGFloat = 8.0 let verticalOffset: CGFloat = 0.0 let avatarSize: CGFloat = 40.0 + + var titleIconsWidth: CGFloat = 0.0 + var currentCredibilityIconImage: UIImage? + var credibilityIconOffset: CGFloat = 0.0 + if item.peer.isScam { + currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) + credibilityIconOffset = 2.0 + } else if item.peer.isFake { + currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) + credibilityIconOffset = 2.0 + } else if item.peer.isVerified { + currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) + credibilityIconOffset = 3.0 + } + + if let currentCredibilityIconImage = currentCredibilityIconImage { + titleIconsWidth += 4.0 + currentCredibilityIconImage.size.width + } - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - rightInset - 30.0 - titleIconsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let insets = UIEdgeInsets() @@ -549,6 +568,25 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + verticalOffset), size: titleLayout.size)) transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size)) + if let currentCredibilityIconImage = currentCredibilityIconImage { + let iconNode: ASImageNode + if let current = strongSelf.credibilityIconNode { + iconNode = current + } else { + iconNode = ASImageNode() + iconNode.isLayerBacked = true + iconNode.displaysAsynchronously = false + iconNode.displayWithoutProcessing = true + strongSelf.offsetContainerNode.addSubnode(iconNode) + strongSelf.credibilityIconNode = iconNode + } + iconNode.image = currentCredibilityIconImage + transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: leftInset + titleLayout.size.width + 3.0, y: verticalInset + credibilityIconOffset), size: currentCredibilityIconImage.size)) + } else if let credibilityIconNode = strongSelf.credibilityIconNode { + strongSelf.credibilityIconNode = nil + credibilityIconNode.removeFromSupernode() + } + let avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)) transition.updateFrameAsPositionAndBounds(node: strongSelf.avatarNode, frame: avatarFrame) @@ -636,7 +674,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { animationNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) } } - animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, color: color), animated: true) + animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: false, color: color), animated: true) strongSelf.actionButtonNode.isUserInteractionEnabled = item.contextAction != nil } else if let animationNode = strongSelf.animationNode { strongSelf.animationNode = nil