Merge commit '73d25e7be3'
This commit is contained in:
commit
1ae854e2e9
26 changed files with 432 additions and 128 deletions
|
|
@ -2067,6 +2067,7 @@
|
|||
"StickerPack.Share" = "Share";
|
||||
"StickerPack.Send" = "Send Sticker";
|
||||
"StickerPack.AddSticker" = "Add Sticker";
|
||||
"StickerPack.RemoveStickerSet" = "Remove Sticker Set";
|
||||
|
||||
"StickerPack.RemoveStickerCount_1" = "Remove 1 Sticker";
|
||||
"StickerPack.RemoveStickerCount_2" = "Remove 2 Stickers";
|
||||
|
|
@ -16214,5 +16215,5 @@ Error: %8$@";
|
|||
|
||||
"Chat.AdminAction.ToastReactionsDeletedTitleSingle" = "Reaction Deleted";
|
||||
"Chat.AdminAction.ToastReactionsDeletedTextSingle" = "Reaction Deleted.";
|
||||
"Chat.AdminAction.ToastReactionsDeletedTextMultiple" = "Messages Deleted.";
|
||||
"Chat.AdminAction.ToastReactionsDeletedTextMultiple" = "Reactions Deleted.";
|
||||
"Chat.AdminAction.ToastMessagesAndReactionsDeletedText" = "Messages and reactions deleted.";
|
||||
|
|
|
|||
|
|
@ -1117,6 +1117,7 @@ public protocol ChatController: ViewController {
|
|||
func activateSearch(domain: ChatSearchDomain, query: String)
|
||||
func activateInput(type: ChatControllerActivateInput)
|
||||
func beginClearHistory(type: InteractiveHistoryClearingType)
|
||||
func presentReactionDeletionOptions(author: Peer, messageId: MessageId)
|
||||
|
||||
func performScrollToTop() -> Bool
|
||||
func transferScrollingVelocity(_ velocity: CGFloat)
|
||||
|
|
|
|||
|
|
@ -2578,6 +2578,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
case poll
|
||||
case todo
|
||||
case game
|
||||
case voiceMessage
|
||||
}
|
||||
var messageTypeIcon: MessageTypeIcon?
|
||||
var ignoreForwardedIcon = false
|
||||
|
|
@ -2895,7 +2896,11 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
messageTypeIcon = .story
|
||||
} else {
|
||||
for media in message.media {
|
||||
if let _ = media as? TelegramMediaPoll {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if file.isVoice {
|
||||
messageTypeIcon = .voiceMessage
|
||||
}
|
||||
} else if let _ = media as? TelegramMediaPoll {
|
||||
messageTypeIcon = .poll
|
||||
} else if let _ = media as? TelegramMediaTodo {
|
||||
messageTypeIcon = .todo
|
||||
|
|
@ -3106,6 +3111,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
case .game:
|
||||
currentMessageTypeIcon = PresentationResourcesChatList.gameIcon(item.presentationData.theme)
|
||||
currentMessageTypeIconOffset.y = -1.0
|
||||
case .voiceMessage:
|
||||
currentMessageTypeIcon = PresentationResourcesChatList.voiceMessageIcon(item.presentationData.theme)
|
||||
currentMessageTypeIconOffset.y = -1.0
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -3115,7 +3123,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
if !contentImageSpecs.isEmpty {
|
||||
textLeftCutout += forwardedIconSpacing
|
||||
} else {
|
||||
textLeftCutout += contentImageTrailingSpace
|
||||
textLeftCutout += contentImageTrailingSpace - 1.0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1591,3 +1591,22 @@ public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bo
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func canSendReactionsToChat(_ state: ChatPresentationInterfaceState) -> Bool {
|
||||
if let peer = state.renderedPeer?.peer {
|
||||
let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: state)
|
||||
return canSendReactionsToPeer(EnginePeer(peer), ignoreDefault: canBypassRestrictions)
|
||||
} else if case .customChatContents = state.chatLocation {
|
||||
if case let .customChatContents(contents) = state.subject {
|
||||
if case .hashTagSearch = contents.kind {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,82 @@ func _internal_deleteAllReactionsWithAuthor(account: Account, peerId: PeerId, au
|
|||
|
||||
func _internal_deleteReaction(account: Account, messageId: MessageId, authorId: PeerId) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
return (transaction.getPeer(messageId.peerId), transaction.getPeer(authorId))
|
||||
let peer = transaction.getPeer(messageId.peerId)
|
||||
let author = transaction.getPeer(authorId)
|
||||
|
||||
if peer.flatMap(apiInputPeer) != nil && author.flatMap(apiInputPeer) != nil {
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var attributes = currentMessage.attributes
|
||||
var updated = false
|
||||
|
||||
for i in 0 ..< attributes.count {
|
||||
guard let attribute = attributes[i] as? ReactionsMessageAttribute else {
|
||||
continue
|
||||
}
|
||||
|
||||
let removedRecentPeers = attribute.recentPeers.filter { $0.peerId == authorId }
|
||||
var updatedTopPeers = attribute.topPeers
|
||||
var removedStarsTopPeerCount: Int32 = 0
|
||||
for j in (0 ..< updatedTopPeers.count).reversed() {
|
||||
if updatedTopPeers[j].peerId == authorId {
|
||||
removedStarsTopPeerCount += updatedTopPeers[j].count
|
||||
updatedTopPeers.remove(at: j)
|
||||
}
|
||||
}
|
||||
|
||||
if removedRecentPeers.isEmpty && removedStarsTopPeerCount == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var updatedReactions = attribute.reactions
|
||||
for removedRecentPeer in removedRecentPeers {
|
||||
if let index = updatedReactions.firstIndex(where: { $0.value == removedRecentPeer.value }) {
|
||||
if removedRecentPeer.value != .stars || removedStarsTopPeerCount == 0 {
|
||||
updatedReactions[index].count -= 1
|
||||
}
|
||||
if removedRecentPeer.isMy {
|
||||
updatedReactions[index].chosenOrder = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if removedStarsTopPeerCount != 0, let index = updatedReactions.firstIndex(where: { $0.value == .stars }) {
|
||||
updatedReactions[index].count -= removedStarsTopPeerCount
|
||||
}
|
||||
for j in (0 ..< updatedReactions.count).reversed() {
|
||||
if updatedReactions[j].count <= 0 {
|
||||
updatedReactions.remove(at: j)
|
||||
}
|
||||
}
|
||||
|
||||
let updatedRecentPeers = attribute.recentPeers.filter { $0.peerId != authorId }
|
||||
let updatedAttribute = ReactionsMessageAttribute(canViewList: attribute.canViewList, isTags: attribute.isTags, reactions: updatedReactions, recentPeers: updatedRecentPeers, topPeers: updatedTopPeers)
|
||||
if updatedAttribute != attribute {
|
||||
attributes[i] = updatedAttribute
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
if !updated {
|
||||
return .skip
|
||||
}
|
||||
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||
}
|
||||
|
||||
var tags = currentMessage.tags
|
||||
if attributes.contains(where: { ($0 as? ReactionsMessageAttribute)?.hasUnseen == true }) {
|
||||
tags.insert(.unseenReaction)
|
||||
} else {
|
||||
tags.remove(.unseenReaction)
|
||||
}
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
|
||||
return (peer, author)
|
||||
}
|
||||
|> mapToSignal { peer, author in
|
||||
guard let inputPeer = peer.flatMap(apiInputPeer), let inputAuthor = author.flatMap(apiInputPeer) else {
|
||||
|
|
|
|||
|
|
@ -19,3 +19,24 @@ public func canSendMessagesToPeer(_ peer: EnginePeer, ignoreDefault: Bool = fals
|
|||
return channel.hasPermission(.sendSomething, ignoreDefault: ignoreDefault)
|
||||
}
|
||||
}
|
||||
|
||||
public func canSendReactionsToPeer(_ peer: EnginePeer, ignoreDefault: Bool = false) -> Bool {
|
||||
switch peer {
|
||||
case .user:
|
||||
return !peer.isDeleted
|
||||
case let .legacyGroup(group):
|
||||
switch group.role {
|
||||
case .creator, .admin:
|
||||
return !peer.isDeleted
|
||||
case .member:
|
||||
if let defaultBannedRights = group.defaultBannedRights, defaultBannedRights.flags.contains(.banSendReactions) && !ignoreDefault {
|
||||
return false
|
||||
}
|
||||
return !peer.isDeleted
|
||||
}
|
||||
case let .secretChat(secretChat):
|
||||
return secretChat.embeddedState == .active
|
||||
case let .channel(channel):
|
||||
return channel.hasBannedPermission(.banSendReactions, ignoreDefault: ignoreDefault) == nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ public enum PresentationResourceKey: Int32 {
|
|||
case chatListCallOutgoingIcon
|
||||
case chatListCallVideoIncomingIcon
|
||||
case chatListCallVideoOutgoingIcon
|
||||
case chatListVoiceMessageIcon
|
||||
|
||||
case chatListGeneralTopicIcon
|
||||
case chatListGeneralTopicTemplateIcon
|
||||
|
|
|
|||
|
|
@ -349,6 +349,12 @@ public struct PresentationResourcesChatList {
|
|||
})
|
||||
}
|
||||
|
||||
public static func voiceMessageIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatListVoiceMessageIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/VoiceMessageIcon"), color: theme.chatList.muteIconColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
|
||||
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {
|
||||
|
|
|
|||
|
|
@ -2367,6 +2367,8 @@ private let deletedMessagesDisplayedLimit = 4
|
|||
func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentationData: ChatPresentationData, expandedDeletedMessages: Set<EngineMessage.Id>, currentDeletedHeaderMessages: inout Set<EngineMessage.Id>) -> [ChatRecentActionsEntry] {
|
||||
var result: [ChatRecentActionsEntry] = []
|
||||
var deleteMessageEntries: [ChannelAdminEventLogEntry] = []
|
||||
let previousDeletedHeaderMessages = currentDeletedHeaderMessages
|
||||
currentDeletedHeaderMessages = Set()
|
||||
|
||||
func appendCurrentDeleteEntries() {
|
||||
if !deleteMessageEntries.isEmpty, let lastEntry = deleteMessageEntries.last, let lastMessageId = lastEntry.event.action.messageId {
|
||||
|
|
@ -2394,9 +2396,16 @@ func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentation
|
|||
var skipAppendingGeneralEntry = false
|
||||
if case let .deleteMessage(message) = entry.event.action {
|
||||
var skipAppendingDeletionEntry = false
|
||||
if currentDeleteMessageEvent == nil || (currentDeleteMessageEvent!.peerId == entry.event.peerId && abs(currentDeleteMessageEvent!.date - entry.event.date) < 5 && !currentDeletedHeaderMessages.contains(message.id)) {
|
||||
let belongsToCurrentDeleteGroup: Bool
|
||||
if let currentDeleteMessageEvent {
|
||||
belongsToCurrentDeleteGroup = currentDeleteMessageEvent.peerId == entry.event.peerId && abs(currentDeleteMessageEvent.date - entry.event.date) < 5
|
||||
} else {
|
||||
if currentDeletedHeaderMessages.contains(message.id) {
|
||||
belongsToCurrentDeleteGroup = true
|
||||
}
|
||||
let wasPreviousHeaderMessage = previousDeletedHeaderMessages.contains(message.id)
|
||||
if currentDeleteMessageEvent == nil || (belongsToCurrentDeleteGroup && !wasPreviousHeaderMessage) {
|
||||
} else {
|
||||
if belongsToCurrentDeleteGroup && wasPreviousHeaderMessage {
|
||||
deleteMessageEntries.append(entry)
|
||||
skipAppendingDeletionEntry = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public enum AllowedReactions {
|
|||
case all
|
||||
}
|
||||
|
||||
public func peerMessageAllowedReactions(context: AccountContext, message: Message) -> Signal<(allowedReactions: AllowedReactions?, areStarsEnabled: Bool), NoError> {
|
||||
public func peerMessageAllowedReactions(context: AccountContext, message: Message, ignoreDefault: Bool = false) -> Signal<(allowedReactions: AllowedReactions?, areStarsEnabled: Bool), NoError> {
|
||||
if message.id.peerId == context.account.peerId {
|
||||
return .single((.all, false))
|
||||
}
|
||||
|
|
@ -41,6 +41,10 @@ public func peerMessageAllowedReactions(context: AccountContext, message: Messag
|
|||
areStarsEnabled = value
|
||||
}
|
||||
|
||||
if let peer, !canSendReactionsToPeer(peer, ignoreDefault: ignoreDefault) {
|
||||
return (nil, areStarsEnabled)
|
||||
}
|
||||
|
||||
if let effectiveReactions = message.effectiveReactions(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)), effectiveReactions.count >= maxReactionCount {
|
||||
return (.set(Set(effectiveReactions.map(\.value))), areStarsEnabled)
|
||||
}
|
||||
|
|
@ -256,7 +260,7 @@ public func tagMessageReactions(context: AccountContext, subPeerId: EnginePeer.I
|
|||
}
|
||||
}
|
||||
|
||||
public func topMessageReactions(context: AccountContext, message: Message, subPeerId: EnginePeer.Id?) -> Signal<[ReactionItem], NoError> {
|
||||
public func topMessageReactions(context: AccountContext, message: Message, subPeerId: EnginePeer.Id?, ignoreDefault: Bool = false) -> Signal<[ReactionItem], NoError> {
|
||||
if message.id.peerId == context.account.peerId {
|
||||
var loadTags = false
|
||||
if let effectiveReactionsAttribute = message.effectiveReactionsAttribute(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)) {
|
||||
|
|
@ -286,7 +290,7 @@ public func topMessageReactions(context: AccountContext, message: Message, subPe
|
|||
}
|
||||
}
|
||||
|
||||
let allowedReactionsWithFiles: Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> = peerMessageAllowedReactions(context: context, message: message)
|
||||
let allowedReactionsWithFiles: Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> = peerMessageAllowedReactions(context: context, message: message, ignoreDefault: ignoreDefault)
|
||||
|> mapToSignal { allowedReactions, areStarsEnabled -> Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> in
|
||||
guard let allowedReactions = allowedReactions else {
|
||||
return .single(nil)
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
|
|||
return self.contentNode.itemAt(point: CGPoint(x: point.x, y: point.y - searchBarHeight))
|
||||
}
|
||||
|
||||
private func openSelectedPackMoreMenu() {
|
||||
private func openMore() {
|
||||
guard let selectedStickerPack = self.selectedStickerPack, let controlsView = self.navigationButtons.view as? GlassControlPanelComponent.View, let rightItemView = controlsView.rightItemView, let sourceView = rightItemView.itemView(id: AnyHashable("more")) else {
|
||||
return
|
||||
}
|
||||
|
|
@ -272,6 +272,59 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
|
|||
}), nil)
|
||||
})))
|
||||
|
||||
if selectedStickerPack.installed {
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: strings.StickerPack_RemoveStickerSet, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let info = selectedStickerPack.info
|
||||
let context = self.context
|
||||
let _ = (context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] indexAndItems in
|
||||
guard let self, let (positionInList, items) = indexAndItems else {
|
||||
return
|
||||
}
|
||||
|
||||
let stickerItems = items.compactMap { $0 as? StickerPackItem }
|
||||
if let contentNode = self.contentNode as? StickerPaneSearchContentNode {
|
||||
contentNode.setPackInstalledState(id: info.id, installed: false)
|
||||
}
|
||||
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = self.interaction.getNavigationController() {
|
||||
for controller in navigationController.overlayControllers {
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitActionAndReplacementAnimation()
|
||||
animateInAsReplacement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme)
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: stickerItems.first, context: context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { [weak self] action in
|
||||
if case .undo = action {
|
||||
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: stickerItems, positionInList: positionInList).start()
|
||||
if let contentNode = self?.contentNode as? StickerPaneSearchContentNode {
|
||||
contentNode.setPackInstalledState(id: info.id, installed: true)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if let navigationController = self.interaction.getNavigationController() {
|
||||
navigationController.presentOverlay(controller: undoController)
|
||||
} else {
|
||||
self.interaction.presentController(undoController, nil)
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
let contextController = makeContextController(
|
||||
presentationData: presentationData,
|
||||
source: .reference(StickerPaneSearchHeaderContextReferenceContentSource(sourceView: sourceView)),
|
||||
|
|
@ -327,7 +380,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
|
|||
id: AnyHashable("more"),
|
||||
content: .animation("anim_morewide"),
|
||||
action: { [weak self] in
|
||||
self?.openSelectedPackMoreMenu()
|
||||
self?.openMore()
|
||||
}
|
||||
)
|
||||
],
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ private enum StickerSearchEntry: Identifiable, Comparable {
|
|||
|
||||
struct StickerPaneSearchSelectedPack {
|
||||
let info: StickerPackCollectionInfo
|
||||
let installed: Bool
|
||||
}
|
||||
|
||||
private struct StickerPaneSearchPack: Equatable {
|
||||
|
|
@ -497,19 +498,8 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = strongSelf.interaction.getNavigationController() {
|
||||
for controller in navigationController.overlayControllers {
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitActionAndReplacementAnimation()
|
||||
animateInAsReplacement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: theme)
|
||||
strongSelf.interaction.getNavigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: strongSelf.context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in
|
||||
strongSelf.interaction.getNavigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: strongSelf.context), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}))
|
||||
}))
|
||||
|
|
@ -688,7 +678,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
strongSelf.currentSearchEntries = entries
|
||||
strongSelf.currentPacks = packItems
|
||||
if let selectedPack = strongSelf.selectedPack, let updatedPack = packItems.first(where: { $0.info.id == selectedPack.info.id }), selectedPack.installed != updatedPack.installed {
|
||||
strongSelf.selectedPack = StickerPaneSearchPack(info: selectedPack.info, topItems: selectedPack.topItems, installed: updatedPack.installed)
|
||||
strongSelf.setPackInstalledState(id: selectedPack.info.id, installed: updatedPack.installed, transition: .immediate)
|
||||
}
|
||||
strongSelf.currentSearchIsFinal = final
|
||||
strongSelf.searchIsActive = true
|
||||
|
|
@ -796,20 +786,41 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
return !selectedPack.installed && !self.installedPackIds.contains(selectedPack.info.id)
|
||||
}
|
||||
|
||||
private func markPackInstalled(_ id: ItemCollectionId) {
|
||||
self.installedPackIds.insert(id)
|
||||
|
||||
if let selectedPack = self.selectedPack, selectedPack.info.id == id, !selectedPack.installed {
|
||||
self.selectedPack = StickerPaneSearchPack(info: selectedPack.info, topItems: selectedPack.topItems, installed: true)
|
||||
func setPackInstalledState(id: ItemCollectionId, installed: Bool, transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)) {
|
||||
if installed {
|
||||
self.installedPackIds.insert(id)
|
||||
} else {
|
||||
self.installedPackIds.remove(id)
|
||||
}
|
||||
|
||||
var updatedSelectedPack: StickerPaneSearchPack?
|
||||
if let selectedPack = self.selectedPack, selectedPack.info.id == id {
|
||||
if selectedPack.installed != installed {
|
||||
let pack = StickerPaneSearchPack(info: selectedPack.info, topItems: selectedPack.topItems, installed: installed)
|
||||
self.selectedPack = pack
|
||||
updatedSelectedPack = pack
|
||||
} else {
|
||||
updatedSelectedPack = selectedPack
|
||||
}
|
||||
}
|
||||
|
||||
var updatedPacks = false
|
||||
self.currentPacks = self.currentPacks.map { pack in
|
||||
if pack.info.id == id && !pack.installed {
|
||||
return StickerPaneSearchPack(info: pack.info, topItems: pack.topItems, installed: true)
|
||||
if pack.info.id == id && pack.installed != installed {
|
||||
updatedPacks = true
|
||||
return StickerPaneSearchPack(info: pack.info, topItems: pack.topItems, installed: installed)
|
||||
} else {
|
||||
return pack
|
||||
}
|
||||
}
|
||||
|
||||
if let updatedSelectedPack {
|
||||
self.selectedPackUpdated?(StickerPaneSearchSelectedPack(info: updatedSelectedPack.info, installed: updatedSelectedPack.installed))
|
||||
}
|
||||
|
||||
if updatedSelectedPack != nil || updatedPacks {
|
||||
self.requestLayout(transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePackPanelExpansionFromScroll() {
|
||||
|
|
@ -865,7 +876,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
self.notFoundNode.isHidden = true
|
||||
self.gridNode.isHidden = false
|
||||
self.trendingPane.isHidden = true
|
||||
self.selectedPackUpdated?(StickerPaneSearchSelectedPack(info: pack.info))
|
||||
self.selectedPackUpdated?(StickerPaneSearchSelectedPack(info: pack.info, installed: pack.installed))
|
||||
|
||||
self.enqueueEntries(self.entries(packItems: pack.topItems), interaction: interaction, crossfade: true)
|
||||
self.isPackPanelExpanded = true
|
||||
|
|
@ -896,8 +907,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
let packId = selectedPack.info.id
|
||||
let accessHash = selectedPack.info.accessHash
|
||||
|
||||
self.markPackInstalled(packId)
|
||||
self.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
self.setPackInstalledState(id: packId, installed: true)
|
||||
|
||||
let installSignal = (context.engine.stickers.loadedStickerPack(reference: .id(id: packId.id, accessHash: accessHash), forceActualized: false)
|
||||
|> mapToSignal { result -> Signal<(StickerPackCollectionInfo, [StickerPackItem]), NoError> in
|
||||
|
|
@ -931,19 +941,8 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = self.interaction.getNavigationController() {
|
||||
for controller in navigationController.overlayControllers {
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitActionAndReplacementAnimation()
|
||||
animateInAsReplacement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme)
|
||||
self.interaction.getNavigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: self.context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in
|
||||
self.interaction.getNavigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: self.context), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}))
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ final class PeerInfoInteraction {
|
|||
let openAddContact: () -> Void
|
||||
let updateBlocked: (Bool) -> Void
|
||||
let openReport: (PeerInfoReportType) -> Void
|
||||
let openDeleteReaction: (MessageId) -> Void
|
||||
let openShareBot: () -> Void
|
||||
let openAddBotToGroup: () -> Void
|
||||
let performBotCommand: (PeerInfoBotCommand) -> Void
|
||||
|
|
@ -101,6 +102,7 @@ final class PeerInfoInteraction {
|
|||
openAddContact: @escaping () -> Void,
|
||||
updateBlocked: @escaping (Bool) -> Void,
|
||||
openReport: @escaping (PeerInfoReportType) -> Void,
|
||||
openDeleteReaction: @escaping (MessageId) -> Void,
|
||||
openShareBot: @escaping () -> Void,
|
||||
openAddBotToGroup: @escaping () -> Void,
|
||||
performBotCommand: @escaping (PeerInfoBotCommand) -> Void,
|
||||
|
|
@ -178,6 +180,7 @@ final class PeerInfoInteraction {
|
|||
self.openAddContact = openAddContact
|
||||
self.updateBlocked = updateBlocked
|
||||
self.openReport = openReport
|
||||
self.openDeleteReaction = openDeleteReaction
|
||||
self.openShareBot = openShareBot
|
||||
self.openAddBotToGroup = openAddBotToGroup
|
||||
self.performBotCommand = performBotCommand
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ func infoItems(
|
|||
presentationData: PresentationData,
|
||||
interaction: PeerInfoInteraction,
|
||||
reactionSourceMessageId: MessageId?,
|
||||
canDeleteReaction: Bool,
|
||||
callMessages: [Message],
|
||||
chatLocation: ChatLocation,
|
||||
isOpenedFromChat: Bool,
|
||||
|
|
@ -91,9 +92,9 @@ func infoItems(
|
|||
let ItemAffiliateInfo = 4001
|
||||
let ItemBusinessHours = 5000
|
||||
let ItemLocation = 5001
|
||||
let ItemSendMessage = 6000
|
||||
let ItemReport = 6001
|
||||
let ItemAddToContacts = 6002
|
||||
let ItemAddToContacts = 6000
|
||||
let ItemDeleteReaction = 6001
|
||||
let ItemReport = 6002
|
||||
let ItemBlock = 6003
|
||||
let ItemEncryptionKey = 6004
|
||||
let ItemBalanceHeader = 7000
|
||||
|
|
@ -369,23 +370,23 @@ func infoItems(
|
|||
}
|
||||
|
||||
if !isMyProfile {
|
||||
if let reactionSourceMessageId = reactionSourceMessageId, !data.isContact {
|
||||
items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemSendMessage, text: presentationData.strings.UserInfo_SendMessage, action: {
|
||||
interaction.openChat(nil)
|
||||
if !data.isContact, user.botInfo == nil {
|
||||
items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemAddToContacts, text: presentationData.strings.PeerInfo_AddToContacts, action: {
|
||||
interaction.openAddContact()
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
if let reactionSourceMessageId = reactionSourceMessageId {
|
||||
if canDeleteReaction {
|
||||
//TODO:localize
|
||||
items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemDeleteReaction, text: "Delete Reaction", color: .destructive, action: {
|
||||
interaction.openDeleteReaction(reactionSourceMessageId)
|
||||
}))
|
||||
}
|
||||
items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemReport, text: presentationData.strings.ReportPeer_BanAndReport, color: .destructive, action: {
|
||||
interaction.openReport(.reaction(reactionSourceMessageId))
|
||||
}))
|
||||
} else {
|
||||
if !data.isContact {
|
||||
if user.botInfo == nil {
|
||||
items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemAddToContacts, text: presentationData.strings.PeerInfo_AddToContacts, action: {
|
||||
interaction.openAddContact()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
var isBlocked = false
|
||||
if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked {
|
||||
isBlocked = true
|
||||
|
|
|
|||
|
|
@ -283,6 +283,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
var forceIsContactPromise = ValuePromise<Bool>(false)
|
||||
let reactionSourceMessageId: MessageId?
|
||||
let sourceMessageId: MessageId?
|
||||
var canDeleteReaction = false
|
||||
var dataDisposable: Disposable?
|
||||
|
||||
let activeActionDisposable = MetaDisposable()
|
||||
|
|
@ -460,6 +461,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
openReport: { [weak self] type in
|
||||
self?.openReport(type: type, contextController: nil, backAction: nil)
|
||||
},
|
||||
openDeleteReaction: { [weak self] messageId in
|
||||
self?.openDeleteReaction(messageId: messageId)
|
||||
},
|
||||
openShareBot: { [weak self] in
|
||||
self?.openShareBot()
|
||||
},
|
||||
|
|
@ -2485,14 +2489,37 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
self?.updateNavigation(transition: .immediate, additive: true, animateHeader: true)
|
||||
}
|
||||
|
||||
let reactionSourceMessage: Signal<EngineMessage?, NoError>
|
||||
if let reactionSourceMessageId = self.reactionSourceMessageId {
|
||||
reactionSourceMessage = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: reactionSourceMessageId))
|
||||
} else {
|
||||
reactionSourceMessage = .single(nil)
|
||||
}
|
||||
|
||||
self.dataDisposable = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
screenData,
|
||||
self.forceIsContactPromise.get()
|
||||
).startStrict(next: { [weak self] data, forceIsContact in
|
||||
self.forceIsContactPromise.get(),
|
||||
reactionSourceMessage
|
||||
).startStrict(next: { [weak self] data, forceIsContact, reactionSourceMessage in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var canDeleteReaction = false
|
||||
if let sourceMessage = reactionSourceMessage?._asMessage(), let reactionPeerId = data.peer?.id, let channel = sourceMessage.peers[sourceMessage.id.peerId] as? TelegramChannel, channel.hasPermission(.deleteAllMessages) {
|
||||
for attribute in sourceMessage.attributes {
|
||||
guard let attribute = attribute as? ReactionsMessageAttribute else {
|
||||
continue
|
||||
}
|
||||
if attribute.recentPeers.contains(where: { $0.peerId == reactionPeerId }) || attribute.topPeers.contains(where: { $0.peerId == reactionPeerId }) {
|
||||
canDeleteReaction = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.canDeleteReaction = canDeleteReaction
|
||||
|
||||
if data.isContact && forceIsContact {
|
||||
strongSelf.forceIsContactPromise.set(false)
|
||||
} else {
|
||||
|
|
@ -5414,7 +5441,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
insets.left += sectionInset
|
||||
insets.right += sectionInset
|
||||
|
||||
let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, chatLocation: self.chatLocation, isOpenedFromChat: self.isOpenedFromChat, isMyProfile: self.isMyProfile)
|
||||
let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, reactionSourceMessageId: self.reactionSourceMessageId, canDeleteReaction: self.canDeleteReaction, callMessages: self.callMessages, chatLocation: self.chatLocation, isOpenedFromChat: self.isOpenedFromChat, isMyProfile: self.isMyProfile)
|
||||
|
||||
contentHeight += headerHeight
|
||||
if !((self.isSettings || self.isMyProfile) && self.state.isEditing) {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,50 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
}
|
||||
|
||||
func openDeleteReaction(messageId: MessageId) {
|
||||
guard let authorPeer = self.data?.peer?._asPeer(), let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId),
|
||||
TelegramEngine.EngineData.Item.Messages.Message(id: messageId)
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] sourcePeer, sourceMessage in
|
||||
guard let self, let sourcePeer, let sourceMessage = sourceMessage?._asMessage() else {
|
||||
return
|
||||
}
|
||||
guard let channel = sourceMessage.peers[sourceMessage.id.peerId] as? TelegramChannel, channel.hasPermission(.deleteAllMessages) else {
|
||||
return
|
||||
}
|
||||
|
||||
var hasReaction = false
|
||||
for attribute in sourceMessage.attributes {
|
||||
guard let attribute = attribute as? ReactionsMessageAttribute else {
|
||||
continue
|
||||
}
|
||||
if attribute.recentPeers.contains(where: { $0.peerId == authorPeer.id }) || attribute.topPeers.contains(where: { $0.peerId == authorPeer.id }) {
|
||||
hasReaction = true
|
||||
break
|
||||
}
|
||||
}
|
||||
guard hasReaction else {
|
||||
return
|
||||
}
|
||||
|
||||
let chatLocation: NavigateToChatControllerParams.Location
|
||||
if case let .channel(channel) = sourcePeer, channel.isForumOrMonoForum, let threadId = sourceMessage.threadId {
|
||||
chatLocation = .replyThread(ChatReplyThreadMessage(peerId: sourcePeer.id, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, isMonoforumPost: channel.isMonoForum, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
||||
} else {
|
||||
chatLocation = .peer(sourcePeer)
|
||||
}
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), keepStack: .always, useExisting: true, completion: { chatController in
|
||||
chatController.presentReactionDeletionOptions(author: authorPeer, messageId: messageId)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
func openChatWithClearedHistory(type: InteractiveHistoryClearingType) {
|
||||
guard let peer = self.data?.chatPeer, let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -496,19 +496,18 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte
|
|||
}
|
||||
}
|
||||
|
||||
if "".isEmpty {
|
||||
//TODO:localize
|
||||
let automationBotTitle: String
|
||||
if let botPeer = data.businessConnectedBot {
|
||||
automationBotTitle = "@\(botPeer.compactDisplayTitle)"
|
||||
} else {
|
||||
automationBotTitle = "Off"
|
||||
}
|
||||
items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerChatAutomation, label: .text(automationBotTitle), text: "Chat Automation", icon: PresentationResourcesSettings.aiTools, action: {
|
||||
interaction.editingOpenBusinessChatBots()
|
||||
}))
|
||||
items[.info]!.append(PeerInfoScreenCommentItem(id: ItemPeerChatAutomationHelp, text: "Add a bot to reply to messages on your behalf."))
|
||||
//TODO:localize
|
||||
let automationBotTitle: String
|
||||
if let botPeer = data.businessConnectedBot {
|
||||
let _ = botPeer
|
||||
automationBotTitle = "@\(botPeer.compactDisplayTitle)"
|
||||
} else {
|
||||
automationBotTitle = "Off"
|
||||
}
|
||||
items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerChatAutomation, label: .text(automationBotTitle), additionalBadgeLabel: nil, text: "Chat Automation", icon: PresentationResourcesSettings.aiTools, action: {
|
||||
interaction.editingOpenBusinessChatBots()
|
||||
}))
|
||||
items[.info]!.append(PeerInfoScreenCommentItem(id: ItemPeerChatAutomationHelp, text: "Add a bot to reply to messages on your behalf."))
|
||||
|
||||
items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: {
|
||||
interaction.openSettings(.addAccount)
|
||||
|
|
|
|||
|
|
@ -677,7 +677,11 @@ final class ChatbotSetupScreenComponent: Component {
|
|||
}
|
||||
transition.setPosition(view: self.titleTransformContainer, position: navigationTitleFrame.center)
|
||||
transition.setBounds(view: self.titleTransformContainer, bounds: CGRect(origin: CGPoint(), size: navigationTitleFrame.size))
|
||||
transition.setFrame(view: navigationTitleView, frame: CGRect(origin: CGPoint(), size: navigationTitleFrame.size))
|
||||
transition.setBounds(view: navigationTitleView, bounds: CGRect(origin: CGPoint(), size: navigationTitleFrame.size))
|
||||
transition.setPosition(view: navigationTitleView, position: CGPoint(
|
||||
x: navigationTitleFrame.size.width * 0.5,
|
||||
y: navigationTitleFrame.size.height * 0.5
|
||||
))
|
||||
}
|
||||
|
||||
let bottomContentInset: CGFloat = 24.0
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "voice_message_20.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/voice_message_20.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/voice_message_20.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -18,6 +18,7 @@ import TooltipUI
|
|||
import TopMessageReactions
|
||||
import TelegramNotices
|
||||
import PresentationDataUtils
|
||||
import ChatPresentationInterfaceState
|
||||
|
||||
extension ChatControllerImpl {
|
||||
func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void {
|
||||
|
|
@ -45,13 +46,15 @@ extension ChatControllerImpl {
|
|||
guard let topMessage = messages.first else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let canBypassReactionRestrictions = canBypassRestrictions(chatPresentationInterfaceState: self.presentationInterfaceState)
|
||||
|
||||
let _ = combineLatest(queue: .mainQueue(),
|
||||
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)),
|
||||
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: updatedMessages, controllerInteraction: self.controllerInteraction, selectAll: selectAll, interfaceInteraction: self.interfaceInteraction, messageNode: node as? ChatMessageItemView),
|
||||
peerMessageAllowedReactions(context: self.context, message: topMessage),
|
||||
peerMessageAllowedReactions(context: self.context, message: topMessage, ignoreDefault: canBypassReactionRestrictions),
|
||||
peerMessageSelectedReactions(context: self.context, message: topMessage),
|
||||
topMessageReactions(context: self.context, message: topMessage, subPeerId: self.chatLocation.threadId.flatMap(EnginePeer.Id.init)),
|
||||
topMessageReactions(context: self.context, message: topMessage, subPeerId: self.chatLocation.threadId.flatMap(EnginePeer.Id.init), ignoreDefault: canBypassReactionRestrictions),
|
||||
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: self.context.sharedContext.accountManager)
|
||||
).startStandalone(next: { [weak self] peer, actions, allowedReactionsAndStars, selectedReactions, topReactions, chatTextSelectionTips in
|
||||
guard let self else {
|
||||
|
|
@ -370,6 +373,11 @@ extension ChatControllerImpl {
|
|||
controller?.view.endEditing(true)
|
||||
|
||||
if case .stars = chosenUpdatedReaction.reaction {
|
||||
if !canSendReactionsToChat(self.presentationInterfaceState) {
|
||||
controller?.dismiss(completion: {})
|
||||
return
|
||||
}
|
||||
|
||||
if isLarge {
|
||||
if let controller {
|
||||
controller.dismiss(completion: { [weak self] in
|
||||
|
|
@ -505,6 +513,11 @@ extension ChatControllerImpl {
|
|||
isFirst = !currentReactions.contains(where: { $0.value == chosenReaction })
|
||||
}
|
||||
|
||||
if removedReaction == nil && !canSendReactionsToChat(self.presentationInterfaceState) {
|
||||
controller?.dismiss(completion: {})
|
||||
return
|
||||
}
|
||||
|
||||
if message.areReactionsTags(accountPeerId: self.context.account.peerId) {
|
||||
if removedReaction == nil, !topReactions.contains(where: { $0.reaction.rawValue == chosenReaction }) {
|
||||
if !self.presentationInterfaceState.isPremium {
|
||||
|
|
|
|||
|
|
@ -1679,6 +1679,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if case .default = reaction, strongSelf.chatLocation.peerId == strongSelf.context.account.peerId {
|
||||
return
|
||||
}
|
||||
if case .default = reaction, !canSendReactionsToChat(strongSelf.presentationInterfaceState) {
|
||||
return
|
||||
}
|
||||
if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject {
|
||||
if case let .hashTagSearch(publicPosts) = customChatContents.kind, publicPosts {
|
||||
return
|
||||
|
|
@ -1747,7 +1750,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
return
|
||||
}
|
||||
|
||||
let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message)
|
||||
let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message, ignoreDefault: canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState))
|
||||
|> deliverOnMainQueue).startStandalone(next: { allowedReactions, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
|
@ -1791,6 +1794,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
|
||||
if case .stars = chosenReaction {
|
||||
if !canSendReactionsToChat(strongSelf.presentationInterfaceState) {
|
||||
return
|
||||
}
|
||||
|
||||
if strongSelf.selectPollOptionFeedback == nil {
|
||||
strongSelf.selectPollOptionFeedback = HapticFeedback()
|
||||
}
|
||||
|
|
@ -1957,6 +1964,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
|
||||
if removedReaction == nil {
|
||||
if !canSendReactionsToChat(strongSelf.presentationInterfaceState) {
|
||||
return
|
||||
}
|
||||
|
||||
if !canAddMessageReactions(message: message) {
|
||||
itemNode.openMessageContextMenu()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -261,6 +261,30 @@ extension ChatControllerImpl {
|
|||
}))
|
||||
}
|
||||
|
||||
public func presentReactionDeletionOptions(author: Peer, messageId: MessageId) {
|
||||
guard self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudChannel else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.sharedContext.chatAvailableMessageActions(
|
||||
engine: self.context.engine,
|
||||
accountPeerId: self.context.account.peerId,
|
||||
messageIds: Set([messageId]),
|
||||
keepUpdated: false
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] actions in
|
||||
guard let self, !actions.options.isEmpty else {
|
||||
return
|
||||
}
|
||||
self.presentBanMessageOptions(
|
||||
accountPeerId: self.context.account.peerId,
|
||||
author: author,
|
||||
messageIds: Set([messageId]),
|
||||
options: actions.options,
|
||||
reaction: true
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func presentBanMessageOptions(accountPeerId: PeerId, author: Peer, messageIds: Set<MessageId>, options: ChatAvailableMessageActionOptions, reaction: Bool = false) {
|
||||
guard let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -188,33 +188,21 @@ extension ChatControllerImpl {
|
|||
}
|
||||
|
||||
var dismissController: ((@escaping () -> Void) -> Void)?
|
||||
let canDeleteReactions = canDeleteOtherUserMessages(message: message)
|
||||
let canDeleteReactions: Bool
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel {
|
||||
canDeleteReactions = channel.hasPermission(.deleteAllMessages)
|
||||
} else {
|
||||
canDeleteReactions = false
|
||||
}
|
||||
let canViewReactions = canViewMessageReactionList(message: message)
|
||||
let deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)?
|
||||
if canDeleteReactions && canViewReactions {
|
||||
deleteReaction = { [weak self] peer, _ in
|
||||
dismissController?({ [weak self] in
|
||||
guard let self, self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudChannel else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.sharedContext.chatAvailableMessageActions(
|
||||
engine: self.context.engine,
|
||||
accountPeerId: self.context.account.peerId,
|
||||
messageIds: Set([message.id]),
|
||||
keepUpdated: false
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] actions in
|
||||
guard let self, !actions.options.isEmpty else {
|
||||
return
|
||||
}
|
||||
self.presentBanMessageOptions(
|
||||
accountPeerId: self.context.account.peerId,
|
||||
author: peer._asPeer(),
|
||||
messageIds: Set([message.id]),
|
||||
options: actions.options,
|
||||
reaction: true
|
||||
)
|
||||
})
|
||||
self.presentReactionDeletionOptions(author: peer._asPeer(), messageId: message.id)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
|
@ -408,6 +396,10 @@ extension ChatControllerImpl {
|
|||
}
|
||||
|
||||
func openMessageSendStarsScreen(message: Message) {
|
||||
guard canSendReactionsToChat(self.presentationInterfaceState) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let current = self.currentSendStarsUndoController {
|
||||
self.currentSendStarsUndoController = nil
|
||||
current.dismiss()
|
||||
|
|
|
|||
|
|
@ -52,13 +52,6 @@ func canEditMessage(context: AccountContext, limitsConfiguration: EngineConfigur
|
|||
return canEditMessage(accountPeerId: context.account.peerId, limitsConfiguration: limitsConfiguration, message: message)
|
||||
}
|
||||
|
||||
func canDeleteOtherUserMessages(message: Message) -> Bool {
|
||||
guard let channel = message.peers[message.id.peerId] as? TelegramChannel else {
|
||||
return false
|
||||
}
|
||||
return channel.hasPermission(.deleteAllMessages)
|
||||
}
|
||||
|
||||
private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineConfiguration.Limits, message: Message, reschedule: Bool = false) -> Bool {
|
||||
var hasEditRights = false
|
||||
var unlimitedInterval = reschedule
|
||||
|
|
@ -2181,30 +2174,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||
}
|
||||
|
||||
let deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)?
|
||||
if canDeleteOtherUserMessages(message: message) {
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.hasPermission(.deleteAllMessages) {
|
||||
deleteReaction = { [weak c] peer, _ in
|
||||
c?.dismiss(completion: {
|
||||
guard let chatController = interfaceInteraction.chatController() as? ChatControllerImpl, chatController.chatLocation.peerId?.namespace == Namespaces.Peer.CloudChannel else {
|
||||
guard let chatController = interfaceInteraction.chatController() as? ChatController else {
|
||||
return
|
||||
}
|
||||
let _ = (context.sharedContext.chatAvailableMessageActions(
|
||||
engine: context.engine,
|
||||
accountPeerId: context.account.peerId,
|
||||
messageIds: Set([message.id]),
|
||||
keepUpdated: false
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { actions in
|
||||
guard !actions.options.isEmpty else {
|
||||
return
|
||||
}
|
||||
chatController.presentBanMessageOptions(
|
||||
accountPeerId: context.account.peerId,
|
||||
author: peer._asPeer(),
|
||||
messageIds: Set([message.id]),
|
||||
options: actions.options,
|
||||
reaction: true
|
||||
)
|
||||
})
|
||||
chatController.presentReactionDeletionOptions(author: peer._asPeer(), messageId: message.id)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||
controller.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: timecode, quote: (highlight?.quote).flatMap { quote in NavigateToMessageParams.Quote(string: quote.string, offset: quote.offset) }, subject: highlight?.subject, setupReply: setupReply)), animated: isFirst || params.forceAnimatedScroll, completion: { [weak navigationController, weak controller] in
|
||||
if let navigationController = navigationController, let controller = controller {
|
||||
let _ = navigationController.popToViewController(controller, animated: animated)
|
||||
params.completion(controller)
|
||||
}
|
||||
}, customPresentProgress: { [weak navigationController] c, a in
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(c, in: .window(.root), with: a)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue