Various improvements

This commit is contained in:
Ilya Laktyushin 2026-02-23 18:45:47 +04:00
parent 1d80861e09
commit 4203d70414
20 changed files with 408 additions and 350 deletions

View file

@ -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";

View file

@ -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

View file

@ -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 }

View file

@ -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:

View file

@ -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 {

View file

@ -420,7 +420,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
return entries
}
public func channelBannedMemberController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = 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<PresentationData, NoError>)? = 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)

View file

@ -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?() {

View file

@ -501,7 +501,7 @@ private struct ChannelPermissionsControllerState: Equatable {
var expandedPermissions = Set<TelegramChatBannedRightsFlags>()
}
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
}

View file

@ -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<Signal<CameraScreenImpl.Result, NoError>>,
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,

View file

@ -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

View file

@ -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)

View file

@ -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<Empty>] = []
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<Empty>] = []
// 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<Empty>] = []
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<Empty>] = []
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()
}

View file

@ -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

View file

@ -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<Bool>(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

View file

@ -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

View file

@ -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:

View file

@ -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)
}
}

View file

@ -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))
}
}

View file

@ -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
}

View file

@ -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))
}