Group boosts

This commit is contained in:
Ilya Laktyushin 2024-02-09 15:17:45 +04:00
parent cd7c9ea73c
commit 971b4d6986
7 changed files with 186 additions and 57 deletions

View File

@ -24,8 +24,9 @@ private final class ChannelAdminsControllerArguments {
let addAdmin: () -> Void
let openAdmin: (ChannelParticipant) -> Void
let updateAntiSpamEnabled: (Bool) -> Void
let updateSignMessagesEnabled: (Bool) -> Void
init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeAdmin: @escaping (EnginePeer.Id) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void) {
init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeAdmin: @escaping (EnginePeer.Id) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void, updateSignMessagesEnabled: @escaping (Bool) -> Void) {
self.context = context
self.openRecentActions = openRecentActions
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
@ -33,12 +34,14 @@ private final class ChannelAdminsControllerArguments {
self.addAdmin = addAdmin
self.openAdmin = openAdmin
self.updateAntiSpamEnabled = updateAntiSpamEnabled
self.updateSignMessagesEnabled = updateSignMessagesEnabled
}
}
private enum ChannelAdminsSection: Int32 {
case administration
case admins
case signMessages
}
private enum ChannelAdminsEntryStableId: Hashable {
@ -56,31 +59,40 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
case addAdmin(PresentationTheme, String, Bool)
case adminsInfo(PresentationTheme, String)
case signMessages(PresentationTheme, String, Bool)
case signMessagesInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .recentActions, .antiSpam, .antiSpamInfo:
return ChannelAdminsSection.administration.rawValue
case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo:
return ChannelAdminsSection.admins.rawValue
case .recentActions, .antiSpam, .antiSpamInfo:
return ChannelAdminsSection.administration.rawValue
case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo:
return ChannelAdminsSection.admins.rawValue
case .signMessages, .signMessagesInfo:
return ChannelAdminsSection.signMessages.rawValue
}
}
var stableId: ChannelAdminsEntryStableId {
switch self {
case .recentActions:
return .index(0)
case .antiSpam:
return .index(1)
case .antiSpamInfo:
return .index(2)
case .adminsHeader:
return .index(3)
case .addAdmin:
return .index(4)
case .adminsInfo:
return .index(5)
case let .adminPeerItem(_, _, _, _, _, _, participant, _, _, _):
return .peer(participant.peer.id)
case .recentActions:
return .index(0)
case .antiSpam:
return .index(1)
case .antiSpamInfo:
return .index(2)
case .adminsHeader:
return .index(3)
case .addAdmin:
return .index(4)
case .adminsInfo:
return .index(5)
case let .adminPeerItem(_, _, _, _, _, _, participant, _, _, _):
return .peer(participant.peer.id)
case .signMessages:
return .index(6)
case .signMessagesInfo:
return .index(7)
}
}
@ -158,6 +170,18 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
} else {
return false
}
case let .signMessages(lhsTheme, lhsText, lhsValue):
if case let .signMessages(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .signMessagesInfo(lhsTheme, lhsText):
if case let .signMessagesInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
@ -203,7 +227,26 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
return true
}
case .adminsInfo:
return false
switch rhs {
case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin, .adminsInfo:
return false
default:
return true
}
case .signMessages:
switch rhs {
case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin, .adminsInfo, .signMessages:
return false
default:
return true
}
case .signMessagesInfo:
switch rhs {
case .recentActions, .antiSpam, .antiSpamInfo, .adminsHeader, .addAdmin, .adminsInfo, .signMessages, .signMessagesInfo:
return false
default:
return true
}
}
}
@ -259,6 +302,12 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
})
case let .adminsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .signMessages(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSignMessagesEnabled(value)
})
case let .signMessagesInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
}
}
@ -337,7 +386,7 @@ private struct ChannelAdminsControllerState: Equatable {
}
}
private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: EnginePeer.Id, peer: EnginePeer?, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamAvailable: Bool, antiSpamEnabled: Bool) -> [ChannelAdminsEntry] {
private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: EnginePeer.Id, peer: EnginePeer?, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamAvailable: Bool, antiSpamEnabled: Bool, signMessagesEnabled: Bool) -> [ChannelAdminsEntry] {
if participants == nil || participants?.count == nil {
return []
}
@ -429,6 +478,11 @@ private func channelAdminsControllerEntries(presentationData: PresentationData,
let info = isGroup ? presentationData.strings.Group_Management_AddModeratorHelp : presentationData.strings.Channel_Management_AddModeratorHelp
entries.append(.adminsInfo(presentationData.theme, info))
}
if !isGroup && peer.hasPermission(.sendSomething) {
entries.append(.signMessages(presentationData.theme, presentationData.strings.Channel_SignMessages, signMessagesEnabled))
entries.append(.signMessagesInfo(presentationData.theme, presentationData.strings.Channel_SignMessages_Help))
}
}
} else if case let .legacyGroup(peer) = peer {
let isGroup = true
@ -536,6 +590,9 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
let updateAntiSpamDisposable = MetaDisposable()
actionsDisposable.add(updateAntiSpamDisposable)
let updateSignMessagesDisposable = MetaDisposable()
actionsDisposable.add(updateSignMessagesDisposable)
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let antiSpamConfiguration = AntiSpamBotConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
@ -732,6 +789,12 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
|> deliverOnMainQueue).start(next: { peerId in
updateAntiSpamDisposable.set(context.engine.peers.toggleAntiSpamProtection(peerId: peerId, enabled: value).start())
})
}, updateSignMessagesEnabled: { value in
let _ = (currentPeerId.get()
|> take(1)
|> deliverOnMainQueue).start(next: { peerId in
updateSignMessagesDisposable.set(context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: peerId, enabled: value).start())
})
})
let membersAndLoadMoreControlValue = Atomic<(Disposable, PeerChannelMemberCategoryControl?)?>(value: nil)
@ -852,6 +915,11 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
antiSpamAvailable = true
}
var signMessagesEnabled = false
if case let .channel(channel) = view.peer, case let .broadcast(info) = channel.info {
signMessagesEnabled = info.flags.contains(.messagesShouldHaveSignatures)
}
var rightNavigationButton: ItemListNavigationButton?
var secondaryRightNavigationButton: ItemListNavigationButton?
if let admins = admins, admins.count > 1 {
@ -923,7 +991,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: view.peer, state: state, participants: admins, antiSpamAvailable: antiSpamAvailable, antiSpamEnabled: antiSpamEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: view.peer, state: state, participants: admins, antiSpamAvailable: antiSpamAvailable, antiSpamEnabled: antiSpamEnabled, signMessagesEnabled: signMessagesEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count)
return (controllerState, (listState, arguments))
} |> afterDisposed {

View File

@ -59,6 +59,7 @@ public enum PresentationResourceKey: Int32 {
case itemListVerifiedPeerIcon
case itemListCloudFetchIcon
case itemListCloseIconImage
case itemListRemoveIconImage
case itemListMakeVisibleIcon
case itemListMakeInvisibleIcon
case itemListEditThemeIcon

View File

@ -211,6 +211,24 @@ public struct PresentationResourcesItemList {
})
}
public static func itemListRemoveIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListRemoveIconImage.rawValue, { theme in
return generateImage(CGSize(width: 15.0, height: 15.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setStrokeColor(theme.list.disclosureArrowColor.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})
})
}
public static func makeVisibleIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListMakeVisibleIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/MakeVisibleIcon"), color: theme.list.itemAccentColor)

