mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Business features
This commit is contained in:
parent
4b1b272081
commit
b25f8ed37f
@ -11362,7 +11362,7 @@ Sorry for the inconvenience.";
|
||||
"Business.QuickReplies" = "Quick Replies";
|
||||
"Business.GreetingMessages" = "Greeting Messages";
|
||||
"Business.AwayMessages" = "Away Messages";
|
||||
"Business.Chatbots" = "Chatbots";
|
||||
"Business.ChatbotsItem" = "Chatbots";
|
||||
"Business.Intro" = "Intro";
|
||||
|
||||
"Business.LocationInfo" = "Display the location of your business on your account.";
|
||||
@ -11409,6 +11409,7 @@ Sorry for the inconvenience.";
|
||||
"ChatList.ItemMenuEdit" = "Edit";
|
||||
"ChatList.ItemMenuDelete" = "Delete";
|
||||
"ChatList.PeerTypeNonContact" = "non-contact";
|
||||
"ChatList.PeerTypeNonContactUser" = "non-contact";
|
||||
|
||||
"ChatListFilter.TagLabelNoTag" = "NO TAG";
|
||||
"ChatListFilter.TagLabelPremiumExpired" = "PREMIUM EXPIRED";
|
||||
@ -11575,7 +11576,7 @@ Sorry for the inconvenience.";
|
||||
"BusinessLocationSetup.AlertUnsavedChanges.Text" = "You have unsaved changes.";
|
||||
"BusinessLocationSetup.AlertUnsavedChanges.ResetAction" = "Revert";
|
||||
|
||||
"ChatbotSetup.Title" = "Chatbots";
|
||||
"ChatbotSetup.TitleItem" = "Chatbots";
|
||||
"ChatbotSetup.Text" = "Add a bot to your account to help you automatically process and respond to the messages you receive. [Learn More >]()";
|
||||
"ChatbotSetup.TextLink" = "https://telegram.org";
|
||||
|
||||
|
@ -4165,14 +4165,14 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres
|
||||
if isContact {
|
||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
||||
} else {
|
||||
return (strings.ChatList_PeerTypeNonContact, false, false, nil)
|
||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
||||
}
|
||||
}
|
||||
} else if case .secretChat = peer {
|
||||
if isContact {
|
||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
||||
} else {
|
||||
return (strings.ChatList_PeerTypeNonContact, false, false, nil)
|
||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
||||
}
|
||||
} else if case .legacyGroup = peer {
|
||||
return (strings.ChatList_PeerTypeGroup, false, false, nil)
|
||||
@ -4183,7 +4183,7 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres
|
||||
return (strings.ChatList_PeerTypeChannel, false, false, nil)
|
||||
}
|
||||
}
|
||||
return (strings.ChatList_PeerTypeNonContact, false, false, nil)
|
||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
||||
}
|
||||
|
||||
public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
|
||||
|
@ -633,7 +633,7 @@ public enum PremiumPerk: CaseIterable {
|
||||
case .businessAwayMessage:
|
||||
return strings.Business_AwayMessages
|
||||
case .businessChatBots:
|
||||
return strings.Business_Chatbots
|
||||
return strings.Business_ChatbotsItem
|
||||
case .businessIntro:
|
||||
return strings.Business_Intro
|
||||
}
|
||||
|
@ -971,7 +971,7 @@ public class PremiumLimitsListScreen: ViewController {
|
||||
videoFile: videos["business_bots"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_Chatbots,
|
||||
title: strings.Business_ChatbotsItem,
|
||||
text: strings.Business_ChatbotsInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
|
@ -600,7 +600,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
|
||||
extension StoreMessage {
|
||||
convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
|
||||
switch apiMessage {
|
||||
case let .message(flags, _, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId):
|
||||
case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId):
|
||||
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
|
||||
|
||||
var namespace = namespace
|
||||
@ -906,7 +906,7 @@ extension StoreMessage {
|
||||
storeFlags.insert(.IsForumTopic)
|
||||
}
|
||||
|
||||
if (flags & (1 << 4)) != 0 || (flags & (1 << 13)) != 0 {
|
||||
if (flags & (1 << 4)) != 0 || (flags & (1 << 13)) != 0 || (flags2 & (1 << 1)) != 0 {
|
||||
var notificationFlags: NotificationInfoMessageAttributeFlags = []
|
||||
if (flags & (1 << 4)) != 0 {
|
||||
notificationFlags.insert(.personal)
|
||||
@ -916,6 +916,9 @@ extension StoreMessage {
|
||||
if (flags & (1 << 13)) != 0 {
|
||||
notificationFlags.insert(.muted)
|
||||
}
|
||||
if (flags2 & (1 << 1)) != 0 {
|
||||
notificationFlags.insert(.automaticMessage)
|
||||
}
|
||||
attributes.append(NotificationInfoMessageAttribute(flags: notificationFlags))
|
||||
}
|
||||
|
||||
|
@ -3452,9 +3452,19 @@ func replayFinalState(
|
||||
if message.flags.contains(.Incoming) {
|
||||
addedOperationIncomingMessageIds.append(id)
|
||||
if let authorId = message.authorId {
|
||||
var isAutomatic = false
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? NotificationInfoMessageAttribute {
|
||||
if attribute.flags.contains(.automaticMessage) {
|
||||
isAutomatic = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isAutomatic {
|
||||
recordPeerActivityTimestamp(peerId: authorId, timestamp: message.timestamp, into: &peerActivityTimestamps)
|
||||
}
|
||||
}
|
||||
}
|
||||
if message.flags.contains(.WasScheduled) {
|
||||
wasOperationScheduledMessageIds.append(id)
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ public struct NotificationInfoMessageAttributeFlags: OptionSet {
|
||||
self.rawValue = 0
|
||||
}
|
||||
|
||||
public static let muted = NotificationInfoMessageAttributeFlags(rawValue: 1)
|
||||
public static let personal = NotificationInfoMessageAttributeFlags(rawValue: 2)
|
||||
|
||||
public static let muted = NotificationInfoMessageAttributeFlags(rawValue: 1 << 0)
|
||||
public static let personal = NotificationInfoMessageAttributeFlags(rawValue: 1 << 1)
|
||||
public static let automaticMessage = NotificationInfoMessageAttributeFlags(rawValue: 1 << 2)
|
||||
}
|
||||
|
||||
public class NotificationInfoMessageAttribute: MessageAttribute {
|
||||
|
@ -1613,34 +1613,6 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
|
||||
public struct BusinessIntro: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = CachedTelegramBusinessIntro?
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .cachedPeerData(peerId: self.id)
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? CachedPeerDataView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let cachedData = view.cachedPeerData as? CachedUserData {
|
||||
return cachedData.businessIntro
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatManagingBot: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = PeerStatusSettings.ManagingBot?
|
||||
|
||||
|
@ -371,6 +371,9 @@ func _internal_toggleChatManagingBotIsPaused(account: Account, chatId: EnginePee
|
||||
if let managingBot = peerStatusSettings.managingBot {
|
||||
isPaused = !managingBot.isPaused
|
||||
peerStatusSettings.managingBot?.isPaused = isPaused
|
||||
if !isPaused {
|
||||
peerStatusSettings.managingBot?.canReply = true
|
||||
}
|
||||
}
|
||||
|
||||
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
@ -412,6 +415,35 @@ func _internal_removeChatManagingBot(account: Account, chatId: EnginePeer.Id) ->
|
||||
return current
|
||||
}
|
||||
})
|
||||
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
|
||||
guard let current = current as? CachedUserData else {
|
||||
return current
|
||||
}
|
||||
|
||||
if let connectedBot = current.connectedBot {
|
||||
var additionalPeers = connectedBot.recipients.additionalPeers
|
||||
var excludePeers = connectedBot.recipients.excludePeers
|
||||
if connectedBot.recipients.exclude {
|
||||
additionalPeers.insert(chatId)
|
||||
} else {
|
||||
additionalPeers.remove(chatId)
|
||||
excludePeers.insert(chatId)
|
||||
}
|
||||
|
||||
return current.withUpdatedConnectedBot(TelegramAccountConnectedBot(
|
||||
id: connectedBot.id,
|
||||
recipients: TelegramBusinessRecipients(
|
||||
categories: connectedBot.recipients.categories,
|
||||
additionalPeers: additionalPeers,
|
||||
excludePeers: excludePeers,
|
||||
exclude: connectedBot.recipients.exclude
|
||||
),
|
||||
canReply: connectedBot.canReply
|
||||
))
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Never, NoError> in
|
||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
|
@ -811,7 +811,7 @@ extension TelegramBusinessRecipients {
|
||||
self.init(
|
||||
categories: categories,
|
||||
additionalPeers: Set((users ?? []).map( { PeerId(namespace: Namespaces.Peer.CloudUser, id: ._internalFromInt64Value($0)) })),
|
||||
excludePeers: Set((excludeUsers ?? []).map(PeerId.init)),
|
||||
excludePeers: Set((excludeUsers ?? []).map( { PeerId(namespace: Namespaces.Peer.CloudUser, id: ._internalFromInt64Value($0)) })),
|
||||
exclude: (flags & (1 << 5)) != 0
|
||||
)
|
||||
}
|
||||
|
@ -1175,6 +1175,250 @@ private enum ChatEmptyNodeContentType: Equatable {
|
||||
case premiumRequired
|
||||
}
|
||||
|
||||
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
|
||||
private struct Params: Equatable {
|
||||
var theme: PresentationTheme
|
||||
var strings: PresentationStrings
|
||||
var chatWallpaper: TelegramWallpaper
|
||||
var peer: EnginePeer
|
||||
var constrainedSize: CGSize
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, chatWallpaper: TelegramWallpaper, peer: EnginePeer, constrainedSize: CGSize) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.chatWallpaper = chatWallpaper
|
||||
self.peer = peer
|
||||
self.constrainedSize = constrainedSize
|
||||
}
|
||||
|
||||
static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.chatWallpaper != rhs.chatWallpaper {
|
||||
return false
|
||||
}
|
||||
if lhs.constrainedSize != rhs.constrainedSize {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private struct Layout {
|
||||
var params: Params
|
||||
var size: CGSize
|
||||
|
||||
init(params: Params, size: CGSize) {
|
||||
self.params = params
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
private let textNode: ImmediateTextNode
|
||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private let textMaskNode: LinkHighlightingNode
|
||||
|
||||
private let badgeTextNode: ImmediateTextNode
|
||||
private let badgeBackgroundView: UIImageView
|
||||
|
||||
private var currentLayout: Layout?
|
||||
|
||||
var action: (() -> Void)?
|
||||
|
||||
override init(pointerStyle: PointerStyle? = nil) {
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.textAlignment = .center
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.textMaskNode = LinkHighlightingNode(color: .white)
|
||||
self.textMaskNode.innerRadius = 5.0
|
||||
self.textMaskNode.outerRadius = 10.0
|
||||
self.textMaskNode.inset = 0.0
|
||||
|
||||
self.badgeTextNode = ImmediateTextNode()
|
||||
self.badgeBackgroundView = UIImageView()
|
||||
|
||||
super.init(pointerStyle: pointerStyle)
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.view.addSubview(self.badgeBackgroundView)
|
||||
self.addSubnode(self.badgeTextNode)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let self, self.bounds.width > 0.0 {
|
||||
let animateScale = true
|
||||
|
||||
let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
|
||||
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
|
||||
|
||||
if highlighted {
|
||||
self.layer.removeAnimation(forKey: "transform.scale")
|
||||
|
||||
if animateScale {
|
||||
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||
transition.setScale(layer: self.layer, scale: topScale)
|
||||
}
|
||||
} else {
|
||||
if animateScale {
|
||||
let transition = Transition(animation: .none)
|
||||
transition.setScale(layer: self.layer, scale: 1.0)
|
||||
|
||||
self.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
self.action?()
|
||||
}
|
||||
|
||||
func update(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
chatWallpaper: TelegramWallpaper,
|
||||
peer: EnginePeer,
|
||||
wallpaperBackgroundNode: WallpaperBackgroundNode?,
|
||||
constrainedSize: CGSize
|
||||
) -> CGSize {
|
||||
let params = Params(
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
chatWallpaper: chatWallpaper,
|
||||
peer: peer,
|
||||
constrainedSize: constrainedSize
|
||||
)
|
||||
if let currentLayout = self.currentLayout, currentLayout.params == params {
|
||||
return currentLayout.size
|
||||
} else {
|
||||
let size = self.updateInternal(params: params, wallpaperBackgroundNode: wallpaperBackgroundNode)
|
||||
self.currentLayout = Layout(params: params, size: size)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
private func updateInternal(params: Params, wallpaperBackgroundNode: WallpaperBackgroundNode?) -> CGSize {
|
||||
let serviceColor = serviceMessageColorComponents(theme: params.theme, wallpaper: params.chatWallpaper)
|
||||
|
||||
//TODO:localize
|
||||
let textString = NSMutableAttributedString()
|
||||
textString.append(NSAttributedString(string: "\(params.peer.compactDisplayTitle) added the message above for all empty chats", font: Font.regular(13.0), textColor: serviceColor.primaryText))
|
||||
textString.append(NSAttributedString(string: " .how?", font: Font.regular(11.0), textColor: .clear))
|
||||
self.textNode.attributedText = textString
|
||||
|
||||
let maxTextSize = CGSize(width: min(300.0, params.constrainedSize.width - 8.0 * 2.0), height: params.constrainedSize.height - 8.0 * 2.0)
|
||||
|
||||
var bestSize: (availableWidth: CGFloat, info: TextNodeLayout)
|
||||
let info = self.textNode.updateLayoutFullInfo(maxTextSize)
|
||||
bestSize = (maxTextSize.width, info)
|
||||
if info.numberOfLines > 1 {
|
||||
let measureIncrement = 8.0
|
||||
var measureWidth = info.size.width
|
||||
measureWidth -= measureIncrement
|
||||
while measureWidth > 0.0 {
|
||||
let otherInfo = self.textNode.updateLayoutFullInfo(CGSize(width: measureWidth, height: maxTextSize.height))
|
||||
if otherInfo.numberOfLines > bestSize.info.numberOfLines {
|
||||
break
|
||||
}
|
||||
if (otherInfo.size.width - otherInfo.trailingLineWidth) < (bestSize.info.size.width - bestSize.info.trailingLineWidth) {
|
||||
bestSize = (measureWidth, otherInfo)
|
||||
}
|
||||
|
||||
measureWidth -= measureIncrement
|
||||
}
|
||||
|
||||
let bestInfo = self.textNode.updateLayoutFullInfo(CGSize(width: bestSize.availableWidth, height: maxTextSize.height))
|
||||
bestSize = (maxTextSize.width, bestInfo)
|
||||
}
|
||||
|
||||
let textLayout = bestSize.info
|
||||
|
||||
var labelRects = textLayout.linesRects()
|
||||
if labelRects.count > 1 {
|
||||
let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
|
||||
for i in 0 ..< sortedIndices.count {
|
||||
let index = sortedIndices[i]
|
||||
for j in -1 ... 1 {
|
||||
if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
|
||||
if abs(labelRects[index + j].width - labelRects[index].width) < 16.0 {
|
||||
labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
|
||||
labelRects[index].size.width = labelRects[index + j].size.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0 ..< labelRects.count {
|
||||
labelRects[i] = labelRects[i].insetBy(dx: -6.0, dy: floor((labelRects[i].height - 20.0) / 2.0))
|
||||
labelRects[i].size.height = 20.0
|
||||
labelRects[i].origin.x = floor((textLayout.size.width - labelRects[i].width) / 2.0)
|
||||
}
|
||||
self.textMaskNode.updateRects(labelRects)
|
||||
|
||||
let size = CGSize(width: textLayout.size.width + 4.0 * 2.0, height: textLayout.size.height + 4.0 * 2.0)
|
||||
let textFrame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: textLayout.size)
|
||||
self.textNode.frame = textFrame
|
||||
|
||||
//TODO:localize
|
||||
self.badgeTextNode.attributedText = NSAttributedString(string: "how?", font: Font.regular(11.0), textColor: serviceColor.primaryText)
|
||||
let badgeTextSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: 100.0))
|
||||
if let lastLineFrame = labelRects.last {
|
||||
let badgeTextFrame = CGRect(origin: CGPoint(x: lastLineFrame.maxX - badgeTextSize.width - 2.0, y: textFrame.maxY - badgeTextSize.height), size: badgeTextSize)
|
||||
self.badgeTextNode.frame = badgeTextFrame
|
||||
|
||||
let badgeBackgroundFrame = badgeTextFrame.insetBy(dx: -4.0, dy: -1.0)
|
||||
if badgeBackgroundFrame.height != self.badgeBackgroundView.image?.size.height {
|
||||
self.badgeBackgroundView.image = generateStretchableFilledCircleImage(diameter: badgeBackgroundFrame.height, color: serviceColor.primaryText.withMultipliedAlpha(0.1))
|
||||
}
|
||||
self.badgeBackgroundView.frame = badgeBackgroundFrame
|
||||
}
|
||||
|
||||
self.textMaskNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - self.textMaskNode.inset + 4.0, y: textFrame.minY - self.textMaskNode.inset - 11.0), size: CGSize())
|
||||
|
||||
if let wallpaperBackgroundNode {
|
||||
if self.backgroundContent == nil, let backgroundContent = wallpaperBackgroundNode.makeBubbleBackground(for: .free) {
|
||||
|
||||
self.backgroundContent = backgroundContent
|
||||
backgroundContent.view.mask = self.textMaskNode.view
|
||||
self.insertSubnode(backgroundContent, at: 0)
|
||||
}
|
||||
|
||||
if let backgroundContent = self.backgroundContent {
|
||||
backgroundContent.frame = CGRect(origin: CGPoint(x: -4.0, y: 0.0), size: CGSize(width: size.width + 4.0 * 2.0, height: size.height))
|
||||
}
|
||||
} else if let backgroundContent = self.backgroundContent {
|
||||
self.backgroundContent = nil
|
||||
backgroundContent.removeFromSupernode()
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func updateAbsolutePosition(rect: CGRect, containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
guard let backgroundContent = self.backgroundContent else {
|
||||
return
|
||||
}
|
||||
var backgroundFrame = backgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatEmptyNode: ASDisplayNode {
|
||||
public enum Subject {
|
||||
public enum EmptyType: Equatable {
|
||||
@ -1203,6 +1447,7 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
private var currentStrings: PresentationStrings?
|
||||
|
||||
private var content: (ChatEmptyNodeContentType, ASDisplayNode & ChatEmptyNodeContent)?
|
||||
private var attachedDescriptionNode: EmptyAttachedDescriptionNode?
|
||||
|
||||
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) {
|
||||
self.context = context
|
||||
@ -1247,6 +1492,12 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
backgroundContent.cornerRadius = initialFrame.size.width / 2.0
|
||||
transition.updateCornerRadius(layer: backgroundContent.layer, cornerRadius: targetCornerRadius)
|
||||
}
|
||||
|
||||
if let attachedDescriptionNode = self.attachedDescriptionNode {
|
||||
attachedDescriptionNode.layer.animatePosition(from: initialFrame.center, to: attachedDescriptionNode.position, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
attachedDescriptionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
attachedDescriptionNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
|
||||
@ -1265,6 +1516,7 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
let contentType: ChatEmptyNodeContentType
|
||||
var displayAttachedDescription = false
|
||||
switch subject {
|
||||
case .detailsPlaceholder:
|
||||
contentType = .regular
|
||||
@ -1298,6 +1550,9 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
contentType = .regular
|
||||
} else {
|
||||
contentType = .greeting
|
||||
if interfaceState.businessIntro != nil {
|
||||
displayAttachedDescription = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1368,6 +1623,46 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.backgroundNode, frame: contentFrame)
|
||||
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(20.0, self.backgroundNode.bounds.height / 2.0), transition: transition)
|
||||
|
||||
if displayAttachedDescription, let peer = interfaceState.renderedPeer?.chatMainPeer {
|
||||
let attachedDescriptionNode: EmptyAttachedDescriptionNode
|
||||
if let current = self.attachedDescriptionNode {
|
||||
attachedDescriptionNode = current
|
||||
} else {
|
||||
attachedDescriptionNode = EmptyAttachedDescriptionNode()
|
||||
self.attachedDescriptionNode = attachedDescriptionNode
|
||||
self.addSubnode(attachedDescriptionNode)
|
||||
|
||||
attachedDescriptionNode.action = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil)
|
||||
self.interaction?.chatController()?.push(controller)
|
||||
}
|
||||
}
|
||||
|
||||
let attachedDescriptionSize = attachedDescriptionNode.update(
|
||||
theme: interfaceState.theme,
|
||||
strings: interfaceState.strings,
|
||||
chatWallpaper: interfaceState.chatWallpaper,
|
||||
peer: EnginePeer(peer),
|
||||
wallpaperBackgroundNode: backgroundNode,
|
||||
constrainedSize: CGSize(width: size.width - insets.left - insets.right, height: 200.0)
|
||||
)
|
||||
let attachedDescriptionFrame = CGRect(origin: CGPoint(x: floor((size.width - attachedDescriptionSize.width) * 0.5), y: contentFrame.maxY + 4.0), size: attachedDescriptionSize)
|
||||
transition.updateFrame(node: attachedDescriptionNode, frame: attachedDescriptionFrame)
|
||||
|
||||
if let (rect, containerSize) = self.absolutePosition {
|
||||
var backgroundFrame = attachedDescriptionNode.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
attachedDescriptionNode.updateAbsolutePosition(rect: backgroundFrame, containerSize: containerSize, transition: .immediate)
|
||||
}
|
||||
} else if let attachedDescriptionNode = self.attachedDescriptionNode {
|
||||
self.attachedDescriptionNode = nil
|
||||
attachedDescriptionNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if backgroundNode?.hasExtraBubbleBackground() == true {
|
||||
if self.backgroundContent == nil, let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
backgroundContent.clipsToBounds = true
|
||||
@ -1409,5 +1704,12 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
|
||||
}
|
||||
|
||||
if let attachedDescriptionNode = self.attachedDescriptionNode {
|
||||
var backgroundFrame = attachedDescriptionNode.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
attachedDescriptionNode.updateAbsolutePosition(rect: backgroundFrame, containerSize: containerSize, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,10 +91,11 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
private var premiumSeparatorRight: SimpleLayer?
|
||||
private var premiumSeparatorText: ComponentView<Empty>?
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let leftButton = ComponentView<Empty>()
|
||||
private let descriptionText = ComponentView<Empty>()
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
|
||||
private var title: ComponentView<Empty>?
|
||||
private var descriptionText: ComponentView<Empty>?
|
||||
private var actionButton: ComponentView<Empty>?
|
||||
|
||||
private let itemContainerView: UIView
|
||||
private var items: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
@ -242,7 +243,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
if let actionButtonView = self.actionButton?.view {
|
||||
actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
@ -256,7 +257,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
})
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
if let actionButtonView = self.actionButton?.view {
|
||||
actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
}
|
||||
@ -280,6 +281,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
self.environment = environment
|
||||
|
||||
let hasPremiumRestrictedUsers = "".isEmpty
|
||||
let hasInviteLink = "".isEmpty
|
||||
|
||||
if themeUpdated {
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
@ -373,35 +375,6 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
self.premiumButton = premiumButton
|
||||
}
|
||||
|
||||
let premiumSeparatorText: ComponentView<Empty>
|
||||
if let current = self.premiumSeparatorText {
|
||||
premiumSeparatorText = current
|
||||
} else {
|
||||
premiumSeparatorText = ComponentView()
|
||||
self.premiumSeparatorText = premiumSeparatorText
|
||||
}
|
||||
|
||||
let premiumSeparatorLeft: SimpleLayer
|
||||
if let current = self.premiumSeparatorLeft {
|
||||
premiumSeparatorLeft = current
|
||||
} else {
|
||||
premiumSeparatorLeft = SimpleLayer()
|
||||
self.premiumSeparatorLeft = premiumSeparatorLeft
|
||||
self.scrollContentView.layer.addSublayer(premiumSeparatorLeft)
|
||||
}
|
||||
|
||||
let premiumSeparatorRight: SimpleLayer
|
||||
if let current = self.premiumSeparatorRight {
|
||||
premiumSeparatorRight = current
|
||||
} else {
|
||||
premiumSeparatorRight = SimpleLayer()
|
||||
self.premiumSeparatorRight = premiumSeparatorRight
|
||||
self.scrollContentView.layer.addSublayer(premiumSeparatorRight)
|
||||
}
|
||||
|
||||
premiumSeparatorLeft.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||
premiumSeparatorRight.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||
|
||||
//TODO:localize
|
||||
let premiumTitleSize = premiumTitle.update(
|
||||
transition: .immediate,
|
||||
@ -531,6 +504,37 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
transition.setFrame(view: premiumButtonView, frame: premiumButtonFrame)
|
||||
}
|
||||
contentHeight += premiumButtonSize.height
|
||||
|
||||
if hasInviteLink {
|
||||
let premiumSeparatorText: ComponentView<Empty>
|
||||
if let current = self.premiumSeparatorText {
|
||||
premiumSeparatorText = current
|
||||
} else {
|
||||
premiumSeparatorText = ComponentView()
|
||||
self.premiumSeparatorText = premiumSeparatorText
|
||||
}
|
||||
|
||||
let premiumSeparatorLeft: SimpleLayer
|
||||
if let current = self.premiumSeparatorLeft {
|
||||
premiumSeparatorLeft = current
|
||||
} else {
|
||||
premiumSeparatorLeft = SimpleLayer()
|
||||
self.premiumSeparatorLeft = premiumSeparatorLeft
|
||||
self.scrollContentView.layer.addSublayer(premiumSeparatorLeft)
|
||||
}
|
||||
|
||||
let premiumSeparatorRight: SimpleLayer
|
||||
if let current = self.premiumSeparatorRight {
|
||||
premiumSeparatorRight = current
|
||||
} else {
|
||||
premiumSeparatorRight = SimpleLayer()
|
||||
self.premiumSeparatorRight = premiumSeparatorRight
|
||||
self.scrollContentView.layer.addSublayer(premiumSeparatorRight)
|
||||
}
|
||||
|
||||
premiumSeparatorLeft.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||
premiumSeparatorRight.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor
|
||||
|
||||
contentHeight += 19.0
|
||||
|
||||
let premiumSeparatorTextSize = premiumSeparatorText.update(
|
||||
@ -557,18 +561,6 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
|
||||
contentHeight += 31.0
|
||||
} else {
|
||||
if let premiumTitle = self.premiumTitle {
|
||||
self.premiumTitle = nil
|
||||
premiumTitle.view?.removeFromSuperview()
|
||||
}
|
||||
if let premiumText = self.premiumText {
|
||||
self.premiumText = nil
|
||||
premiumText.view?.removeFromSuperview()
|
||||
}
|
||||
if let premiumButton = self.premiumButton {
|
||||
self.premiumButton = nil
|
||||
premiumButton.view?.removeFromSuperview()
|
||||
}
|
||||
if let premiumSeparatorLeft = self.premiumSeparatorLeft {
|
||||
self.premiumSeparatorLeft = nil
|
||||
premiumSeparatorLeft.removeFromSuperlayer()
|
||||
@ -581,9 +573,55 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
self.premiumSeparatorText = nil
|
||||
premiumSeparatorText.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
contentHeight += 14.0
|
||||
}
|
||||
} else {
|
||||
if let premiumTitle = self.premiumTitle {
|
||||
self.premiumTitle = nil
|
||||
premiumTitle.view?.removeFromSuperview()
|
||||
}
|
||||
if let premiumText = self.premiumText {
|
||||
self.premiumText = nil
|
||||
premiumText.view?.removeFromSuperview()
|
||||
}
|
||||
if let premiumButton = self.premiumButton {
|
||||
self.premiumButton = nil
|
||||
premiumButton.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
|
||||
var initialContentHeight = contentHeight
|
||||
let clippingY: CGFloat
|
||||
|
||||
if hasInviteLink {
|
||||
let title: ComponentView<Empty>
|
||||
if let current = self.title {
|
||||
title = current
|
||||
} else {
|
||||
title = ComponentView()
|
||||
self.title = title
|
||||
}
|
||||
|
||||
let descriptionText: ComponentView<Empty>
|
||||
if let current = self.descriptionText {
|
||||
descriptionText = current
|
||||
} else {
|
||||
descriptionText = ComponentView()
|
||||
self.descriptionText = descriptionText
|
||||
}
|
||||
|
||||
let actionButton: ComponentView<Empty>
|
||||
if let current = self.actionButton {
|
||||
actionButton = current
|
||||
} else {
|
||||
actionButton = ComponentView()
|
||||
self.actionButton = actionButton
|
||||
}
|
||||
|
||||
let titleSize = title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.link != nil ? environment.strings.SendInviteLink_InviteTitle : environment.strings.SendInviteLink_LinkUnavailableTitle, font: Font.semibold(24.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
@ -592,7 +630,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if let titleView = title.view {
|
||||
if titleView.superview == nil {
|
||||
self.scrollContentView.addSubview(titleView)
|
||||
}
|
||||
@ -633,7 +671,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.itemPrimaryTextColor)
|
||||
|
||||
let descriptionTextSize = self.descriptionText.update(
|
||||
let descriptionTextSize = descriptionText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(text: text, attributes: MarkdownAttributes(
|
||||
@ -649,7 +687,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 16.0 * 2.0, height: 1000.0)
|
||||
)
|
||||
let descriptionTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionTextSize.width) * 0.5), y: contentHeight), size: descriptionTextSize)
|
||||
if let descriptionTextView = self.descriptionText.view {
|
||||
if let descriptionTextView = descriptionText.view {
|
||||
if descriptionTextView.superview == nil {
|
||||
self.scrollContentView.addSubview(descriptionTextView)
|
||||
}
|
||||
@ -733,7 +771,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
}
|
||||
transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight)))
|
||||
|
||||
var initialContentHeight = contentHeight
|
||||
initialContentHeight += singleItemHeight + 16.0
|
||||
initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5))
|
||||
|
||||
contentHeight += itemsHeight
|
||||
@ -746,7 +784,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
} else {
|
||||
actionButtonTitle = environment.strings.SendInviteLink_ActionClose
|
||||
}
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
let actionButtonSize = actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(SolidRoundedButtonComponent(
|
||||
title: actionButtonTitle,
|
||||
@ -793,7 +831,7 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
)
|
||||
let bottomPanelHeight = 15.0 + environment.safeInsets.bottom + actionButtonSize.height
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
if let actionButtonView = actionButton.view {
|
||||
if actionButtonView.superview == nil {
|
||||
self.addSubview(actionButtonView)
|
||||
}
|
||||
@ -803,7 +841,26 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
contentHeight += bottomPanelHeight
|
||||
initialContentHeight += bottomPanelHeight
|
||||
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
clippingY = actionButtonFrame.minY - 24.0
|
||||
} else {
|
||||
if let title = self.title {
|
||||
self.title = nil
|
||||
title.view?.removeFromSuperview()
|
||||
}
|
||||
if let descriptionText = self.descriptionText {
|
||||
self.descriptionText = nil
|
||||
descriptionText.view?.removeFromSuperview()
|
||||
}
|
||||
if let actionButton = self.actionButton {
|
||||
self.actionButton = nil
|
||||
actionButton.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
initialContentHeight += environment.safeInsets.bottom
|
||||
|
||||
clippingY = availableSize.height
|
||||
}
|
||||
|
||||
let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight)
|
||||
|
||||
let scrollContentHeight = max(topInset + contentHeight, availableSize.height - containerInset)
|
||||
@ -817,12 +874,12 @@ private final class SendInviteLinkScreenComponent: Component {
|
||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: actionButtonFrame.minY - 24.0 - (containerInset)))
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: clippingY - containerInset))
|
||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
||||
|
||||
self.ignoreScrolling = true
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height - containerInset)))
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||
let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight)
|
||||
if contentSize != self.scrollView.contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
|
@ -1356,7 +1356,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
sideInset: 0.0,
|
||||
title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||
peer: peer.peer,
|
||||
subtitle: peer.isContact ? environment.strings.ChatList_PeerTypeContact : environment.strings.ChatList_PeerTypeNonContact,
|
||||
subtitle: peer.isContact ? environment.strings.ChatList_PeerTypeContact : environment.strings.ChatList_PeerTypeNonContactUser,
|
||||
subtitleAccessory: .none,
|
||||
presence: nil,
|
||||
selectionState: .none,
|
||||
|
@ -0,0 +1,10 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import ListSectionComponent
|
||||
|
@ -120,6 +120,7 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
private let nameSection = ComponentView<Empty>()
|
||||
private let accessSection = ComponentView<Empty>()
|
||||
private let excludedSection = ComponentView<Empty>()
|
||||
private let excludedUsersSection = ComponentView<Empty>()
|
||||
private let permissionsSection = ComponentView<Empty>()
|
||||
|
||||
private var isUpdating: Bool = false
|
||||
@ -299,7 +300,7 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func openAdditionalPeerListSetup() {
|
||||
private func openAdditionalPeerListSetup(isExclude: Bool) {
|
||||
guard let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
@ -311,7 +312,12 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
case nonContacts
|
||||
}
|
||||
|
||||
let additionalCategories: [ChatListNodeAdditionalCategory] = [
|
||||
let additionalCategories: [ChatListNodeAdditionalCategory]
|
||||
var selectedCategories = Set<Int>()
|
||||
if isExclude {
|
||||
additionalCategories = []
|
||||
} else {
|
||||
additionalCategories = [
|
||||
ChatListNodeAdditionalCategory(
|
||||
id: self.hasAccessToAllChatsByDefault ? AdditionalCategoryId.existingChats.rawValue : AdditionalCategoryId.newChats.rawValue,
|
||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: self.hasAccessToAllChatsByDefault ? "Chat List/Filters/Chats" : "Chat List/Filters/NewChats"), color: .white), cornerRadius: 12.0, color: .purple),
|
||||
@ -331,7 +337,8 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
title: environment.strings.BusinessMessageSetup_Recipients_CategoryNonContacts
|
||||
)
|
||||
]
|
||||
var selectedCategories = Set<Int>()
|
||||
}
|
||||
if !isExclude {
|
||||
for category in self.additionalPeerList.categories {
|
||||
switch category {
|
||||
case .existingChats:
|
||||
@ -344,11 +351,12 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
selectedCategories.insert(AdditionalCategoryId.nonContacts.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controller = component.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: component.context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection(
|
||||
title: self.hasAccessToAllChatsByDefault ? environment.strings.BusinessMessageSetup_Recipients_ExcludeSearchTitle : environment.strings.BusinessMessageSetup_Recipients_IncludeSearchTitle,
|
||||
title: (self.hasAccessToAllChatsByDefault || isExclude) ? environment.strings.BusinessMessageSetup_Recipients_ExcludeSearchTitle : environment.strings.BusinessMessageSetup_Recipients_IncludeSearchTitle,
|
||||
searchPlaceholder: environment.strings.ChatListFilter_AddChatsSearchPlaceholder,
|
||||
selectedChats: Set(self.additionalPeerList.peers.map(\.peer.id)),
|
||||
selectedChats: isExclude ? Set(self.additionalPeerList.excludePeers.map(\.peer.id)) : Set(self.additionalPeerList.peers.map(\.peer.id)),
|
||||
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
|
||||
chatListFilters: nil,
|
||||
onlyUsers: true
|
||||
@ -386,6 +394,7 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
if !isExclude {
|
||||
let mappedCategories = additionalCategoryIds.compactMap { item -> AdditionalPeerList.Category? in
|
||||
switch item {
|
||||
case AdditionalCategoryId.existingChats.rawValue:
|
||||
@ -416,6 +425,28 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
self.additionalPeerList.peers.sort(by: { lhs, rhs in
|
||||
return lhs.peer.debugDisplayTitle < rhs.peer.debugDisplayTitle
|
||||
})
|
||||
|
||||
let includedIds = self.additionalPeerList.peers.map(\.peer.id)
|
||||
self.additionalPeerList.excludePeers.removeAll(where: { includedIds.contains($0.peer.id) })
|
||||
} else {
|
||||
self.additionalPeerList.excludePeers.removeAll()
|
||||
for id in peerIds {
|
||||
guard let maybePeer = peerMap[id], let peer = maybePeer else {
|
||||
continue
|
||||
}
|
||||
self.additionalPeerList.excludePeers.append(AdditionalPeerList.Peer(
|
||||
peer: peer,
|
||||
isContact: isContactMap[id] ?? false
|
||||
))
|
||||
}
|
||||
self.additionalPeerList.excludePeers.sort(by: { lhs, rhs in
|
||||
return lhs.peer.debugDisplayTitle < rhs.peer.debugDisplayTitle
|
||||
})
|
||||
|
||||
let excludedIds = self.additionalPeerList.excludePeers.map(\.peer.id)
|
||||
self.additionalPeerList.peers.removeAll(where: { excludedIds.contains($0.peer.id) })
|
||||
}
|
||||
|
||||
self.state?.updated(transition: .immediate)
|
||||
|
||||
controller?.dismiss()
|
||||
@ -494,7 +525,7 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
let navigationTitleSize = self.navigationTitle.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: environment.strings.ChatbotSetup_Title, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
|
||||
text: .plain(NSAttributedString(string: environment.strings.ChatbotSetup_TitleItem, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
|
||||
horizontalAlignment: .center
|
||||
)),
|
||||
environment: {},
|
||||
@ -795,7 +826,7 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openAdditionalPeerListSetup()
|
||||
self.openAdditionalPeerListSetup(isExclude: false)
|
||||
}
|
||||
))))
|
||||
for category in self.additionalPeerList.categories.sorted(by: { $0.rawValue < $1.rawValue }) {
|
||||
@ -865,7 +896,7 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
sideInset: 0.0,
|
||||
title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||
peer: peer.peer,
|
||||
subtitle: peer.isContact ? environment.strings.ChatList_PeerTypeContact : environment.strings.ChatList_PeerTypeNonContact,
|
||||
subtitle: peer.isContact ? environment.strings.ChatList_PeerTypeContact : environment.strings.ChatList_PeerTypeNonContactUser,
|
||||
subtitleAccessory: .none,
|
||||
presence: nil,
|
||||
selectionState: .none,
|
||||
@ -930,6 +961,103 @@ final class ChatbotSetupScreenComponent: Component {
|
||||
contentHeight += excludedSectionSize.height
|
||||
contentHeight += sectionSpacing
|
||||
|
||||
//TODO:localize
|
||||
var excludedUsersContentHeight: CGFloat = 0.0
|
||||
var excludedUsersSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
excludedUsersSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: environment.strings.BusinessMessageSetup_Recipients_AddExclude,
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: environment.theme.list.itemAccentColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
leftIcon: AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent(
|
||||
name: "Chat List/AddIcon",
|
||||
tintColor: environment.theme.list.itemAccentColor
|
||||
))),
|
||||
accessory: nil,
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openAdditionalPeerListSetup(isExclude: true)
|
||||
}
|
||||
))))
|
||||
for peer in self.additionalPeerList.excludePeers {
|
||||
excludedUsersSectionItems.append(AnyComponentWithIdentity(id: peer.peer.id, component: AnyComponent(PeerListItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
style: .generic,
|
||||
sideInset: 0.0,
|
||||
title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
|
||||
peer: peer.peer,
|
||||
subtitle: peer.isContact ? environment.strings.ChatList_PeerTypeContact : environment.strings.ChatList_PeerTypeNonContactUser,
|
||||
subtitleAccessory: .none,
|
||||
presence: nil,
|
||||
selectionState: .none,
|
||||
hasNext: false,
|
||||
action: { peer, _, _ in
|
||||
},
|
||||
inlineActions: PeerListItemComponent.InlineActionsState(
|
||||
actions: [PeerListItemComponent.InlineAction(
|
||||
id: AnyHashable(0),
|
||||
title: environment.strings.Common_Delete,
|
||||
color: .destructive,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.additionalPeerList.excludePeers.removeAll(where: { $0.peer.id == peer.peer.id })
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
)]
|
||||
)
|
||||
))))
|
||||
}
|
||||
let excludedUsersSectionSize = self.excludedUsersSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: nil,
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(
|
||||
text: environment.strings.ChatbotSetup_Recipients_ExcludedSectionFooter,
|
||||
attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.itemAccentColor),
|
||||
linkAttribute: { _ in
|
||||
return nil
|
||||
}
|
||||
)
|
||||
),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
items: excludedUsersSectionItems
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
let excludedUsersSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + excludedUsersContentHeight), size: excludedSectionSize)
|
||||
if let excludedUsersSectionView = self.excludedUsersSection.view {
|
||||
if excludedUsersSectionView.superview == nil {
|
||||
self.scrollView.addSubview(excludedUsersSectionView)
|
||||
}
|
||||
transition.setFrame(view: excludedUsersSectionView, frame: excludedUsersSectionFrame)
|
||||
transition.setAlpha(view: excludedUsersSectionView, alpha: !self.hasAccessToAllChatsByDefault ? 1.0 : 0.0)
|
||||
}
|
||||
excludedUsersContentHeight += excludedUsersSectionSize.height
|
||||
excludedUsersContentHeight += sectionSpacing
|
||||
if !self.hasAccessToAllChatsByDefault {
|
||||
contentHeight += excludedUsersContentHeight
|
||||
}
|
||||
|
||||
let permissionsSectionSize = self.permissionsSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
@ -1107,6 +1235,7 @@ public final class ChatbotSetupScreen: ViewControllerComponentContainer {
|
||||
|
||||
var additionalPeerIds = Set<EnginePeer.Id>()
|
||||
additionalPeerIds.formUnion(connectedBot.recipients.additionalPeers)
|
||||
additionalPeerIds.formUnion(connectedBot.recipients.excludePeers)
|
||||
|
||||
return context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: connectedBot.id),
|
||||
|
@ -170,10 +170,16 @@ private final class ChatManagingBotTitlePanelComponent: Component {
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
)
|
||||
//TODO:localize
|
||||
let textValue: String
|
||||
if component.isPaused {
|
||||
textValue = "bot paused"
|
||||
} else {
|
||||
textValue = component.managesChat ? "bot manages this chat" : "bot has access to this chat"
|
||||
}
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.managesChat ? "bot manages this chat" : "bot has access to this chat", font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor))
|
||||
text: .plain(NSAttributedString(string: textValue, font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
@ -400,7 +406,7 @@ final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
strings: interfaceState.strings,
|
||||
insets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: 0.0, right: rightInset),
|
||||
peer: managingBot.bot,
|
||||
managesChat: managingBot.canReply,
|
||||
managesChat: managingBot.canReply || managingBot.isPaused,
|
||||
isPaused: managingBot.isPaused,
|
||||
toggleIsPaused: { [weak self] in
|
||||
guard let self else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user