From 398184751412ae0c1239925cc77f3867d89709b2 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 17 Dec 2022 00:17:47 +0400 Subject: [PATCH] Update hide members UI --- .../Sources/Items/ItemListSwitchItem.swift | 37 +++++++++- .../Sources/ChannelMembersController.swift | 74 ++++++++++++++++--- 2 files changed, 101 insertions(+), 10 deletions(-) diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift index afead08bf9..07d6e201f7 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramPresentationData import SwitchNode +import AppBundle public enum ItemListSwitchItemNodeType { case regular @@ -19,6 +20,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem { let type: ItemListSwitchItemNodeType let enableInteractiveChanges: Bool let enabled: Bool + let displayLocked: Bool let disableLeadingInset: Bool let maximumNumberOfLines: Int let noCorners: Bool @@ -28,7 +30,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem { let activatedWhileDisabled: () -> Void public let tag: ItemListItemTag? - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) { + public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) { self.presentationData = presentationData self.icon = icon self.title = title @@ -36,6 +38,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem { self.type = type self.enableInteractiveChanges = enableInteractiveChanges self.enabled = enabled + self.displayLocked = displayLocked self.disableLeadingInset = disableLeadingInset self.maximumNumberOfLines = maximumNumberOfLines self.noCorners = noCorners @@ -128,6 +131,8 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { private let switchGestureNode: ASDisplayNode private var disabledOverlayNode: ASDisplayNode? + private var lockedIconNode: ASImageNode? + private let activateArea: AccessibilityAreaNode private var item: ItemListSwitchItem? @@ -405,6 +410,36 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { } strongSelf.switchGestureNode.isHidden = item.enableInteractiveChanges && item.enabled + if item.displayLocked { + var updateLockedIconImage = false + if let _ = updatedTheme { + updateLockedIconImage = true + } + + let lockedIconNode: ASImageNode + if let current = strongSelf.lockedIconNode { + lockedIconNode = current + } else { + updateLockedIconImage = true + lockedIconNode = ASImageNode() + strongSelf.lockedIconNode = lockedIconNode + strongSelf.insertSubnode(lockedIconNode, aboveSubnode: strongSelf.switchNode) + } + + if updateLockedIconImage, let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: item.presentationData.theme.list.itemSecondaryTextColor) { + lockedIconNode.image = image + } + + let switchFrame = strongSelf.switchNode.frame + + if let icon = lockedIconNode.image { + lockedIconNode.frame = CGRect(origin: CGPoint(x: switchFrame.minX + 10.0 + UIScreenPixel, y: switchFrame.minY + 9.0), size: icon.size) + } + } else if let lockedIconNode = strongSelf.lockedIconNode { + strongSelf.lockedIconNode = nil + lockedIconNode.removeFromSupernode() + } + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel)) } }) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index f948385f1b..d4d163f8fe 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -14,6 +14,7 @@ import PresentationDataUtils import ItemListPeerItem import ItemListPeerActionItem import InviteLinksUI +import UndoUI private final class ChannelMembersControllerArguments { let context: AccountContext @@ -24,8 +25,9 @@ private final class ChannelMembersControllerArguments { let openPeer: (Peer) -> Void let inviteViaLink: () -> Void let updateHideMembers: (Bool) -> Void + let displayHideMembersTip: (HideMembersDisabledReason) -> Void - init(context: AccountContext, addMember: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (Peer) -> Void, inviteViaLink: @escaping () -> Void, updateHideMembers: @escaping (Bool) -> Void) { + init(context: AccountContext, addMember: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (Peer) -> Void, inviteViaLink: @escaping () -> Void, updateHideMembers: @escaping (Bool) -> Void, displayHideMembersTip: @escaping (HideMembersDisabledReason) -> Void) { self.context = context self.addMember = addMember self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions @@ -33,6 +35,7 @@ private final class ChannelMembersControllerArguments { self.openPeer = openPeer self.inviteViaLink = inviteViaLink self.updateHideMembers = updateHideMembers + self.displayHideMembersTip = displayHideMembersTip } } @@ -48,8 +51,13 @@ private enum ChannelMembersEntryStableId: Hashable { case peer(PeerId) } +private enum HideMembersDisabledReason: Equatable { + case notEnoughMembers(Int) + case notAllowed +} + private enum ChannelMembersEntry: ItemListNodeEntry { - case hideMembers(String, Bool, Bool) + case hideMembers(text: String, disabledReason: HideMembersDisabledReason?, isInteractive: Bool, value: Bool) case hideMembersInfo(String) case addMember(PresentationTheme, String) case addMemberInfo(PresentationTheme, String) @@ -96,8 +104,8 @@ private enum ChannelMembersEntry: ItemListNodeEntry { static func ==(lhs: ChannelMembersEntry, rhs: ChannelMembersEntry) -> Bool { switch lhs { - case let .hideMembers(text, enabled, value): - if case .hideMembers(text, enabled, value) = rhs { + case let .hideMembers(text, enabled, isInteractive, value): + if case .hideMembers(text, enabled, isInteractive, value) = rhs { return true } else { return false @@ -244,9 +252,17 @@ private enum ChannelMembersEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChannelMembersControllerArguments switch self { - case let .hideMembers(text, enabled, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in - arguments.updateHideMembers(value) + case let .hideMembers(text, disabledReason, isInteractive, value): + return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !value && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in + if let disabledReason { + arguments.displayHideMembersTip(disabledReason) + } else { + arguments.updateHideMembers(value) + } + }, activatedWhileDisabled: { + if let disabledReason { + arguments.displayHideMembersTip(disabledReason) + } }) case let .hideMembersInfo(text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) @@ -349,13 +365,33 @@ private func channelMembersControllerEntries(context: AccountContext, presentati } var membersHidden = false + var memberCount: Int? if let cachedData = view.cachedData as? CachedChannelData, case let .known(value) = cachedData.membersHidden { membersHidden = value.value + memberCount = cachedData.participantsSummary.memberCount.flatMap(Int.init) } if displayHideMembers { + let appConfiguration = context.currentAppConfiguration.with({ $0 }) + var minMembers = 100 + if let data = appConfiguration.data, let value = data["hidden_members_group_size_min"] as? Double { + minMembers = Int(value) + } + + var disabledReason: HideMembersDisabledReason? + if memberCount ?? 0 < minMembers { + disabledReason = .notEnoughMembers(minMembers) + } else if !canSetupHideMembers { + disabledReason = .notAllowed + } + + var isInteractive = canSetupHideMembers + if canSetupHideMembers && !membersHidden && disabledReason != nil { + isInteractive = false + } + //TODO:localize - entries.append(.hideMembers("Hide Members", canSetupHideMembers, membersHidden)) + entries.append(.hideMembers(text: "Hide Members", disabledReason: disabledReason, isInteractive: isInteractive, value: membersHidden)) let infoText: String if membersHidden { @@ -366,7 +402,7 @@ private func channelMembersControllerEntries(context: AccountContext, presentati entries.append(.hideMembersInfo(infoText)) } - if !membersHidden, let participants = participants, let contacts = contacts { + if let participants = participants, let contacts = contacts { var canAddMember: Bool = false if let peer = view.peers[view.peerId] as? TelegramChannel { canAddMember = peer.hasPermission(.inviteMembers) @@ -462,6 +498,8 @@ public func channelMembersController(context: AccountContext, updatedPresentatio var getControllerImpl: (() -> ViewController?)? + var displayHideMembersTip: ((HideMembersDisabledReason) -> Void)? + let actionsDisposable = DisposableSet() let addMembersDisposable = MetaDisposable() @@ -592,6 +630,8 @@ public func channelMembersController(context: AccountContext, updatedPresentatio } }, updateHideMembers: { value in let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start() + }, displayHideMembersTip: { disabledReason in + displayHideMembersTip?(disabledReason) }) let peerView = context.account.viewTracker.peerView(peerId) @@ -722,6 +762,22 @@ public func channelMembersController(context: AccountContext, updatedPresentatio dismissInputImpl = { [weak controller] in controller?.view.endEditing(true) } + displayHideMembersTip = { [weak controller] reason in + guard let controller else { + return + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let text: String + switch reason { + case let .notEnoughMembers(minCount): + text = presentationData.strings.PeerInfo_HideMembersLimitedParticipantCountText(Int32(minCount)) + case .notAllowed: + text = presentationData.strings.PeerInfo_HideMembersLimitedRights + } + controller.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_topics", scale: 0.066, colors: [:], title: nil, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } getControllerImpl = { [weak controller] in return controller }