View File

@ -180,6 +180,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
self.addSubnode(self.notFoundNode)
self.addSubnode(self.activityIndicator)
self.addSubnode(self.removeButtonIcon)
self.addSubnode(self.removeButton)
var firstTime = true
@ -195,12 +196,29 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
}
firstTime = false
}
self.removeButton.addTarget(self, action: #selector(self.removeButtonPressed), forControlEvents: .touchUpInside)
self.removeButton.highligthedChanged = { [weak self] highlighted in
if let self {
if highlighted {
self.removeButtonIcon.layer.removeAnimation(forKey: "opacity")
self.removeButtonIcon.alpha = 0.4
} else {
self.removeButtonIcon.alpha = 1.0
self.removeButtonIcon.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
deinit {
self.fetchDisposable.dispose()
}
@objc private func removeButtonPressed() {
self.item?.remove?()
}
private func removePlaceholder(animated: Bool) {
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
@ -330,6 +348,14 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
strongSelf.activityIndicator.isHidden = true
}
if case .found = item.content {
strongSelf.removeButtonIcon.isHidden = false
strongSelf.removeButton.isHidden = false
} else {
strongSelf.removeButtonIcon.isHidden = true
strongSelf.removeButton.isHidden = true
}
let revealOffset = strongSelf.revealOffset
let transition: ContainedViewLayoutTransition = .immediate
@ -377,6 +403,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.removeButtonIcon.image = PresentationResourcesItemList.itemListRemoveIconImage(item.theme)
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
@ -403,17 +430,16 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.notFoundNode, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0 + floor((boundingSize.width - image.size.width) / 2.0), y: 13.0 + floor((boundingSize.height - image.size.height) / 2.0)), size: image.size))
}
if let updatedImageSignal = updatedImageSignal {
strongSelf.imageNode.setSignal(updatedImageSignal)
}
let imageFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame)
strongSelf.removeButton.frame = CGRect(origin: CGPoint(x: layoutSize.width - params.rightInset - layoutSize.height, y: 0.0), size: CGSize(width: layoutSize.height, height: layoutSize.height))
if let icon = strongSelf.removeButtonIcon.image {
strongSelf.removeButtonIcon.frame = CGRect(origin: CGPoint(x: layoutSize.width - params.rightInset - icon.size.width - 18.0, y: floor((layoutSize.height - icon.size.height) / 2.0)), size: icon.size)
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))

