From 4203d70414fa06236972a2addf45eaee7d7fac9d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 23 Feb 2026 18:45:47 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../Sources/AccountContext.swift | 1 - .../ChatListUI/Sources/ChatContextMenus.swift | 2 +- .../Sources/ChatListController.swift | 30 ++ .../Sources/ContactsPeerItem.swift | 72 +++- .../ChannelBannedMemberController.swift | 2 +- .../Sources/ChannelMembersController.swift | 13 +- .../ChannelPermissionsController.swift | 16 +- .../CameraScreen/Sources/CameraScreen.swift | 15 +- .../ChatChannelSubscriberInputPanelNode.swift | 38 ++- .../ChatRecentActionsHistoryTransition.swift | 2 + .../Sources/ChatParticipantRightsScreen.swift | 322 ++++++------------ .../Sources/MinimizedHeaderNode.swift | 4 +- .../Sources/Panes/PeerInfoMembersPane.swift | 64 +--- .../PeerInfoScreen/Sources/PeerInfoData.swift | 23 +- .../Sources/PeerInfoProfileItems.swift | 5 +- .../Sources/PeerInfoScreen.swift | 19 +- .../Sources/PeerInfoScreenOpenMember.swift | 102 +++--- .../TelegramUI/Sources/ChatController.swift | 21 +- .../Sources/SharedAccountContext.swift | 4 - 20 files changed, 408 insertions(+), 350 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 413ff1ac2d..816225b03b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15825,3 +15825,6 @@ Error: %8$@"; "Group.EditAdmin.MemberTagInfo" = "Add short tag next to %@'s name."; "Channel.Owner.Title" = "Owner"; + +"Channel.AdminLog.EditRankOwn" = "Edit Member Tag"; +"Channel.AdminLog.EditRank" = "Edit Member Tags"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index cd086902b3..0b6d511a1f 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1528,7 +1528,6 @@ public protocol SharedAccountContext: AnyObject { func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController func makePasskeySetupController(context: AccountContext, displaySkip: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController - func makeChatParticipantRightsScreen(context: AccountContext, peerId: EnginePeer.Id, participantId: EnginePeer.Id, rank: String?) -> ViewController func makeChatCustomRankSetupScreen(context: AccountContext, peerId: EnginePeer.Id, participantId: EnginePeer.Id, rank: String?) -> ViewController func makePeerCopyProtectionInfoScreen(context: AccountContext, completion: @escaping () -> Void) -> ViewController diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 46b79efa80..ef9229872e 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -505,7 +505,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } joinChannelDisposable.set((createSignal - |> deliverOnMainQueue).start(next: { _ in + |> deliverOnMainQueue).start(next: { _ in }, error: { _ in if let chatListController = chatListController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 9dc0423319..d87f126be4 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -5242,6 +5242,36 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }, completed: { [weak self] in + guard let self else { + return + } + Queue.mainQueue().after(0.5) { + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self, let peer = peer?._asPeer() else { + return + } + var canEditRank = false + if let channel = peer as? TelegramChannel, channel.hasPermission(.editRank) { + canEditRank = true + } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { + canEditRank = true + } + //TODO:localize + if canEditRank { + let context = self.context + let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: "You joined the group", cancel: "Add Tag", destructive: false), elevatedLayout: true, action: { [weak self] action in + if let self, case .undo = action { + let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peerId, participantId: context.account.peerId, rank: nil) + self.push(tagController) + } + return true + }) + self.present(controller, in: .current) + } + }) + } }) })) case .savedMessagesChats: diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 6b9dded5d8..294ffad844 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -173,6 +173,18 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { case firstNameFirst case lastNameFirst } + + public struct LabelText { + let text: String + let color: UIColor + let hasBackground: Bool + + public init(text: String, color: UIColor, hasBackground: Bool) { + self.text = text + self.color = color + self.hasBackground = hasBackground + } + } let presentationData: ItemListPresentationData let style: ItemListStyle @@ -186,7 +198,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { public let peer: ContactsPeerItemPeer let status: ContactsPeerItemStatus let badge: ContactsPeerItemBadge? - let rightLabelText: String? + let rightLabelText: LabelText? let requiresPremiumForMessaging: Bool let enabled: Bool let selection: ContactsPeerItemSelection @@ -233,7 +245,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, - rightLabelText: String? = nil, + rightLabelText: LabelText? = nil, requiresPremiumForMessaging: Bool = false, enabled: Bool, selection: ContactsPeerItemSelection, @@ -471,6 +483,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private var actionButtonNodes: [HighlightableButtonNode]? private var moreButtonNode: MoreButtonNode? private var arrowButtonNode: HighlightableButtonNode? + private let labelBadgeNode: ASImageNode private var rightLabelTextNode: TextNode? private var adButton: HighlightableButtonNode? @@ -587,6 +600,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { self.titleNode = TextNode() self.statusNode = TextNodeWithEntities() + self.labelBadgeNode = ASImageNode() + self.labelBadgeNode.displayWithoutProcessing = true + self.labelBadgeNode.displaysAsynchronously = false + self.labelBadgeNode.isLayerBacked = true + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true @@ -753,6 +771,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { let currentItem = self.layoutParams?.0 + let currentHasBadge = self.labelBadgeNode.image != nil + return { [weak self] item, params, first, last, firstWithHeader, neighbors in var updatedTheme: PresentationTheme? @@ -807,7 +827,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { var rightLabelTextLayoutAndApply: (TextNodeLayout, () -> TextNode)? if let rightLabelText = item.rightLabelText { - let rightLabelTextLayoutAndApplyValue = makeRightLabelTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: rightLabelText, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 20.0, height: 100.0))) + let rightLabelTextLayoutAndApplyValue = makeRightLabelTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: rightLabelText.text, font: statusFont, textColor: rightLabelText.color), maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 20.0, height: 100.0))) rightLabelTextLayoutAndApply = rightLabelTextLayoutAndApplyValue rightInset -= 6.0 + rightLabelTextLayoutAndApplyValue.0.size.width } @@ -1208,6 +1228,21 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { peerRevealOptions = [] } + var badgeColor: UIColor? + if let rightLabelText = item.rightLabelText, rightLabelText.hasBackground { + badgeColor = rightLabelText.color.withMultipliedAlpha(0.1) + } + + var updatedLabelBadgeImage: UIImage? + let badgeDiameter: CGFloat = 20.0 + if currentItem?.presentationData.theme !== item.presentationData.theme { + if let badgeColor = badgeColor { + updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor) + } + } else if let badgeColor = badgeColor, !currentHasBadge { + updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor) + } + return (nodeLayout, { [weak self] in if let strongSelf = self { return (.complete(), { [weak strongSelf] animated, synchronousLoads in @@ -1808,6 +1843,21 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } } + if let updateBadgeImage = updatedLabelBadgeImage { + if strongSelf.labelBadgeNode.supernode == nil { + if let rightLabelTextNode = strongSelf.rightLabelTextNode { + strongSelf.offsetContainerNode.insertSubnode(strongSelf.labelBadgeNode, belowSubnode: rightLabelTextNode) + } else { + strongSelf.offsetContainerNode.addSubnode(strongSelf.labelBadgeNode) + } + } + strongSelf.labelBadgeNode.image = updateBadgeImage + } + if badgeColor == nil && strongSelf.labelBadgeNode.supernode != nil { + strongSelf.labelBadgeNode.image = nil + strongSelf.labelBadgeNode.removeFromSupernode() + } + if let (rightLabelTextLayout, rightLabelTextApply) = rightLabelTextLayoutAndApply { let rightLabelTextNode = rightLabelTextApply() var rightLabelTextTransition = transition @@ -1818,11 +1868,25 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { rightLabelTextTransition = .immediate } - var rightLabelTextFrame = CGRect(x: revealOffset + params.width - params.rightInset - 8.0 - rightLabelTextLayout.size.width, y: floor((nodeLayout.contentSize.height - rightLabelTextLayout.size.height) / 2.0), width: rightLabelTextLayout.size.width, height: rightLabelTextLayout.size.height) + let rightInset: CGFloat + switch item.systemStyle { + case .glass: + rightInset = 16.0 + case .legacy: + rightInset = 8.0 + } + + var rightLabelTextFrame = CGRect(x: revealOffset + params.width - params.rightInset - rightInset - rightLabelTextLayout.size.width, y: floor((nodeLayout.contentSize.height - rightLabelTextLayout.size.height) / 2.0), width: rightLabelTextLayout.size.width, height: rightLabelTextLayout.size.height) + if strongSelf.labelBadgeNode.image != nil { + rightLabelTextFrame.origin.x -= 6.0 + } if let arrowButtonImage = arrowButtonImage { rightLabelTextFrame.origin.x -= arrowButtonImage.size.width + 6.0 } + let badgeWidth = max(badgeDiameter, rightLabelTextLayout.size.width + 12.0) + strongSelf.labelBadgeNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(rightLabelTextFrame.midX - badgeWidth * 0.5), y: floorToScreenPixels(rightLabelTextFrame.midY - badgeDiameter * 0.5)), size: CGSize(width: badgeWidth, height: badgeDiameter)) + rightLabelTextNode.bounds = CGRect(origin: CGPoint(), size: rightLabelTextFrame.size) rightLabelTextTransition.updatePosition(node: rightLabelTextNode, position: rightLabelTextFrame.center) } else if let rightLabelTextNode = strongSelf.rightLabelTextNode { diff --git a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift index 4f944e0e72..6ac05e5ef0 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift @@ -420,7 +420,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation return entries } -public func channelBannedMemberController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, memberId: PeerId, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatBannedRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController { +public func channelBannedMemberController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, memberId: PeerId, editMember: Bool = false, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatBannedRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController { let initialState = ChannelBannedMemberControllerState(referenceTimestamp: Int32(Date().timeIntervalSince1970), updatedFlags: nil, updatedTimeout: nil, updating: false) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index 78ceebd76d..4ab23ac3a4 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -23,7 +23,7 @@ private final class ChannelMembersControllerArguments { let addMember: () -> Void let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void let removePeer: (EnginePeer.Id) -> Void - let openPeer: (EnginePeer, String?) -> Void + let openParticipant: (RenderedChannelParticipant) -> Void let inviteViaLink: () -> Void let updateHideMembers: (Bool) -> Void let displayHideMembersTip: (HideMembersDisabledReason) -> Void @@ -33,7 +33,7 @@ private final class ChannelMembersControllerArguments { addMember: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, - openPeer: @escaping (EnginePeer, String?) -> Void, + openParticipant: @escaping (RenderedChannelParticipant) -> Void, inviteViaLink: @escaping () -> Void, updateHideMembers: @escaping (Bool) -> Void, displayHideMembersTip: @escaping (HideMembersDisabledReason) -> Void @@ -42,7 +42,7 @@ private final class ChannelMembersControllerArguments { self.addMember = addMember self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removePeer = removePeer - self.openPeer = openPeer + self.openParticipant = openParticipant self.inviteViaLink = inviteViaLink self.updateHideMembers = updateHideMembers self.displayHideMembersTip = displayHideMembersTip @@ -319,7 +319,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { } return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: label, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: { - arguments.openPeer(EnginePeer(participant.peer), participant.participant.rank) + arguments.openParticipant(participant) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in @@ -680,8 +680,9 @@ public func channelMembersController(context: AccountContext, updatedPresentatio return $0.withUpdatedRemovingPeerId(nil) } })) - }, openPeer: { peer, rank in - let controller = context.sharedContext.makeChatParticipantRightsScreen(context: context, peerId: peerId, participantId: peer.id, rank: rank) + }, openParticipant: { participant in + let controller = channelBannedMemberController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, memberId: participant.peer.id, editMember: true, initialParticipant: participant.participant, updated: { rights in + }, upgradedToSupergroup: { _, _ in }) pushControllerImpl?(controller) }, inviteViaLink: { if let controller = getControllerImpl?() { diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index a2ad2cea29..d5c725294f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -501,7 +501,7 @@ private struct ChannelPermissionsControllerState: Equatable { var expandedPermissions = Set() } -func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags, isForum: Bool) -> String { +func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags, isForum: Bool, defaultPermissions: Bool = false) -> String { if right.contains(.banSendText) { return strings.Channel_BanUser_PermissionSendMessages } else if right.contains(.banSendMedia) { @@ -536,7 +536,11 @@ func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatB return strings.Channel_BanUser_PermissionSendVideoMessage } else if right.contains(.banEditRank) { //TODO:localize - return "Edit Own Tags" + if defaultPermissions { + return "Edit Own Tags" + } else { + return "Edit Member Tag" + } } else { return "" } @@ -715,11 +719,11 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen for (subRight, _) in banSendMediaSubList() { let subRightEnabled = true - subItems.append(SubPermission(title: stringForGroupPermission(strings: presentationData.strings, right: subRight, isForum: channel.isForum), flags: subRight, isSelected: !effectiveRightsFlags.contains(subRight), isEnabled: enabled && subRightEnabled)) + subItems.append(SubPermission(title: stringForGroupPermission(strings: presentationData.strings, right: subRight, isForum: channel.isForum, defaultPermissions: true), flags: subRight, isSelected: !effectiveRightsFlags.contains(subRight), isEnabled: enabled && subRightEnabled)) } } - entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights, isForum: channel.isForum), isSelected, rights, enabled, subItems, state.expandedPermissions.contains(rights))) + entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights, isForum: channel.isForum, defaultPermissions: true), isSelected, rights, enabled, subItems, state.expandedPermissions.contains(rights))) rightIndex += 1 } @@ -799,11 +803,11 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen for (subRight, _) in banSendMediaSubList() { let subRightEnabled = true - subItems.append(SubPermission(title: stringForGroupPermission(strings: presentationData.strings, right: subRight, isForum: false), flags: subRight, isSelected: !effectiveRightsFlags.contains(subRight), isEnabled: subRightEnabled)) + subItems.append(SubPermission(title: stringForGroupPermission(strings: presentationData.strings, right: subRight, isForum: false, defaultPermissions: true), flags: subRight, isSelected: !effectiveRightsFlags.contains(subRight), isEnabled: subRightEnabled)) } } - entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights, isForum: false), isSelected, rights, true, subItems, state.expandedPermissions.contains(rights))) + entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights, isForum: false, defaultPermissions: true), isSelected, rights, true, subItems, state.expandedPermissions.contains(rights))) rightIndex += 1 } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 6840cfbd57..456d01e1fc 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -171,6 +171,7 @@ private final class CameraScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let mode: CameraScreenImpl.Mode let cameraState: CameraState let cameraAuthorizationStatus: AccessType let microphoneAuthorizationStatus: AccessType @@ -190,6 +191,7 @@ private final class CameraScreenComponent: CombinedComponent { init( context: AccountContext, + mode: CameraScreenImpl.Mode, cameraState: CameraState, cameraAuthorizationStatus: AccessType, microphoneAuthorizationStatus: AccessType, @@ -208,6 +210,7 @@ private final class CameraScreenComponent: CombinedComponent { openResolvedPeer: @escaping (EnginePeer) -> Void ) { self.context = context + self.mode = mode self.cameraState = cameraState self.cameraAuthorizationStatus = cameraAuthorizationStatus self.microphoneAuthorizationStatus = microphoneAuthorizationStatus @@ -230,6 +233,9 @@ private final class CameraScreenComponent: CombinedComponent { if lhs.context !== rhs.context { return false } + if lhs.mode != rhs.mode { + return false + } if lhs.cameraState != rhs.cameraState { return false } @@ -318,7 +324,7 @@ private final class CameraScreenComponent: CombinedComponent { fileprivate var sendAsPeerId: EnginePeer.Id? fileprivate var isCustomTarget = false - fileprivate var canLivestream = true + fileprivate var canLivestream = false private var privacy: EngineStoryPrivacy = EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []) private var allowComments = true @@ -345,6 +351,7 @@ private final class CameraScreenComponent: CombinedComponent { init( context: AccountContext, + mode: CameraScreenImpl.Mode, present: @escaping (ViewController) -> Void, completion: ActionSlot>, animateShutter: @escaping () -> Void = {}, @@ -380,6 +387,10 @@ private final class CameraScreenComponent: CombinedComponent { self.setupRecentAssetSubscription() } + if case .story = mode { + self.canLivestream = true + } + if let controller = getController() { if controller.resumeLiveStream { controller.updateCameraState({ $0.updatedMode(.live).updatedIsWaitingForStream(true) }, update: false, transition: .immediate) @@ -1299,6 +1310,7 @@ private final class CameraScreenComponent: CombinedComponent { func makeState() -> State { return State( context: self.context, + mode: self.mode, present: self.present, completion: self.completion, animateShutter: self.animateShutter, @@ -3707,6 +3719,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { component: AnyComponent( CameraScreenComponent( context: self.context, + mode: controller.mode, cameraState: self.cameraState, cameraAuthorizationStatus: self.cameraAuthorizationStatus, microphoneAuthorizationStatus: self.microphoneAuthorizationStatus, diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index 0051a057d7..f970400d6e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -213,18 +213,6 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { switch action { case .join, .joinGroup, .applyToJoin: - Queue.mainQueue().after(1.0) { - //TODO:localize - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: "You joined the group", cancel: "Add Tag", destructive: false), elevatedLayout: true, action: { action in - if case .undo = action { - let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peer.id, participantId: context.account.peerId, rank: nil) - self.interfaceInteraction?.getNavigationController()?.pushViewController(tagController) - } - return true - }) - self.interfaceInteraction?.presentController(controller, nil) - } self.isJoining = true if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState { let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true) @@ -263,6 +251,32 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { } } strongSelf.interfaceInteraction?.presentController(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationInterfaceState.strings.Common_OK, action: {})]), nil) + }, completed: { [weak self] in + guard let self else { + return + } + Queue.mainQueue().after(0.5) { + if let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer { + var canEditRank = false + if let channel = peer as? TelegramChannel, channel.hasPermission(.editRank) { + canEditRank = true + } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { + canEditRank = true + } + //TODO:localize + if canEditRank { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: "You joined the group", cancel: "Add Tag", destructive: false), elevatedLayout: true, action: { action in + if case .undo = action { + let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peer.id, participantId: context.account.peerId, rank: nil) + self.interfaceInteraction?.getNavigationController()?.pushViewController(tagController) + } + return true + }) + self.interfaceInteraction?.presentController(controller, nil) + } + } + } })) case .kicked: break diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index 6fd9d9caee..5a0cb73a5a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -777,6 +777,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { (.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks), (.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls), (.banAddMembers, self.presentationData.strings.Channel_AdminLog_AddMembers), + (.banEditRank, self.presentationData.strings.Channel_AdminLog_EditRankOwn), (.banPinMessages, self.presentationData.strings.Channel_AdminLog_PinMessages), (.banManageTopics, self.presentationData.strings.Channel_AdminLog_ManageTopics), (.banChangeInfo, self.presentationData.strings.Channel_AdminLog_ChangeInfo) @@ -1139,6 +1140,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { (.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks), (.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls), (.banAddMembers, self.presentationData.strings.Channel_AdminLog_AddMembers), + (.banEditRank, self.presentationData.strings.Channel_AdminLog_EditRank), (.banPinMessages, self.presentationData.strings.Channel_AdminLog_PinMessages), (.banManageTopics, self.presentationData.strings.Channel_AdminLog_ManageTopics), (.banChangeInfo, self.presentationData.strings.Channel_AdminLog_ChangeInfo) diff --git a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift index 0627e2f49f..2cefec7ea3 100644 --- a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift +++ b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift @@ -26,6 +26,7 @@ import UndoUI import RankChatPreviewItem private let rankFieldTag = GenericComponentViewTag() +private let rankMaxLength: Int32 = 16 private final class ChatParticipantRightsContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -115,16 +116,6 @@ private final class ChatParticipantRightsContent: CombinedComponent { self.disposable?.dispose() } - func promoteMember() { - let controller = channelAdminController(context: self.context, updatedPresentationData: nil, peerId: self.subject.peerId, adminId: self.memberId, initialParticipant: nil, updated: { _ in - }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) - self.controller?.push(controller) - } - - func removeMember() { - - } - func complete() { var rank = self.rank if rank?.isEmpty == true { @@ -160,8 +151,6 @@ private final class ChatParticipantRightsContent: CombinedComponent { let title = Child(MultilineTextComponent.self) let peerSection = Child(ListSectionComponent.self) let rankSection = Child(ListSectionComponent.self) - //let permissionsSection = Child(ListSectionComponent.self) - let actionsSection = Child(ListSectionComponent.self) let doneButton = Child(ButtonComponent.self) return { context in @@ -262,207 +251,113 @@ private final class ChatParticipantRightsContent: CombinedComponent { break } - if let peer = state.peer { - var rankPreviewPlaceholder = "" - var rankFooterString = "" - var rankRole: ChatRankInfoScreenRole = .member - switch component.subject { - case .admin: - rankFooterString = "Add short tag next to \(peer.compactDisplayTitle)'s name." - rankPreviewPlaceholder = "admin" - rankRole = .admin - case .member: - rankFooterString = "Add short tag next to \(peer.compactDisplayTitle)'s name." - rankPreviewPlaceholder = "0️⃣" - case .rank: - if peer.id == component.context.account.peerId { - rankFooterString = "Share your role, title, or how you're known in this group. Your tag is visible to all members." - } else { - rankFooterString = "Add short tag next to \(peer.compactDisplayTitle)'s name." - } - rankPreviewPlaceholder = "0️⃣" - } - - let rankValue = state.rank ?? "" - let messageItem = RankChatPreviewItem.MessageItem( - peer: peer, - text: "Reinhardt, we need to find you some new tunes.", - entities: nil, - media: [], - rank: rankValue.isEmpty ? rankPreviewPlaceholder : rankValue, - rankRole: rankRole - ) - - var rankSectionItems: [AnyComponentWithIdentity] = [] - rankSectionItems.append( - AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( - itemGenerator: RankChatPreviewItem( - context: component.context, - theme: environment.theme, - componentTheme: theme, - strings: strings, - sectionId: 0, - fontSize: presentationData.chatFontSize, - chatBubbleCorners: presentationData.chatBubbleCorners, - wallpaper: presentationData.chatWallpaper, - dateTimeFormat: environment.dateTimeFormat, - nameDisplayOrder: presentationData.nameDisplayOrder, - messageItems: [messageItem] - ), - params: listItemParams - ))) - ) - rankSectionItems.append( - AnyComponentWithIdentity(id: 1, component: AnyComponent(ListTextFieldItemComponent( - style: .glass, - theme: theme, - initialText: state.initialRank ?? "", - resetText: nil, - placeholder: "Add tag", - characterLimit: 16, - autocapitalizationType: .sentences, - autocorrectionType: .default, - returnKeyType: .done, - contentInsets: .zero, - updated: { [weak state] value in - guard let state else { - return - } - state.rank = value - state.updated(transition: .easeInOut(duration: 0.2)) - }, - onReturn: {}, - tag: rankFieldTag - ))) - ) - - let rankSection = rankSection.update( - component: ListSectionComponent( - theme: theme, - style: .glass, - header: nil, - footer: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: rankFooterString, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), - items: rankSectionItems - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: context.transition - ) - context.add(rankSection - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + rankSection.size.height / 2.0)) - ) - contentHeight += rankSection.size.height + let peer: EnginePeer + if let current = state.peer { + peer = current + } else { + peer = EnginePeer.user(TelegramUser(id: EnginePeer.Id(0), accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) } + var rankPreviewPlaceholder = "" + var rankFooterString = "" + var rankRole: ChatRankInfoScreenRole = .member switch component.subject { - case .member: - contentHeight += 24.0 - -// var permissionsSectionItems: [AnyComponentWithIdentity] = [] -// permissionsSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( -// theme: theme, -// style: .glass, -// title: AnyComponent(VStack([ -// AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( -// text: .plain(NSAttributedString( -// string: "Permissions", -// font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), -// textColor: theme.list.itemPrimaryTextColor -// )), -// maximumNumberOfLines: 1 -// ))), -// ], alignment: .left, spacing: 2.0)), -// accessory: .arrow, -// action: { _ in -// -// } -// )))) -// let permissionsSection = permissionsSection.update( -// component: ListSectionComponent( -// theme: theme, -// style: .glass, -// header: AnyComponent(MultilineTextComponent( -// text: .plain(NSAttributedString( -// string: "What can this member do".uppercased(), -// font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), -// textColor: environment.theme.list.freeTextColor -// )), -// maximumNumberOfLines: 0 -// )), -// footer: nil, -// items: permissionsSectionItems -// ), -// availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), -// transition: context.transition -// ) -// context.add(permissionsSection -// .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + permissionsSection.size.height / 2.0)) -// ) -// contentHeight += permissionsSection.size.height -// contentHeight += 24.0 - - var actionsSectionItems: [AnyComponentWithIdentity] = [] - actionsSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: "Promote Member", - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - )), - titleAlignment: .center, - accessory: .none, - action: { [weak state] _ in - state?.promoteMember() - } - )))) - actionsSectionItems.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: "Remove Member", - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemDestructiveColor - )), - maximumNumberOfLines: 1 - )), - titleAlignment: .center, - accessory: .none, - action: { [weak state] _ in - state?.removeMember() - } - )))) - let actionsSection = actionsSection.update( - component: ListSectionComponent( - theme: theme, - style: .glass, - header: nil, - footer: nil, - items: actionsSectionItems - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: context.transition - ) - context.add(actionsSection - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + actionsSection.size.height / 2.0)) - ) - contentHeight += actionsSection.size.height case .admin: - break - default: - break + rankFooterString = "Add short tag next to \(peer.compactDisplayTitle)'s name." + rankPreviewPlaceholder = "admin" + rankRole = .admin + case .member: + rankFooterString = "Add short tag next to \(peer.compactDisplayTitle)'s name." + rankPreviewPlaceholder = "0️⃣" + case .rank: + if peer.id == component.context.account.peerId { + rankFooterString = "Share your role, title, or how you're known in this group. Your tag is visible to all members." + } else { + rankFooterString = "Add short tag next to \(peer.compactDisplayTitle)'s name." + } + rankPreviewPlaceholder = "0️⃣" } + let rankValue = state.rank ?? "" + let messageItem = RankChatPreviewItem.MessageItem( + peer: peer, + text: "Reinhardt, we need to find you some new tunes.", + entities: nil, + media: [], + rank: rankValue.isEmpty ? rankPreviewPlaceholder : rankValue, + rankRole: rankRole + ) + + var rankSectionItems: [AnyComponentWithIdentity] = [] + rankSectionItems.append( + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( + itemGenerator: RankChatPreviewItem( + context: component.context, + theme: environment.theme, + componentTheme: theme, + strings: strings, + sectionId: 0, + fontSize: presentationData.chatFontSize, + chatBubbleCorners: presentationData.chatBubbleCorners, + wallpaper: presentationData.chatWallpaper, + dateTimeFormat: environment.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + messageItems: [messageItem] + ), + params: listItemParams + ))) + ) + rankSectionItems.append( + AnyComponentWithIdentity(id: 1, component: AnyComponent(ListTextFieldItemComponent( + style: .glass, + theme: theme, + initialText: state.initialRank ?? "", + resetText: nil, + placeholder: "Add tag", + characterLimit: Int(rankMaxLength), + autocapitalizationType: .sentences, + autocorrectionType: .default, + returnKeyType: .done, + contentInsets: .zero, + updated: { [weak state] value in + guard let state else { + return + } + state.rank = value + state.updated(transition: .easeInOut(duration: 0.2)) + }, + onReturn: { [weak state] in + guard let state else { + return + } + state.complete() + }, + tag: rankFieldTag + ))) + ) + + let rankSection = rankSection.update( + component: ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: rankFooterString, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + items: rankSectionItems + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: context.transition + ) + context.add(rankSection + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + rankSection.size.height / 2.0)) + ) + contentHeight += rankSection.size.height contentHeight += 24.0 @@ -500,7 +395,6 @@ private final class ChatParticipantRightsContent: CombinedComponent { return } state.complete() - component.cancel(true) } ), availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), @@ -510,7 +404,13 @@ private final class ChatParticipantRightsContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + doneButton.size.height / 2.0)) ) contentHeight += doneButton.size.height - contentHeight += buttonInsets.bottom + + if environment.inputHeight > 0.0 { + contentHeight += 15.0 + contentHeight += max(environment.inputHeight, environment.safeInsets.bottom) + } else { + contentHeight += buttonInsets.bottom + } return CGSize(width: context.availableSize.width, height: contentHeight) } @@ -565,7 +465,7 @@ private final class ChatParticipantRightsComponent: CombinedComponent { )), style: .glass, backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor), - followContentSizeChanges: true, + followContentSizeChanges: false, clipsContent: true, isScrollEnabled: false, animateOut: animateOut @@ -663,7 +563,7 @@ public class ChatParticipantRightsScreen: ViewControllerComponentContainer { super.viewDidAppear(animated) if let view = self.node.hostView.findTaggedView(tag: rankFieldTag) as? ListTextFieldItemComponent.View { - Queue.mainQueue().after(0.15) { + Queue.mainQueue().after(0.01) { view.activateInput() view.selectAll() } diff --git a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift index 9aef66836a..65e25bff1b 100644 --- a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift +++ b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift @@ -208,7 +208,7 @@ final class MinimizedHeaderNode: ASDisplayNode { self.iconView.contentMode = .scaleAspectFit self.iconView.clipsToBounds = true self.iconView.layer.cornerRadius = 2.5 - self.iconView.tintColor = self.theme.navigationBar.primaryTextColor + self.iconView.tintColor = theme.navigationBar.primaryTextColor super.init() @@ -242,7 +242,7 @@ final class MinimizedHeaderNode: ASDisplayNode { self.validLayout = (size, insets, isExpanded) if self.closeIcon == nil { - self.closeIcon = generateCloseButtonImage() + self.closeIcon = generateCloseButtonImage()?.withRenderingMode(.alwaysTemplate) } let headerHeight: CGFloat = 56.0 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index 92eea31088..8cccbdae48 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -31,6 +31,7 @@ enum PeerMembersListAction { case restrict case remove case openStories(sourceView: UIView) + case openContextMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) } private enum PeerMembersListEntryStableId: Hashable { @@ -94,6 +95,8 @@ private enum PeerMembersListEntry: Comparable, Identifiable { addMemberAction() }) case let .member(_, _, member): + var labelColor = presentationData.theme.list.itemSecondaryTextColor + var labelBackground = false let label: String? if let rank = member.rank { label = rank @@ -107,6 +110,17 @@ private enum PeerMembersListEntry: Comparable, Identifiable { label = nil } } + + switch member.role { + case .creator: + labelBackground = true + labelColor = UIColor(rgb: 0x956ac8) + case .admin: + labelBackground = true + labelColor = UIColor(rgb: 0x49a355) + default: + break + } let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: enclosingPeer, member: member) @@ -150,6 +164,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { return ContactsPeerItem( presentationData: ItemListPresentationData(presentationData), style: .plain, + systemStyle: .glass, sectionId: 0, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, @@ -157,7 +172,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { peerMode: .memberList, peer: .peer(peer: EnginePeer(member.peer), chatPeer: EnginePeer(member.peer)), status: status, - rightLabelText: label, + rightLabelText: label.flatMap { .init(text: $0, color: labelColor, hasBackground: labelBackground) }, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), @@ -400,51 +415,8 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { gesture?.cancel() return } - - let actions = availableActionsForMemberOfPeer(accountPeerId: self.context.account.peerId, peer: enclosingPeer, member: member) - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - var items: [ContextMenuItem] = [] - let action = self.action - - if actions.contains(.promote) && enclosingPeer is TelegramChannel { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.GroupInfo_ActionPromote, icon: { _ in - return nil - }, action: { c, _ in - c?.dismiss(completion: { - action(member, .promote) - }) - }))) - } - if actions.contains(.restrict) { - if enclosingPeer is TelegramChannel { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.GroupInfo_ActionRestrict, icon: { _ in - return nil - }, action: { c, _ in - c?.dismiss(completion: { - action(member, .restrict) - }) - }))) - } - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Delete, textColor: .destructive, icon: { _ in - return nil - }, action: { c, _ in - c?.dismiss(completion: { - action(member, .remove) - }) - }))) - } - - if items.isEmpty { - gesture?.cancel() - return - } - - let dismissPromise = ValuePromise(false) - let source = PeerInfoMemberExtractedContentSource(sourceNode: node, keepInPlace: false, blurBackground: true, centerVertically: false, shouldBeDismissed: dismissPromise.get()) - - let contextController = makeContextController(presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - self.parentController?.presentInGlobalOverlay(contextController) + + self.action(member, .openContextMenu(sourceNode: node, gesture: gesture)) }) self.enclosingPeer = enclosingPeer self.currentEntries = entries diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index ed5076a044..0b321e2d99 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -2237,6 +2237,7 @@ struct PeerInfoMemberActions: OptionSet { static let restrict = PeerInfoMemberActions(rawValue: 1 << 0) static let promote = PeerInfoMemberActions(rawValue: 1 << 1) static let logout = PeerInfoMemberActions(rawValue: 1 << 2) + static let editRank = PeerInfoMemberActions(rawValue: 1 << 3) } func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: PeerInfoMember) -> PeerInfoMemberActions { @@ -2244,13 +2245,24 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: if peer == nil { result.insert(.logout) - } else if member.id != accountPeerId { + } else if member.id == accountPeerId { + if let channel = peer as? TelegramChannel { + if channel.hasPermission(.editRank) { + result.insert(.editRank) + } + } else if let group = peer as? TelegramGroup { + if !group.hasBannedPermission(.banEditRank) { + result.insert(.editRank) + } + } + } else { if let channel = peer as? TelegramChannel { if channel.flags.contains(.isCreator) { if !channel.flags.contains(.isGigagroup) { result.insert(.restrict) } result.insert(.promote) + result.insert(.editRank) } else { switch member { case let .channelMember(channelMember, _): @@ -2266,6 +2278,9 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: if channel.hasPermission(.addAdmins) { result.insert(.promote) } + if channel.hasPermission(.editRank) { + result.insert(.editRank) + } } } else { if channel.hasPermission(.banMembers) && !channel.flags.contains(.isGigagroup) { @@ -2274,6 +2289,9 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: if channel.hasPermission(.addAdmins) { result.insert(.promote) } + if channel.hasPermission(.editRank) { + result.insert(.editRank) + } } } case .legacyGroupMember: @@ -2287,12 +2305,14 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: case .creator: result.insert(.restrict) result.insert(.promote) + result.insert(.editRank) case .admin: switch member { case let .legacyGroupMember(_, _, invitedBy, _, _, _): result.insert(.restrict) if invitedBy == accountPeerId { result.insert(.promote) + result.insert(.editRank) } case .channelMember: break @@ -2304,6 +2324,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: case let .legacyGroupMember(_, _, invitedBy, _, _, _): if invitedBy == accountPeerId { result.insert(.restrict) + result.insert(.editRank) } case .channelMember: break diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift index 4debfe6834..b4a13b640f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift @@ -836,7 +836,10 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD for member in memberList { let isAccountPeer = member.id == context.account.peerId items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: .account(context), enclosingPeer: peer, member: member, isAccount: false, action: isAccountPeer ? { _ in - interaction.performMemberAction(member, .editRank) + let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: peer, member: member) + if actions.contains(.editRank) { + interaction.performMemberAction(member, .editRank) + } } : { action in switch action { case .open: diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index f6c4c4c3b8..d02232d67f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -1448,6 +1448,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.performMemberAction(member: member, action: .remove) case let .openStories(sourceView): strongSelf.performMemberAction(member: member, action: .openStories(sourceView: sourceView)) + case let .openContextMenu(sourceNode, gesture): + strongSelf.openMemberContextMenu(member: member, node: sourceNode, gesture: gesture) } } @@ -4250,17 +4252,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func openPeerInfo(peer: Peer, isMember: Bool) { - if peer.id == self.context.account.peerId { - let controller = self.context.sharedContext.makeChatParticipantRightsScreen(context: self.context, peerId: self.peerId, participantId: peer.id, rank: "") - (self.controller?.navigationController as? NavigationController)?.pushViewController(controller) - } else { - var mode: PeerInfoControllerMode = .generic - if isMember { - mode = .group(self.peerId) - } - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { - (self.controller?.navigationController as? NavigationController)?.pushViewController(infoController) - } + var mode: PeerInfoControllerMode = .generic + if isMember { + mode = .group(self.peerId) + } + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + (self.controller?.navigationController as? NavigationController)?.pushViewController(infoController) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift index 651adfeb29..8a95ddec53 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift @@ -12,59 +12,81 @@ import ContextUI extension PeerInfoScreenNode { func openMemberContextMenu(member: PeerInfoMember, node: ASDisplayNode, gesture: ContextGesture?) { - guard let controller = self.controller else { + guard let controller = self.controller, let enclosingPeer = self.data?.peer else { return } var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: "Send Message", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self, let navigationController = self.controller?.navigationController as? NavigationController else { - return + let actions = availableActionsForMemberOfPeer(accountPeerId: self.context.account.peerId, peer: enclosingPeer, member: member) + + //TODO:localize + if member.id != self.context.account.peerId { + items.append(.action(ContextMenuActionItem(text: "Send Message", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self, let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(member.peer)))) } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(member.peer)))) - } - }))) + }))) + } - items.append(.action(ContextMenuActionItem(text: "Edit Member Tag", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tag"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return + if actions.contains(.editRank) { + items.append(.action(ContextMenuActionItem(text: "Edit Member Tag", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tag"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.performMemberAction(member: member, action: .editRank) } - self.performMemberAction(member: member, action: .editRank) - } - }))) + }))) + } - items.append(.action(ContextMenuActionItem(text: "Promote", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Promote"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return + if actions.contains(.promote) && enclosingPeer is TelegramChannel { + items.append(.action(ContextMenuActionItem(text: "Promote", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Promote"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.performMemberAction(member: member, action: .promote) } - self.performMemberAction(member: member, action: .promote) - } - }))) + }))) + } - items.append(.action(ContextMenuActionItem(text: "Restrict", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return + if actions.contains(.restrict) { + if enclosingPeer is TelegramChannel { + items.append(.action(ContextMenuActionItem(text: "Restrict", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.performMemberAction(member: member, action: .restrict) + } + }))) + } + + items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.performMemberAction(member: member, action: .remove) } - self.performMemberAction(member: member, action: .restrict) - } - }))) + }))) + } - items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - self.performMemberAction(member: member, action: .remove) - } - }))) + if items.isEmpty { + gesture?.cancel() + return + } - let actions = ContextController.Items(content: .list(items)) - - let contextController = makeContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceView: node.view)), items: .single(actions), gesture: gesture) + let source: ContextContentSource + if let node = node as? ContextExtractedContentContainingNode { + source = .extracted(PeerInfoMemberExtractedContentSource(sourceNode: node, keepInPlace: false, blurBackground: true, centerVertically: false, shouldBeDismissed: .single(false))) + } else { + source = .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceView: node.view)) + } + let contextController = makeContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), gesture: gesture) self.controller?.present(contextController, in: .window(.root)) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d21a92b0e8..549654b028 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2551,11 +2551,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return buffer.baseAddress!.assumingMemoryBound(to: UInt8.self).pointee } + //TODO:localize switch buttonType { case 0: - let _ = strongSelf.context.engine.peers.toggleMessageCopyProtection(peerId: message.id.peerId, enabled: true, requestMessageId: message.id).startStandalone() + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: "Disable Sharing", text: "Are you sure you want to keep sharing disabled?", actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: "Yes", action: { [weak self] in + guard let self else { + return + } + let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: message.id.peerId, enabled: true, requestMessageId: message.id).startStandalone() + }) + ], parseMarkdown: true), in: .window(.root)) case 1: - let _ = strongSelf.context.engine.peers.toggleMessageCopyProtection(peerId: message.id.peerId, enabled: false, requestMessageId: message.id).startStandalone() + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: "Enable Sharing", text: "Are you sure you want to enable sharing?", actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: "Yes", action: { [weak self] in + guard let self else { + return + } + let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: message.id.peerId, enabled: false, requestMessageId: message.id).startStandalone() + }) + ], parseMarkdown: true), in: .window(.root)) default: break } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 9d051c4b47..6132922684 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -4317,10 +4317,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { return PasskeysScreen(context: context, displaySkip: displaySkip, initialPasskeysData: nil, passkeysDataUpdated: { _ in }, completion: completion, cancel: dismiss) } - public func makeChatParticipantRightsScreen(context: AccountContext, peerId: EnginePeer.Id, participantId: EnginePeer.Id, rank: String?) -> ViewController { - return ChatParticipantRightsScreen(context: context, subject: .member(peerId: peerId, participantId: participantId, rank: rank)) - } - public func makeChatCustomRankSetupScreen(context: AccountContext, peerId: EnginePeer.Id, participantId: EnginePeer.Id, rank: String?) -> ViewController { return ChatParticipantRightsScreen(context: context, subject: .rank(peerId: peerId, participantId: participantId, rank: rank)) }