diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b7e3a23bc7..ae9ce796bf 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -16256,3 +16256,11 @@ Error: %8$@"; "Settings.ChatAutomation" = "Chat Automation"; "Settings.ChatAutomationInfo" = "Add a bot to reply to messages on your behalf."; "Settings.ChatAutomationOff" = "Off"; + +"Chat.SendReactionRestricted" = "You cannot send reactions in this chat."; + +"ChatbotSetup.BotInstalled" = "%@ now manages your account."; + +"ChatbotSetup.SetupNotCompleted.Title" = "No Bot Added"; +"ChatbotSetup.SetupNotCompleted.Text" = "You haven’t added a bot to manage your account. Leave anyway?"; +"ChatbotSetup.SetupNotCompleted.Leave" = "Leave"; diff --git a/submodules/Components/ReactionListContextMenuContent/BUILD b/submodules/Components/ReactionListContextMenuContent/BUILD index 4ee75ad2d2..d91f85c4ab 100644 --- a/submodules/Components/ReactionListContextMenuContent/BUILD +++ b/submodules/Components/ReactionListContextMenuContent/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/ContextUI:ContextUI", "//submodules/AvatarNode:AvatarNode", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", "//submodules/Components/ReactionImageComponent:ReactionImageComponent", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", diff --git a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift index 5ae4f9ddcc..980ca8e473 100644 --- a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift +++ b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift @@ -2,6 +2,7 @@ import Foundation import AsyncDisplayKit import Display import ComponentFlow +import MultilineTextComponent import SwiftSignalKit import TelegramCore import AccountContext @@ -373,12 +374,13 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent let availableReactions: AvailableReactions? let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer + private let highlightBackgroundView: UIView let avatarNode: AvatarNode let titleLabelNode: ImmediateTextNode let textLabelNode: ImmediateTextNode let readIconView: UIImageView var credibilityIconView: ComponentView? - + private var reactionLayer: InlineStickerItemLayer? private var iconFrame: CGRect? private var file: TelegramMediaFile? @@ -419,16 +421,35 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.readIconView = UIImageView(image: readIconImage) + self.highlightBackgroundView = UIView() + self.highlightBackgroundView.alpha = 0.0 + self.highlightBackgroundView.isUserInteractionEnabled = false + super.init() - + self.isAccessibilityElement = true - + + self.view.insertSubview(self.highlightBackgroundView, at: 0) + self.addSubnode(self.avatarNode) self.addSubnode(self.titleLabelNode) self.addSubnode(self.textLabelNode) self.view.addSubview(self.readIconView) self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.highlightBackgroundView.layer.removeAnimation(forKey: "opacity") + self.highlightBackgroundView.alpha = 0.1 + } else { + let currentAlpha = self.highlightBackgroundView.alpha + self.highlightBackgroundView.alpha = 0.0 + self.highlightBackgroundView.layer.animateAlpha(from: currentAlpha, to: 0.0, duration: 0.3) + } + } let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longTapGesture(_:))) longTapRecognizer.isEnabled = false @@ -537,7 +558,12 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent let sideInset: CGFloat = 16.0 let reaction: MessageReaction.Reaction? = item.reaction - + + self.highlightBackgroundView.backgroundColor = presentationData.theme.overallDarkAppearance ? UIColor.white : UIColor.black + self.highlightBackgroundView.setMonochromaticEffect(tintColor: self.highlightBackgroundView.backgroundColor) + self.highlightBackgroundView.frame = CGRect(origin: CGPoint(x: 10.0, y: 5.0), size: CGSize(width: max(0.0, size.width - 20.0), height: size.height - 10.0)) + self.highlightBackgroundView.layer.cornerRadius = self.highlightBackgroundView.frame.height * 0.5 + if self.displayReactionIcon, reaction != self.item?.reaction { if let reaction = reaction { switch reaction { @@ -810,6 +836,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent private var hasMore: Bool = false private let scrollNode: ASScrollNode + private let separatorNode: ASDisplayNode private var ignoreScrolling: Bool = false private var animateIn: Bool = false private var bottomScrollInset: CGFloat = 0.0 @@ -826,7 +853,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent private var placeholderItemImage: UIImage? private var placeholderLayers: [Int: SimpleLayer] = [:] - + + private let deleteReactionInfoText: ComponentView + init( context: AccountContext, displayReadTimestamps: Bool, @@ -858,6 +887,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.state = ItemsState(listState: EngineMessageReactionListContext.State(message: message, readStats: readStats, reaction: reaction), readStats: readStats) self.scrollNode = ASScrollNode() + self.separatorNode = ASDisplayNode() + self.deleteReactionInfoText = ComponentView() + self.scrollNode.canCancelAllTouchesInViews = true self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.showsVerticalScrollIndicator = false @@ -871,6 +903,10 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.addSubnode(self.scrollNode) self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.separatorNode.isHidden = true + self.separatorNode.isUserInteractionEnabled = false + self.scrollNode.addSubnode(self.separatorNode) + self.clipsToBounds = true self.stateDisposable = (self.listContext.state @@ -965,7 +1001,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent } itemNode.update(size: itemFrame.size, presentationData: presentationData, item: item, isLast: self.state.item(at: index + 1) == nil, syncronousLoad: syncronousLoad) - if let deleteReaction = self.deleteReaction, let reaction = item.reaction { + if let deleteReaction = self.deleteReaction, let reaction = item.reaction, item.peer.id != self.context.account.peerId { let peer = item.peer itemNode.longTapAction = { deleteReaction(peer, reaction) @@ -1079,7 +1115,70 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent } self.presentationData = presentationData - let size = CGSize(width: constrainedSize.width, height: topInset + CGFloat(self.state.totalCount) * itemHeight + topInset) + let baseContentHeight = topInset + CGFloat(self.state.totalCount) * itemHeight + topInset + var footerHeight: CGFloat = 0.0 + let displayDeleteReactionInfoFooter: Bool + if self.deleteReaction != nil { + displayDeleteReactionInfoFooter = self.state.mergedItems.contains(where: { item in + return item.reaction != nil && item.peer.id != self.context.account.peerId + }) + } else { + displayDeleteReactionInfoFooter = false + } + + if displayDeleteReactionInfoFooter { + let separatorHeight: CGFloat = 20.0 + let horizontalInset: CGFloat = 18.0 + let textTopInset: CGFloat = 5.0 + let textBottomInset: CGFloat = 18.0 + let textFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)) + + self.separatorNode.isHidden = false + self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor + + var animateIn = false + var footerTransition = transition + if self.deleteReactionInfoText.view?.superview == nil, footerTransition.isAnimated { + footerTransition = .immediate + animateIn = true + } + footerTransition.updateFrame(node: self.separatorNode, frame: CGRect( + origin: CGPoint(x: horizontalInset, y: baseContentHeight + floorToScreenPixels((separatorHeight - UIScreenPixel) * 0.5)), + size: CGSize(width: max(0.0, constrainedSize.width - horizontalInset * 2.0), height: 1.0) + )) + + let textSize = self.deleteReactionInfoText.update( + transition: ComponentTransition(footerTransition), + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: presentationData.strings.Chat_DeleteReactionInfo, + font: textFont, + textColor: presentationData.theme.contextMenu.primaryColor + )), + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: CGSize(width: max(0.0, constrainedSize.width - horizontalInset * 2.0), height: .greatestFiniteMagnitude) + ) + if let textView = self.deleteReactionInfoText.view { + if textView.superview == nil { + self.scrollNode.view.addSubview(textView) + textView.isUserInteractionEnabled = false + } + footerTransition.updateFrame(view: textView, frame: CGRect(origin: CGPoint(x: horizontalInset, y: baseContentHeight + separatorHeight + textTopInset), size: textSize)) + + if animateIn { + self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + textView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } + + footerHeight = separatorHeight + textTopInset + textSize.height + textBottomInset + } else { + self.separatorNode.isHidden = true + self.deleteReactionInfoText.view?.removeFromSuperview() + } + let size = CGSize(width: constrainedSize.width, height: baseContentHeight + footerHeight) let containerSize = CGSize(width: size.width, height: min(constrainedSize.height, size.height)) self.currentSize = containerSize diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD index 210ac682a5..ed399a9fc6 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD @@ -36,6 +36,7 @@ swift_library( "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", "//submodules/TelegramUI/Components/AlertComponent", "//submodules/ShimmerEffect", + "//submodules/UndoUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift index bbe37bf01b..ecda61297b 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift @@ -208,7 +208,11 @@ final class ChatbotSearchResultItemComponent: Component { case let .found(peer, _): isTextVisible = true titleValue = peer.displayTitle(strings: component.strings, displayOrder: .firstLast) - subtitleValue = component.strings.Bot_GenericBotStatus + if let addressName = peer.addressName { + subtitleValue = "@\(addressName)" + } else { + subtitleValue = component.strings.Bot_GenericBotStatus + } } let titleSize = self.titleLabel.update( diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift index 8020a1a2f4..c387c29fa6 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift @@ -25,6 +25,7 @@ import Markdown import PeerListItemComponent import AvatarNode import AlertComponent +import UndoUI private let checkIcon: UIImage = { return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in @@ -216,10 +217,21 @@ final class ChatbotSetupScreenComponent: Component { } func attemptNavigation(complete: @escaping () -> Void) -> Bool { - guard let component = self.component else { + guard let component = self.component, let environemnt = self.environment else { return true } + if let botResolutionState = self.botResolutionState, case let .found(_, isInstalled) = botResolutionState.state, !isInstalled { + let alertController = textAlertController(context: component.context, title: environemnt.strings.ChatbotSetup_SetupNotCompleted_Title, text: environemnt.strings.ChatbotSetup_SetupNotCompleted_Text, actions: [ + TextAlertAction(type: .defaultAction, title: environemnt.strings.Common_Cancel, action: {}), + TextAlertAction(type: .genericAction, title: environemnt.strings.ChatbotSetup_SetupNotCompleted_Leave, action: { + complete() + }) + ]) + environemnt.controller()?.present(alertController, in: .window(.root)) + return false + } + var mappedCategories: TelegramBusinessRecipients.Categories = [] if self.additionalPeerList.categories.contains(.existingChats) { mappedCategories.insert(.existingChats) @@ -803,7 +815,7 @@ final class ChatbotSetupScreenComponent: Component { strings: environment.strings, content: mappedContent, installAction: { [weak self] in - guard let self else { + guard let self, let component = self.component, let environment = self.environment, let controller = self.environment?.controller() else { return } self.endEditing(true) @@ -820,6 +832,14 @@ final class ChatbotSetupScreenComponent: Component { }) ]), in: .window(.root)) } + controller.present(UndoOverlayController( + presentationData: presentationData, + content: .invitedToVoiceChat(context: component.context, peer: peer, title: nil, text: environment.strings.ChatbotSetup_BotInstalled(peer.compactDisplayTitle).string, action: nil, duration: 2.0), + elevatedLayout: false, + position: .bottom, + animateInAsReplacement: false, + action: { _ in return true } + ), in: .current) } }, removeAction: { [weak self] in diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index a1201fb48a..08750090f1 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -374,7 +374,13 @@ extension ChatControllerImpl { if case .stars = chosenUpdatedReaction.reaction { if !canSendReactionsToChat(self.presentationInterfaceState) { - controller?.dismiss(completion: {}) + if let controller { + controller.dismiss(completion: { [weak self] in + self?.displaySendReactionRestrictedToast() + }) + } else { + self.displaySendReactionRestrictedToast() + } return } @@ -514,7 +520,13 @@ extension ChatControllerImpl { } if removedReaction == nil && !canSendReactionsToChat(self.presentationInterfaceState) { - controller?.dismiss(completion: {}) + if let controller { + controller.dismiss(completion: { [weak self] in + self?.displaySendReactionRestrictedToast() + }) + } else { + self.displaySendReactionRestrictedToast() + } return } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift index f2ddc3926b..754f6996c2 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift @@ -10,6 +10,17 @@ import AccountContext import ChatControllerInteraction extension ChatControllerImpl { + func displaySendReactionRestrictedToast() { + self.controllerInteraction?.presentControllerInCurrent(UndoOverlayController( + presentationData: self.presentationData, + content: .banned(text: self.presentationData.strings.Chat_SendReactionRestricted), + elevatedLayout: false, + position: .bottom, + animateInAsReplacement: false, + action: { _ in return true } + ), nil) + } + func displayPostedScheduledMessagesToast(ids: [EngineMessage.Id]) { let timestamp = CFAbsoluteTimeGetCurrent() if self.lastPostedScheduledMessagesToastTimestamp + 0.4 >= timestamp { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fa5f539165..f845e2024d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1682,9 +1682,6 @@ 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 @@ -1798,6 +1795,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .stars = chosenReaction { if !canSendReactionsToChat(strongSelf.presentationInterfaceState) { + strongSelf.displaySendReactionRestrictedToast() return } @@ -1968,6 +1966,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if removedReaction == nil { if !canSendReactionsToChat(strongSelf.presentationInterfaceState) { + strongSelf.displaySendReactionRestrictedToast() return } diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index 28e3fc0de9..958341234f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -263,7 +263,7 @@ extension ChatControllerImpl { } public func presentReactionDeletionOptions(author: Peer, messageId: MessageId) { - guard self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudChannel else { + guard self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudChannel, author.id != self.context.account.peerId else { return } let _ = (self.context.sharedContext.chatAvailableMessageActions( diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index 96ce489741..2be170fc78 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -321,9 +321,7 @@ extension ChatControllerImpl { let presentationContext = self.controllerInteraction?.presentationContext let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - if canDeleteReactions && canViewReactions { - items.tip = .deleteReaction - } else if !packReferences.isEmpty && !premiumConfiguration.isPremiumDisabled { + if !packReferences.isEmpty && !premiumConfiguration.isPremiumDisabled { items.tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil) if packReferences.count > 1 {