View File

@ -1588,18 +1588,16 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
case .broadcast:
let ItemUsername = 1
let ItemPeerColor = 2
let ItemInviteLinks = 4
let ItemDiscussionGroup = 5
let ItemSignMessages = 6
let ItemSignMessagesHelp = 7
let ItemDeleteChannel = 8
let ItemReactions = 9
let ItemAdmins = 10
let ItemMembers = 11
let ItemMemberRequests = 12
let ItemStats = 13
let ItemBanned = 14
let ItemRecentActions = 15
let ItemInviteLinks = 3
let ItemDiscussionGroup = 4
let ItemDeleteChannel = 5
let ItemReactions = 6
let ItemAdmins = 7
let ItemMembers = 8
let ItemMemberRequests = 9
let ItemStats = 10
let ItemBanned = 11
let ItemRecentActions = 12
let isCreator = channel.flags.contains(.isCreator)
@ -1708,19 +1706,19 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
}))
}
if isCreator || (channel.adminRights != nil && channel.hasPermission(.sendSomething)) {
let messagesShouldHaveSignatures: Bool
switch channel.info {
case let .broadcast(info):
messagesShouldHaveSignatures = info.flags.contains(.messagesShouldHaveSignatures)
default:
messagesShouldHaveSignatures = false
}
items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemSignMessages, text: presentationData.strings.Channel_SignMessages, value: messagesShouldHaveSignatures, icon: UIImage(bundleImageName: "Chat/Info/GroupSignIcon"), toggled: { value in
interaction.editingToggleMessageSignatures(value)
}))
items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemSignMessagesHelp, text: presentationData.strings.Channel_SignMessages_Help))
}
// if isCreator || (channel.adminRights != nil && channel.hasPermission(.sendSomething)) {
// let messagesShouldHaveSignatures: Bool
// switch channel.info {
// case let .broadcast(info):
// messagesShouldHaveSignatures = info.flags.contains(.messagesShouldHaveSignatures)
// default:
// messagesShouldHaveSignatures = false
// }
// items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemSignMessages, text: presentationData.strings.Channel_SignMessages, value: messagesShouldHaveSignatures, icon: UIImage(bundleImageName: "Chat/Info/GroupSignIcon"), toggled: { value in
// interaction.editingToggleMessageSignatures(value)
// }))
// items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemSignMessagesHelp, text: presentationData.strings.Channel_SignMessages_Help))
// }
var canEditMembers = false
if channel.hasPermission(.banMembers) && (channel.adminRights != nil || channel.flags.contains(.isCreator)) {

View File

@ -10800,8 +10800,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
items.append(.custom(ChatSendAsPeerTitleContextItem(text: strongSelf.presentationInterfaceState.strings.Conversation_SendMesageAs.uppercased()), false))
items.append(.custom(ChatSendAsPeerListContextItem(context: strongSelf.context, chatPeerId: peerId, peers: peers, selectedPeerId: myPeerId, isPremium: isPremium, presentToast: { [weak self] peer in
if let strongSelf = self {
let hapticFeedback = HapticFeedback()
hapticFeedback.impact()
HapticFeedback().impact()
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: strongSelf.presentationData.strings.EmojiInput_PremiumEmojiToast_Action, duration: 3), elevatedLayout: false, action: { [weak self] action in
guard let strongSelf = self else {
@ -11075,6 +11074,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let self, let peerId = self.chatLocation.peerId, let cachedData = self.peerView?.cachedData as? CachedChannelData, let boostToUnrestrict = cachedData.boostsToUnrestrict else {
return
}
HapticFeedback().impact()
let _ = combineLatest(queue: Queue.mainQueue(),
context.engine.peers.getChannelBoostStatus(peerId: peerId),
context.engine.peers.getMyBoostStatus()

View File

@ -4703,6 +4703,7 @@ private func generateClearImage(color: UIColor) -> UIImage? {
private final class BoostSlowModeButton: HighlightTrackingButtonNode {
let containerNode: ASDisplayNode
let backgroundNode: ASImageNode
let textNode: ImmediateAnimatedCountLabelNode
let iconNode: ASImageNode
@ -4712,6 +4713,8 @@ private final class BoostSlowModeButton: HighlightTrackingButtonNode {
var requestUpdate: () -> Void = {}
override init(pointerStyle: PointerStyle? = nil) {
self.containerNode = ASDisplayNode()
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.clipsToBounds = true
@ -4727,9 +4730,20 @@ private final class BoostSlowModeButton: HighlightTrackingButtonNode {
super.init(pointerStyle: pointerStyle)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.textNode)
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.iconNode)
self.containerNode.addSubnode(self.textNode)
self.highligthedChanged = { [weak self] highlighted in
if let self {
if highlighted {
self.containerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
} else if let presentationLayer = self.containerNode.layer.presentation() {
self.containerNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
}
}
}
}
func update(size: CGSize, interfaceState: ChatPresentationInterfaceState) -> CGSize {
@ -4778,6 +4792,8 @@ private final class BoostSlowModeButton: HighlightTrackingButtonNode {
let textSize = self.textNode.updateLayout(size: CGSize(width: 200.0, height: 100.0), animated: true)
let totalSize = CGSize(width: textSize.width > 0.0 ? textSize.width + 38.0 : 33.0, height: 33.0)
self.containerNode.bounds = CGRect(origin: .zero, size: totalSize)
self.containerNode.position = CGPoint(x: totalSize.width / 2.0, y: totalSize.height / 2.0)
self.backgroundNode.frame = CGRect(origin: .zero, size: totalSize)
self.backgroundNode.cornerRadius = totalSize.height / 2.0
self.textNode.frame = CGRect(origin: CGPoint(x: 9.0, y: floorToScreenPixels((totalSize.height - textSize.height) / 2.0)), size: textSize)