mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
06a0031bdd
commit
14bbdca050
11 changed files with 172 additions and 19 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<Empty>?
|
||||
|
||||
|
||||
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<Empty>
|
||||
|
||||
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<Empty>()
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
|
||||
"//submodules/TelegramUI/Components/AlertComponent",
|
||||
"//submodules/ShimmerEffect",
|
||||
"//submodules/UndoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue