[WIP] Topics

This commit is contained in:
Ali 2022-10-20 20:57:11 +04:00
parent 9a013c85c9
commit 99396ec7d4
17 changed files with 191 additions and 44 deletions

View File

@ -509,12 +509,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
var canManage: Bool = false
if channel.hasPermission(.pinMessages) { if channel.hasPermission(.pinMessages) {
canManage = true
}
if canManage {
//TODO:localize //TODO:localize
items.append(.action(ContextMenuActionItem(text: isPinned ? "Unpin" : "Pin", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: isPinned ? "Unpin" : "Pin", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default) f(.default)
@ -714,7 +709,15 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
} }
}))) })))
if canManage || threadData.isOwnedByMe { var canManage = false
if channel.flags.contains(.isCreator) {
canManage = true
} else if channel.adminRights != nil {
canManage = true
} else if threadData.isOwnedByMe {
canManage = true
}
if canManage {
//TODO:localize //TODO:localize
items.append(.action(ContextMenuActionItem(text: threadData.isClosed ? "Restart" : "Close", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: threadData.isClosed ? "Chat/Context Menu/Play": "Chat/Context Menu/Pause"), color: theme.contextMenu.primaryColor) }, action: { _, f in items.append(.action(ContextMenuActionItem(text: threadData.isClosed ? "Restart" : "Close", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: threadData.isClosed ? "Chat/Context Menu/Play": "Chat/Context Menu/Pause"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default) f(.default)

View File

@ -1590,10 +1590,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create) let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create)
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
controller.completion = { title, fileId in controller.completion = { [weak controller] title, fileId in
controller?.isInProgress = true
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)
|> deliverOnMainQueue).start(next: { topicId in |> deliverOnMainQueue).start(next: { topicId in
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start()
}, error: { _ in
controller?.isInProgress = false
}) })
} }
strongSelf.push(controller) strongSelf.push(controller)
@ -2631,12 +2635,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create) let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create)
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
controller.completion = { title, fileId in
controller.completion = { [weak controller] title, fileId in
controller?.isInProgress = true
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)
|> deliverOnMainQueue).start(next: { topicId in |> deliverOnMainQueue).start(next: { topicId in
if let navigationController = (sourceController.navigationController as? NavigationController) { if let navigationController = (sourceController.navigationController as? NavigationController) {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start()
} }
}, error: { _ in
controller?.isInProgress = false
}) })
} }
sourceController.push(controller) sourceController.push(controller)
@ -3709,6 +3718,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
//TODO:localize //TODO:localize
items.append(ActionSheetTextItem(title: "This will delete the topic with all its messages", parseMarkdown: true))
items.append(ActionSheetButtonItem(title: "Delete", color: .destructive, action: { [weak self, weak actionSheet] in items.append(ActionSheetButtonItem(title: "Delete", color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
self?.commitDeletePeerThread(peerId: peerId, threadId: threadId) self?.commitDeletePeerThread(peerId: peerId, threadId: threadId)

View File

@ -330,10 +330,10 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr
return options return options
} }
private func forumRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isClosed: Bool, isPinned: Bool, isEditing: Bool, canManage: Bool) -> [ItemListRevealOption] { private func forumRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isClosed: Bool, isPinned: Bool, isEditing: Bool, canPin: Bool, canManage: Bool) -> [ItemListRevealOption] {
var options: [ItemListRevealOption] = [] var options: [ItemListRevealOption] = []
if !isEditing { if !isEditing {
if canManage { if canPin {
if isPinned { if isPinned {
options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
} else { } else {
@ -1852,16 +1852,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if case .forum = item.chatListLocation { if case .forum = item.chatListLocation {
if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer { if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer {
var canManage = false var canManage = false
if channel.hasPermission(.pinMessages) { if channel.flags.contains(.isCreator) {
canManage = true
} else if channel.adminRights != nil {
canManage = true
} else if let threadInfo = threadInfo, threadInfo.isOwner {
canManage = true canManage = true
} else if let threadInfo {
canManage = threadInfo.isOwner
} }
var isClosed = false var isClosed = false
if let threadInfo { if let threadInfo {
isClosed = threadInfo.isClosed isClosed = threadInfo.isClosed
} }
peerRevealOptions = forumRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isPinned: isPinned, isEditing: item.editing, canManage: canManage) peerRevealOptions = forumRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isPinned: isPinned, isEditing: item.editing, canPin: channel.hasPermission(.pinMessages), canManage: canManage)
peerLeftRevealOptions = [] peerLeftRevealOptions = []
} else { } else {
peerRevealOptions = [] peerRevealOptions = []

View File

@ -270,7 +270,12 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
} }
} }
default: default:
hideAuthor = true switch action.action {
case .topicCreated, .topicEdited:
hideAuthor = false
default:
hideAuthor = true
}
if let (text, textSpoilers, customEmojiRangesValue) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { if let (text, textSpoilers, customEmojiRangesValue) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) {
messageText = text messageText = text
spoilers = textSpoilers spoilers = textSpoilers

View File

@ -226,7 +226,7 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title:
} }
if let topicId = topicId { if let topicId = topicId {
return resolveForumThreads(postbox: account.postbox, network: account.network, ids: []) return resolveForumThreads(postbox: account.postbox, network: account.network, ids: [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: topicId))])
|> castError(CreateForumChannelTopicError.self) |> castError(CreateForumChannelTopicError.self)
|> map { _ -> Int64 in |> map { _ -> Int64 in
return topicId return topicId

View File

@ -31,6 +31,7 @@ swift_library(
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
"//submodules/Components/PagerComponent:PagerComponent", "//submodules/Components/PagerComponent:PagerComponent",
"//submodules/PremiumUI", "//submodules/PremiumUI",
"//submodules/ProgressNavigationButtonNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -15,6 +15,7 @@ import MultilineTextComponent
import EmojiStatusComponent import EmojiStatusComponent
import Postbox import Postbox
import PremiumUI import PremiumUI
import ProgressNavigationButtonNode
private final class TitleFieldComponent: Component { private final class TitleFieldComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty
@ -162,6 +163,8 @@ private final class TitleFieldComponent: Component {
placeholderComponentView.frame = CGRect(origin: CGPoint(x: 62.0, y: floorToScreenPixels((availableSize.height - placeholderSize.height) / 2.0) + 1.0 - UIScreenPixel), size: placeholderSize) placeholderComponentView.frame = CGRect(origin: CGPoint(x: 62.0, y: floorToScreenPixels((availableSize.height - placeholderSize.height) / 2.0) + 1.0 - UIScreenPixel), size: placeholderSize)
} }
self.placeholderView.view?.isHidden = !component.text.isEmpty
let iconSize = self.iconView.update( let iconSize = self.iconView.update(
transition: .easeInOut(duration: 0.2), transition: .easeInOut(duration: 0.2),
component: AnyComponent(EmojiStatusComponent( component: AnyComponent(EmojiStatusComponent(
@ -782,10 +785,32 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
case edit(topic: EngineMessageHistoryThread.Info) case edit(topic: EngineMessageHistoryThread.Info)
} }
private let context: AccountContext
private let mode: Mode
private var doneBarItem: UIBarButtonItem?
private var state: (String, Int64?) = ("", nil) private var state: (String, Int64?) = ("", nil)
public var completion: (String, Int64?) -> Void = { _, _ in } public var completion: (String, Int64?) -> Void = { _, _ in }
public var isInProgress: Bool = false {
didSet {
if self.isInProgress != oldValue {
if self.isInProgress {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: presentationData.theme.rootController.navigationBar.accentTextColor))
} else {
//TODO:localize
self.navigationItem.rightBarButtonItem = self.doneBarItem
}
}
}
}
public init(context: AccountContext, peerId: EnginePeer.Id, mode: ForumCreateTopicScreen.Mode) { public init(context: AccountContext, peerId: EnginePeer.Id, mode: ForumCreateTopicScreen.Mode) {
self.context = context
self.mode = mode
var titleUpdatedImpl: ((String) -> Void)? var titleUpdatedImpl: ((String) -> Void)?
var iconUpdatedImpl: ((Int64?) -> Void)? var iconUpdatedImpl: ((Int64?) -> Void)?
var openPremiumImpl: (() -> Void)? var openPremiumImpl: (() -> Void)?
@ -802,12 +827,14 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
let title: String let title: String
let doneTitle: String let doneTitle: String
switch mode { switch mode {
case .create: case .create:
title = "New Topic" title = "New Topic"
doneTitle = "Create" doneTitle = "Create"
case .edit: case let .edit(topic):
title = "Edit Topic" title = "Edit Topic"
doneTitle = "Done" doneTitle = "Done"
self.state = (topic.title, topic.icon)
} }
self.title = title self.title = title
@ -815,20 +842,21 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: doneTitle, style: .done, target: self, action: #selector(self.createPressed)) self.doneBarItem = UIBarButtonItem(title: doneTitle, style: .done, target: self, action: #selector(self.createPressed))
self.navigationItem.rightBarButtonItem?.isEnabled = false self.navigationItem.rightBarButtonItem = self.doneBarItem
self.doneBarItem?.isEnabled = false
if case .edit = mode { if case .edit = mode {
self.navigationItem.rightBarButtonItem?.isEnabled = true self.doneBarItem?.isEnabled = true
} }
titleUpdatedImpl = { [weak self] title in titleUpdatedImpl = { [weak self] title in
guard let strongSelf = self else { guard let self else {
return return
} }
strongSelf.navigationItem.rightBarButtonItem?.isEnabled = !title.isEmpty self.doneBarItem?.isEnabled = !title.isEmpty
strongSelf.state = (title, strongSelf.state.1) self.state = (title, self.state.1)
} }
iconUpdatedImpl = { [weak self] fileId in iconUpdatedImpl = { [weak self] fileId in

View File

@ -3038,7 +3038,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
case let .replyThread(replyThreadMessage): case let .replyThread(replyThreadMessage):
let peerId = replyThreadMessage.messageId.peerId let peerId = replyThreadMessage.messageId.peerId
strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, forceInCurrentChat: true, animated: true, completion: nil)
case .feed: case .feed:
//TODO:implement //TODO:implement
break break

View File

@ -262,7 +262,9 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
if let threadData = chatPresentationInterfaceState.threadData { if let threadData = chatPresentationInterfaceState.threadData {
if threadData.isClosed { if threadData.isClosed {
var canManage = false var canManage = false
if channel.hasPermission(.pinMessages) { if channel.flags.contains(.isCreator) {
canManage = true
} else if channel.adminRights != nil {
canManage = true canManage = true
} else if threadData.isOwn { } else if threadData.isOwn {
canManage = true canManage = true

View File

@ -162,7 +162,9 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
if let threadData = chatPresentationInterfaceState.threadData { if let threadData = chatPresentationInterfaceState.threadData {
if threadData.isClosed { if threadData.isClosed {
var canManage = false var canManage = false
if channel.hasPermission(.pinMessages) { if channel.flags.contains(.isCreator) {
canManage = true
} else if channel.adminRights != nil {
canManage = true canManage = true
} else if threadData.isOwn { } else if threadData.isOwn {
canManage = true canManage = true

View File

@ -61,7 +61,9 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
if let threadData = chatPresentationInterfaceState.threadData { if let threadData = chatPresentationInterfaceState.threadData {
if threadData.isClosed { if threadData.isClosed {
var canManage = false var canManage = false
if channel.hasPermission(.pinMessages) { if channel.flags.contains(.isCreator) {
canManage = true
} else if channel.adminRights != nil {
canManage = true canManage = true
} else if threadData.isOwn { } else if threadData.isOwn {
canManage = true canManage = true

View File

@ -102,7 +102,9 @@ private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReport
if let threadData = state.threadData { if let threadData = state.threadData {
if threadData.isClosed { if threadData.isClosed {
var canManage = false var canManage = false
if channel.hasPermission(.pinMessages) { if channel.flags.contains(.isCreator) {
canManage = true
} else if channel.adminRights != nil {
canManage = true canManage = true
} else if threadData.isOwn { } else if threadData.isOwn {
canManage = true canManage = true
@ -597,7 +599,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
if let renderedPeer = interfaceState.renderedPeer { if let renderedPeer = interfaceState.renderedPeer {
chatPeer = renderedPeer.peers[renderedPeer.peerId] chatPeer = renderedPeer.peers[renderedPeer.peerId]
} }
if let chatPeer = chatPeer, let invitedBy = interfaceState.contactStatus?.invitedBy { if let chatPeer = chatPeer, (updatedButtons.contains(.block) || updatedButtons.contains(.reportSpam) || updatedButtons.contains(.reportUserSpam)), let invitedBy = interfaceState.contactStatus?.invitedBy {
var inviteInfoTransition = transition var inviteInfoTransition = transition
let inviteInfoNode: ChatInfoTitlePanelInviteInfoNode let inviteInfoNode: ChatInfoTitlePanelInviteInfoNode
if let current = self.inviteInfoNode { if let current = self.inviteInfoNode {

View File

@ -1189,7 +1189,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
return result return result
} }
func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool { func peerInfoCanEdit(peer: Peer?, threadData: MessageHistoryThreadData?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool {
if let user = peer as? TelegramUser { if let user = peer as? TelegramUser {
if user.isDeleted { if user.isDeleted {
return false return false
@ -1199,14 +1199,26 @@ func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?, isContact: Bool?)
} }
return true return true
} else if let peer = peer as? TelegramChannel { } else if let peer = peer as? TelegramChannel {
if peer.flags.contains(.isCreator) { if peer.flags.contains(.isForum) {
return true if peer.flags.contains(.isCreator) {
} else if peer.hasPermission(.changeInfo) { return true
return true } else if let threadData = threadData, threadData.isOwnedByMe {
} else if let _ = peer.adminRights { return true
return true } else if let _ = peer.adminRights {
return true
} else {
return false
}
} else {
if peer.flags.contains(.isCreator) {
return true
} else if peer.hasPermission(.changeInfo) {
return true
} else if let _ = peer.adminRights {
return true
}
return false
} }
return false
} else if let peer = peer as? TelegramGroup { } else if let peer = peer as? TelegramGroup {
if case .creator = peer.role { if case .creator = peer.role {
return true return true

View File

@ -8183,7 +8183,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false)) leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
} else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { } else if peerInfoCanEdit(peer: self.data?.peer, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
} }
if self.state.selectedMessageIds == nil { if self.state.selectedMessageIds == nil {
@ -9602,6 +9602,31 @@ func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (in
contactsController?.dismiss() contactsController?.dismiss()
}, completed: { }, completed: {
contactsController?.dismiss() contactsController?.dismiss()
let mappedPeerIds: [EnginePeer.Id] = peers.compactMap { peer -> EnginePeer.Id? in
switch peer {
case let .peer(id):
return id
default:
return nil
}
}
if !mappedPeerIds.isEmpty {
let _ = (context.engine.data.get(EngineDataMap(mappedPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))))
|> deliverOnMainQueue).start(next: { maybePeers in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let peers = maybePeers.compactMap { $0.value }
//TODO:localize
let text: String
if peers.count == 1 {
text = "**\(peers[0].displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** added to the group."
} else {
text = "**\(peers.count)** members added to the group."
}
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: context, peers: peers, title: nil, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})
}
})) }))
})) }))
contactsController.dismissed = { contactsController.dismissed = {

View File

@ -27,6 +27,7 @@ swift_library(
"//submodules/AvatarNode:AvatarNode", "//submodules/AvatarNode:AvatarNode",
"//submodules/AccountContext:AccountContext", "//submodules/AccountContext:AccountContext",
"//submodules/ComponentFlow:ComponentFlow", "//submodules/ComponentFlow:ComponentFlow",
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -42,6 +42,7 @@ public enum UndoOverlayContent {
case image(image: UIImage, text: String) case image(image: UIImage, text: String)
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?) case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?) case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?)
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
} }
public enum UndoOverlayAction { public enum UndoOverlayAction {

View File

@ -17,6 +17,7 @@ import AnimationUI
import StickerResources import StickerResources
import AvatarNode import AvatarNode
import AccountContext import AccountContext
import AnimatedAvatarSetNode
final class UndoOverlayControllerNode: ViewControllerTracingNode { final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let elevatedLayout: Bool private let elevatedLayout: Bool
@ -25,6 +26,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let timerTextNode: ImmediateTextNode private let timerTextNode: ImmediateTextNode
private let avatarNode: AvatarNode? private let avatarNode: AvatarNode?
private let iconNode: ASImageNode? private let iconNode: ASImageNode?
private var multiAvatarsNode: AnimatedAvatarSetNode?
private var multiAvatarsSize: CGSize?
private var iconImageSize: CGSize? private var iconImageSize: CGSize?
private let iconCheckNode: RadialStatusNode? private let iconCheckNode: RadialStatusNode?
private let animationNode: AnimationNode? private let animationNode: AnimationNode?
@ -872,6 +875,44 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = true displayUndo = true
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
case let .peers(context, peers, title, text, customUndoText):
self.avatarNode = nil
let multiAvatarsNode = AnimatedAvatarSetNode()
self.multiAvatarsNode = multiAvatarsNode
let avatarsContext = AnimatedAvatarSetContext()
self.multiAvatarsSize = multiAvatarsNode.update(context: context, content: avatarsContext.update(peers: peers, animated: false), itemSize: CGSize(width: 28.0, height: 28.0), animated: false, synchronousLoad: false)
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
if text.contains("](") {
isUserInteractionEnabled = true
}
self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
self.textNode.maximumNumberOfLines = 5
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
} }
self.remainingSeconds = self.originalRemainingSeconds self.remainingSeconds = self.originalRemainingSeconds
@ -900,7 +941,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content { switch content {
case .removedChat: case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode) self.panelWrapperNode.addSubnode(self.timerTextNode)
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal: case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .peers:
if self.textNode.tapAttributeAction != nil || displayUndo { if self.textNode.tapAttributeAction != nil || displayUndo {
self.isUserInteractionEnabled = true self.isUserInteractionEnabled = true
} else { } else {
@ -927,6 +968,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.animationNode?.isUserInteractionEnabled = false self.animationNode?.isUserInteractionEnabled = false
self.iconCheckNode?.isUserInteractionEnabled = false self.iconCheckNode?.isUserInteractionEnabled = false
self.avatarNode?.isUserInteractionEnabled = false self.avatarNode?.isUserInteractionEnabled = false
self.multiAvatarsNode?.isUserInteractionEnabled = false
self.slotMachineNode?.isUserInteractionEnabled = false self.slotMachineNode?.isUserInteractionEnabled = false
self.animatedStickerNode?.isUserInteractionEnabled = false self.animatedStickerNode?.isUserInteractionEnabled = false
@ -938,6 +980,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode) self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode) self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
self.avatarNode.flatMap(self.panelWrapperNode.addSubnode) self.avatarNode.flatMap(self.panelWrapperNode.addSubnode)
self.multiAvatarsNode.flatMap(self.panelWrapperNode.addSubnode)
self.panelWrapperNode.addSubnode(self.buttonNode) self.panelWrapperNode.addSubnode(self.buttonNode)
self.panelWrapperNode.addSubnode(self.titleNode) self.panelWrapperNode.addSubnode(self.titleNode)
self.panelWrapperNode.addSubnode(self.textNode) self.panelWrapperNode.addSubnode(self.textNode)
@ -1088,7 +1131,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
if iconSize.width > leftInset { if iconSize.width > leftInset {
leftInset = iconSize.width - 8.0 leftInset = iconSize.width - 8.0
} }
} else if let multiAvatarsSize = self.multiAvatarsSize {
leftInset = 13.0 + multiAvatarsSize.width + 20.0
} }
let rightInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0
var contentHeight: CGFloat = 20.0 var contentHeight: CGFloat = 20.0
@ -1228,6 +1274,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let avatarSize: CGFloat = 30.0 let avatarSize: CGFloat = 30.0
transition.updateFrame(node: avatarNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - avatarSize) / 2.0), y: floor((contentHeight - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))) transition.updateFrame(node: avatarNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - avatarSize) / 2.0), y: floor((contentHeight - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)))
} }
if let multiAvatarsNode = self.multiAvatarsNode, let multiAvatarsSize = self.multiAvatarsSize {
let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: floor((contentHeight - multiAvatarsSize.height) / 2.0) + verticalOffset), size: multiAvatarsSize)
transition.updateFrame(node: multiAvatarsNode, frame: avatarsFrame)
}
} }
func animateIn(asReplacement: Bool) { func animateIn(asReplacement: Bool) {