Temp: reaction improvements

This commit is contained in:
Ali 2021-12-10 19:34:57 +04:00
parent ec675606ee
commit 4a8f28b866
55 changed files with 1383 additions and 251 deletions

View File

@ -312,7 +312,7 @@ public final class AnimatedAvatarSetView: UIView {
self.unclippedView = UIImageView()
self.clippedView = UIImageView()
super.init()
super.init(frame: CGRect())
self.addSubview(self.unclippedView)
self.addSubview(self.clippedView)
@ -397,7 +397,7 @@ public final class AnimatedAvatarSetView: UIView {
private var contentViews: [AnimatedAvatarSetContext.Content.Item.Key: ContentView] = [:]
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, itemSize: CGSize = CGSize(width: 30.0, height: 30.0), customSpacing: CGFloat? = nil, animated: Bool, synchronousLoad: Bool) -> CGSize {
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, itemSize: CGSize = CGSize(width: 30.0, height: 30.0), customSpacing: CGFloat? = nil, animation: ListViewItemUpdateAnimation, synchronousLoad: Bool) -> CGSize {
var contentWidth: CGFloat = 0.0
let contentHeight: CGFloat = itemSize.height
@ -408,13 +408,6 @@ public final class AnimatedAvatarSetView: UIView {
spacing = 10.0
}
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.2, curve: .easeInOut)
} else {
transition = .immediate
}
var validKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
var index = 0
for i in 0 ..< content.items.count {
@ -427,15 +420,15 @@ public final class AnimatedAvatarSetView: UIView {
let itemView: ContentView
if let current = self.contentViews[key] {
itemView = current
itemView.updateLayout(size: itemSize, isClipped: index != 0, animated: animated)
transition.updateFrame(layer: itemView.layer, frame: itemFrame)
itemView.updateLayout(size: itemSize, isClipped: index != 0, animated: animation.isAnimated)
animation.animator.updateFrame(layer: itemView.layer, frame: itemFrame, completion: nil)
} else {
itemView = ContentView(context: context, peer: item.peer, placeholderColor: item.placeholderColor, synchronousLoad: synchronousLoad, size: itemSize, spacing: spacing)
self.addSubview(itemView)
self.contentViews[key] = itemView
itemView.updateLayout(size: itemSize, isClipped: index != 0, animated: false)
itemView.frame = itemFrame
if animated {
if animation.isAnimated {
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
itemView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
}
@ -454,10 +447,14 @@ public final class AnimatedAvatarSetView: UIView {
guard let itemView = self.contentViews.removeValue(forKey: key) else {
continue
}
if animation.isAnimated {
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak itemView] _ in
itemView?.removeFromSuperview()
})
itemView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
} else {
itemView.removeFromSuperview()
}
}
return CGSize(width: contentWidth, height: contentHeight)

View File

@ -366,7 +366,7 @@ public final class CallListController: TelegramBaseController {
}
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: self, sourceNode: buttonNode.contentNode, keepInPlace: false, blurBackground: false)), items: .single(ContextController.Items(items: items)), gesture: nil)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: self, sourceNode: buttonNode.contentNode, keepInPlace: false, blurBackground: false)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
self.presentInGlobalOverlay(contextController)
}
@ -482,7 +482,7 @@ public final class CallListController: TelegramBaseController {
})
})))
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(CallListTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(CallListTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
}

View File

@ -250,10 +250,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined) |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
c.setItems(.single(ContextController.Items(items: updatedItems)), minHeight: nil)
c.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil)
})))
}
}

View File

@ -840,12 +840,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
case let .groupReference(groupId, _, _, _, _):
let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId._asGroup(), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
chatListController.navigationPresentation = .master
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
case let .peer(_, peer, _, _, _, _, _, _, promoInfo, _, _, _):
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
}
@ -869,7 +869,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
@ -1096,7 +1096,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})))
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}
@ -2890,7 +2890,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListTabBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListTabBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}

View File

@ -788,7 +788,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return items
}
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, recognizer: nil, gesture: gesture)
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: items |> map { ContextController.Items(content: .list($0)) }, recognizer: nil, gesture: gesture)
self.presentInGlobalOverlay?(controller, nil)
}
@ -852,7 +852,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
switch previewData {
case let .gallery(gallery):
gallery.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay?(contextController, nil)
case .instantPage:
break

View File

@ -18,6 +18,7 @@ swift_library(
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/WebPBinding:WebPBinding",
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
],
visibility = [
"//visibility:public",

View File

@ -1,4 +1,5 @@
import Foundation
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
@ -8,6 +9,426 @@ import AccountContext
import TelegramPresentationData
import UIKit
import WebPBinding
import AnimatedAvatarSetNode
private final class NullActionClass: NSObject, CAAction {
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
}
}
private let nullAction = NullActionClass()
private final class SimpleLayer: CALayer {
override func action(forKey event: String) -> CAAction? {
return nullAction
}
override init() {
super.init()
}
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
fileprivate final class CounterLayer: CALayer {
fileprivate final class Layout {
struct Spec: Equatable {
let clippingHeight: CGFloat
var stringComponents: [String]
var backgroundColor: UInt32
var foregroundColor: UInt32
}
let spec: Spec
let size: CGSize
let image: UIImage
init(
spec: Spec,
size: CGSize,
image: UIImage
) {
self.spec = spec
self.size = size
self.image = image
}
static func calculate(spec: Spec, previousLayout: Layout?) -> Layout {
let image: UIImage
if let previousLayout = previousLayout, previousLayout.spec == spec {
image = previousLayout.image
} else {
let textColor = UIColor(argb: spec.foregroundColor)
let string = NSAttributedString(string: spec.stringComponents.joined(separator: ""), font: Font.medium(11.0), textColor: textColor)
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
image = generateImage(CGSize(width: boundingRect.size.width, height: spec.clippingHeight), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
/*context.setFillColor(UIColor(argb: spec.backgroundColor).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
if textColor.alpha < 1.0 {
context.setBlendMode(.copy)
}*/
context.translateBy(x: 0.0, y: (size.height - boundingRect.size.height) / 2.0)
UIGraphicsPushContext(context)
string.draw(at: CGPoint())
UIGraphicsPopContext()
})!
}
return Layout(
spec: spec,
size: image.size,
image: image
)
}
}
var layout: Layout?
override init(layer: Any) {
super.init(layer: layer)
}
override init() {
super.init()
self.masksToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func action(forKey event: String) -> CAAction? {
return nullAction
}
func apply(layout: Layout, animation: ListViewItemUpdateAnimation) {
/*if animation.isAnimated, let previousContents = self.contents {
self.animate(from: previousContents as! CGImage, to: layout.image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
} else {*/
self.contents = layout.image.cgImage
//}
self.layout = layout
}
}
public final class ReactionButtonAsyncView: UIButton {
fileprivate final class Layout {
struct Spec: Equatable {
var component: ReactionButtonComponent
}
let spec: Spec
let backgroundColor: UInt32
let clippingHeight: CGFloat
let sideInsets: CGFloat
let imageFrame: CGRect
let counter: CounterLayer.Layout?
let counterFrame: CGRect?
let backgroundImage: UIImage
let size: CGSize
init(
spec: Spec,
backgroundColor: UInt32,
clippingHeight: CGFloat,
sideInsets: CGFloat,
imageFrame: CGRect,
counter: CounterLayer.Layout?,
counterFrame: CGRect?,
backgroundImage: UIImage,
size: CGSize
) {
self.spec = spec
self.backgroundColor = backgroundColor
self.clippingHeight = clippingHeight
self.sideInsets = sideInsets
self.imageFrame = imageFrame
self.counter = counter
self.counterFrame = counterFrame
self.backgroundImage = backgroundImage
self.size = size
}
static func calculate(spec: Spec, currentLayout: Layout?, currentCounter: CounterLayer.Layout?) -> Layout {
let clippingHeight: CGFloat = 22.0
let sideInsets: CGFloat = 8.0
let height: CGFloat = 30.0
let spacing: CGFloat = 4.0
let defaultImageSize = CGSize(width: 22.0, height: 22.0)
let imageSize: CGSize
if let file = spec.component.reaction.iconFile {
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
} else {
imageSize = defaultImageSize
}
var counterComponents: [String] = []
for character in "\(spec.component.count)" {
counterComponents.append(String(character))
}
let backgroundColor = spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
let imageFrame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
var previousDisplayCounter: String?
if let currentLayout = currentLayout {
if currentLayout.spec.component.avatarPeers.isEmpty {
previousDisplayCounter = "\(spec.component.count)"
}
}
var currentDisplayCounter: String?
if spec.component.avatarPeers.isEmpty {
currentDisplayCounter = "\(spec.component.count)"
}
let backgroundImage: UIImage
if let currentLayout = currentLayout, currentLayout.spec.component.isSelected == spec.component.isSelected, currentLayout.spec.component.colors == spec.component.colors, previousDisplayCounter == currentDisplayCounter {
backgroundImage = currentLayout.backgroundImage
} else {
backgroundImage = generateImage(CGSize(width: height + 18.0, height: height), rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setFillColor(UIColor(argb: backgroundColor).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: height, height: height)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - height, y: 0.0), size: CGSize(width: height, height: size.height)))
context.fill(CGRect(origin: CGPoint(x: height / 2.0, y: 0.0), size: CGSize(width: size.width - height, height: size.height)))
context.setBlendMode(.normal)
if let currentDisplayCounter = currentDisplayCounter {
let textColor = UIColor(argb: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground)
let string = NSAttributedString(string: currentDisplayCounter, font: Font.medium(11.0), textColor: textColor)
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
if textColor.alpha < 1.0 {
context.setBlendMode(.copy)
}
string.draw(at: CGPoint(x: size.width - sideInsets - boundingRect.width, y: (size.height - boundingRect.height) / 2.0))
}
UIGraphicsPopContext()
})!.stretchableImage(withLeftCapWidth: Int(height / 2.0), topCapHeight: Int(height / 2.0))
}
var counter: CounterLayer.Layout?
var counterFrame: CGRect?
var size = CGSize(width: imageSize.width + sideInsets * 2.0, height: height)
if !spec.component.avatarPeers.isEmpty {
size.width += 4.0 + 24.0
if spec.component.avatarPeers.count > 1 {
size.width += CGFloat(spec.component.avatarPeers.count - 1) * 12.0
} else {
size.width -= 2.0
}
} else {
let counterSpec = CounterLayer.Layout.Spec(
clippingHeight: clippingHeight,
stringComponents: counterComponents,
backgroundColor: backgroundColor,
foregroundColor: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground
)
let counterValue: CounterLayer.Layout
if let currentCounter = currentCounter, currentCounter.spec == counterSpec {
counterValue = currentCounter
} else {
counterValue = CounterLayer.Layout.calculate(
spec: counterSpec,
previousLayout: currentCounter
)
}
counter = counterValue
size.width += spacing + counterValue.size.width
counterFrame = CGRect(origin: CGPoint(x: sideInsets + imageSize.width + spacing, y: floorToScreenPixels((height - counterValue.size.height) / 2.0)), size: counterValue.size)
}
return Layout(
spec: spec,
backgroundColor: backgroundColor,
clippingHeight: clippingHeight,
sideInsets: sideInsets,
imageFrame: imageFrame,
counter: counter,
counterFrame: counterFrame,
backgroundImage: backgroundImage,
size: size
)
}
}
private var layout: Layout?
public let iconView: UIImageView
private var counterLayer: CounterLayer?
private var avatarsView: AnimatedAvatarSetView?
private let iconImageDisposable = MetaDisposable()
override init(frame: CGRect) {
self.iconView = UIImageView()
self.iconView.isUserInteractionEnabled = false
super.init(frame: CGRect())
self.addSubview(self.iconView)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
preconditionFailure()
}
deinit {
self.iconImageDisposable.dispose()
}
@objc private func pressed() {
guard let layout = self.layout else {
return
}
layout.spec.component.action(layout.spec.component.reaction.value)
}
fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation) {
let backgroundCapInsets = layout.backgroundImage.capInsets
if backgroundCapInsets.left.isZero && backgroundCapInsets.top.isZero {
self.layer.contentsScale = layout.backgroundImage.scale
self.layer.contents = layout.backgroundImage.cgImage
} else {
ASDisplayNodeSetResizableContents(self.layer, layout.backgroundImage)
}
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil)
if self.layout?.spec.component.reaction != layout.spec.component.reaction {
if let file = layout.spec.component.reaction.iconFile {
self.iconImageDisposable.set((layout.spec.component.context.account.postbox.mediaBox.resourceData(file.resource)
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self else {
return
}
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = WebP.convert(fromWebP: dataValue) {
strongSelf.iconView.image = image
}
}
}))
}
}
if let counter = layout.counter, let counterFrame = layout.counterFrame {
let counterLayer: CounterLayer
var counterAnimation = animation
if let current = self.counterLayer {
counterLayer = current
} else {
counterAnimation = .None
counterLayer = CounterLayer()
self.counterLayer = counterLayer
//self.layer.addSublayer(counterLayer)
if animation.isAnimated {
counterLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
counterAnimation.animator.updateFrame(layer: counterLayer, frame: counterFrame, completion: nil)
counterLayer.apply(layout: counter, animation: counterAnimation)
} else if let counterLayer = self.counterLayer {
self.counterLayer = nil
if animation.isAnimated {
animation.animator.updateAlpha(layer: counterLayer, alpha: 0.0, completion: { [weak counterLayer] _ in
counterLayer?.removeFromSuperlayer()
})
} else {
counterLayer.removeFromSuperlayer()
}
}
if !layout.spec.component.avatarPeers.isEmpty {
let avatarsView: AnimatedAvatarSetView
if let current = self.avatarsView {
avatarsView = current
} else {
avatarsView = AnimatedAvatarSetView()
avatarsView.isUserInteractionEnabled = false
self.avatarsView = avatarsView
self.addSubview(avatarsView)
}
let content = AnimatedAvatarSetContext().update(peers: layout.spec.component.avatarPeers, animated: false)
let avatarsSize = avatarsView.update(
context: layout.spec.component.context,
content: content,
itemSize: CGSize(width: 24.0, height: 24.0),
customSpacing: 10.0,
animation: animation,
synchronousLoad: false
)
animation.animator.updateFrame(layer: avatarsView.layer, frame: CGRect(origin: CGPoint(x: layout.imageFrame.maxX + 4.0, y: floorToScreenPixels((layout.size.height - avatarsSize.height) / 2.0)), size: CGSize(width: avatarsSize.width, height: avatarsSize.height)), completion: nil)
} else if let avatarsView = self.avatarsView {
self.avatarsView = nil
if animation.isAnimated {
animation.animator.updateAlpha(layer: avatarsView.layer, alpha: 0.0, completion: { [weak avatarsView] _ in
avatarsView?.removeFromSuperview()
})
animation.animator.updateScale(layer: avatarsView.layer, scale: 0.01, completion: nil)
} else {
avatarsView.removeFromSuperview()
}
}
self.layout = layout
}
public static func asyncLayout(_ view: ReactionButtonAsyncView?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionButtonAsyncView) {
let currentLayout = view?.layout
return { component in
let spec = Layout.Spec(component: component)
let layout: Layout
if let currentLayout = currentLayout, currentLayout.spec == spec {
layout = currentLayout
} else {
layout = Layout.calculate(spec: spec, currentLayout: currentLayout, currentCounter: currentLayout?.counter)
}
return (size: layout.size, apply: { animation in
var animation = animation
let updatedView: ReactionButtonAsyncView
if let view = view {
updatedView = view
} else {
updatedView = ReactionButtonAsyncView()
animation = .None
}
updatedView.apply(layout: layout, animation: animation)
return updatedView
})
}
}
}
public final class ReactionButtonComponent: Component {
public struct ViewTag: Equatable {
@ -60,6 +481,7 @@ public final class ReactionButtonComponent: Component {
public let context: AccountContext
public let colors: Colors
public let reaction: Reaction
public let avatarPeers: [EnginePeer]
public let count: Int
public let isSelected: Bool
public let action: (String) -> Void
@ -68,6 +490,7 @@ public final class ReactionButtonComponent: Component {
context: AccountContext,
colors: Colors,
reaction: Reaction,
avatarPeers: [EnginePeer],
count: Int,
isSelected: Bool,
action: @escaping (String) -> Void
@ -75,6 +498,7 @@ public final class ReactionButtonComponent: Component {
self.context = context
self.colors = colors
self.reaction = reaction
self.avatarPeers = avatarPeers
self.count = count
self.isSelected = isSelected
self.action = action
@ -90,6 +514,9 @@ public final class ReactionButtonComponent: Component {
if lhs.reaction != rhs.reaction {
return false
}
if lhs.avatarPeers != rhs.avatarPeers {
return false
}
if lhs.count != rhs.count {
return false
}
@ -247,19 +674,136 @@ public final class ReactionButtonComponent: Component {
}
}
public final class ReactionButtonsAsyncLayoutContainer {
public struct Result {
public struct Item {
public var size: CGSize
}
public var items: [Item]
public var apply: (ListViewItemUpdateAnimation) -> ApplyResult
}
public struct ApplyResult {
public struct Item {
public var value: String
public var view: ReactionButtonAsyncView
public var size: CGSize
}
public var items: [Item]
public var removedViews: [ReactionButtonAsyncView]
}
public private(set) var buttons: [String: ReactionButtonAsyncView] = [:]
public init() {
}
public func update(
context: AccountContext,
action: @escaping (String) -> Void,
reactions: [ReactionButtonsLayoutContainer.Reaction],
colors: ReactionButtonComponent.Colors,
constrainedWidth: CGFloat
) -> Result {
var items: [Result.Item] = []
var applyItems: [(key: String, size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionButtonAsyncView)] = []
var validIds = Set<String>()
for reaction in reactions.sorted(by: { lhs, rhs in
var lhsCount = lhs.count
if lhs.isSelected {
lhsCount -= 1
}
var rhsCount = rhs.count
if rhs.isSelected {
rhsCount -= 1
}
if lhsCount != rhsCount {
return lhsCount > rhsCount
}
return lhs.reaction.value < rhs.reaction.value
}) {
validIds.insert(reaction.reaction.value)
var avatarPeers = reaction.peers
for i in 0 ..< avatarPeers.count {
if avatarPeers[i].id == context.account.peerId {
let peer = avatarPeers[i]
avatarPeers.remove(at: i)
avatarPeers.insert(peer, at: 0)
break
}
}
let viewLayout = ReactionButtonAsyncView.asyncLayout(self.buttons[reaction.reaction.value])
let (size, apply) = viewLayout(ReactionButtonComponent(
context: context,
colors: colors,
reaction: reaction.reaction,
avatarPeers: avatarPeers,
count: reaction.count,
isSelected: reaction.isSelected,
action: action
))
items.append(Result.Item(
size: size
))
applyItems.append((reaction.reaction.value, size, apply))
}
var removeIds: [String] = []
for (id, _) in self.buttons {
if !validIds.contains(id) {
removeIds.append(id)
}
}
var removedViews: [ReactionButtonAsyncView] = []
for id in removeIds {
if let view = self.buttons.removeValue(forKey: id) {
removedViews.append(view)
}
}
return Result(
items: items,
apply: { animation in
var items: [ApplyResult.Item] = []
for (key, size, apply) in applyItems {
let view = apply(animation)
items.append(ApplyResult.Item(value: key, view: view, size: size))
if let current = self.buttons[key] {
assert(current === view)
} else {
self.buttons[key] = view
}
}
return ApplyResult(items: items, removedViews: removedViews)
}
)
}
}
public final class ReactionButtonsLayoutContainer {
public struct Reaction {
public var reaction: ReactionButtonComponent.Reaction
public var count: Int
public var peers: [EnginePeer]
public var isSelected: Bool
public init(
reaction: ReactionButtonComponent.Reaction,
count: Int,
peers: [EnginePeer],
isSelected: Bool
) {
self.reaction = reaction
self.count = count
self.peers = peers
self.isSelected = isSelected
}
}
@ -322,6 +866,7 @@ public final class ReactionButtonsLayoutContainer {
context: context,
colors: colors,
reaction: reaction.reaction,
avatarPeers: reaction.peers,
count: reaction.count,
isSelected: reaction.isSelected,
action: action

View File

@ -0,0 +1,27 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ReactionListContextMenuContent",
module_name = "ReactionListContextMenuContent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display:Display",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/WebPBinding:WebPBinding",
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
"//submodules/ContextUI:ContextUI",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,46 @@
import Foundation
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TelegramPresentationData
import UIKit
import WebPBinding
import AnimatedAvatarSetNode
import ContextUI
public final class ReactionListContextMenuContent: ContextControllerItemsContent {
final class ItemsNode: ASDisplayNode, ContextControllerItemsNode {
private let contentNode: ASDisplayNode
override init() {
self.contentNode = ASDisplayNode()
super.init()
self.addSubnode(self.contentNode)
//self.contentNode.backgroundColor = .blue
}
func update(constrainedWidth: CGFloat, maxHeight: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, visibleSize: CGSize) {
let size = CGSize(width: min(260.0, constrainedWidth), height: maxHeight)
let contentSize = CGSize(width: size.width, height: size.height + bottomInset + 14.0)
//contentSize.height = 120.0
self.contentNode.frame = CGRect(origin: CGPoint(), size: contentSize)
return (size, contentSize)
}
}
public init() {
}
public func node() -> ContextControllerItemsNode {
return ItemsNode()
}
}

View File

@ -542,7 +542,7 @@ public class ContactsController: ViewController {
})
})))
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ContactsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ContactsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
}

View File

@ -174,7 +174,7 @@ final class ContactsControllerNode: ASDisplayNode {
}
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(peer.id), subject: nil, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: contactContextMenuItems(context: self.context, peerId: peer.id, contactsController: contactsController) |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: contactContextMenuItems(context: self.context, peerId: peer.id, contactsController: contactsController) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
contactsController.presentInGlobalOverlay(contextController)
}

View File

@ -41,7 +41,43 @@ private enum ContextItemNode {
case separator(ASDisplayNode)
}
private final class InnerActionsContainerNode: ASDisplayNode {
private protocol ContextInnerActionsContainerNode: ASDisplayNode {
var panSelectionGestureEnabled: Bool { get set }
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, bottomInset: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, visibleSize: CGSize)
func updateTheme(presentationData: PresentationData)
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol?
}
private final class InnerCustomActionsContainerNode: ASDisplayNode, ContextInnerActionsContainerNode {
private let node: ContextControllerItemsNode
var panSelectionGestureEnabled: Bool = false
init(content: ContextControllerItemsContent) {
self.node = content.node()
super.init()
self.addSubnode(self.node)
}
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, bottomInset: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, visibleSize: CGSize) {
let nodeLayout = self.node.update(constrainedWidth: constrainedWidth, maxHeight: constrainedHeight, bottomInset: bottomInset, transition: transition)
transition.updateFrame(node: self.node, frame: CGRect(origin: CGPoint(), size: nodeLayout.cleanSize))
return (nodeLayout.cleanSize, nodeLayout.visibleSize)
}
func updateTheme(presentationData: PresentationData) {
}
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol? {
return nil
}
}
private final class InnerActionsContainerNode: ASDisplayNode, ContextInnerActionsContainerNode {
private let blurBackground: Bool
private let presentationData: PresentationData
private let containerNode: ASDisplayNode
@ -189,7 +225,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
gesture.isEnabled = self.panSelectionGestureEnabled
}
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, bottomInset: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, visibleSize: CGSize) {
var minActionsWidth: CGFloat = 250.0
if let minimalWidth = minimalWidth, minimalWidth > minActionsWidth {
minActionsWidth = minimalWidth
@ -298,7 +334,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
if let effectView = self.effectView {
transition.updateFrame(view: effectView, frame: bounds)
}
return size
return (size, size)
}
func updateTheme(presentationData: PresentationData) {
@ -481,9 +517,12 @@ final class ContextActionsContainerNode: ASDisplayNode {
private let shadowNode: ASImageNode
private let additionalShadowNode: ASImageNode?
private let additionalActionsNode: InnerActionsContainerNode?
private let actionsNode: InnerActionsContainerNode
private let contentContainerNode: ASDisplayNode
private let actionsNode: ContextInnerActionsContainerNode
private let textSelectionTipNode: InnerTextSelectionTipContainerNode?
private let scrollNode: ASScrollNode
//private let scrollNode: ASScrollNode
var panSelectionGestureEnabled: Bool = true {
didSet {
@ -506,8 +545,13 @@ final class ContextActionsContainerNode: ASDisplayNode {
self.shadowNode.contentMode = .scaleToFill
self.shadowNode.isHidden = true
self.contentContainerNode = ASDisplayNode()
self.contentContainerNode.clipsToBounds = true
self.contentContainerNode.cornerRadius = 14.0
self.contentContainerNode.backgroundColor = presentationData.theme.contextMenu.backgroundColor
var items = items
if let firstItem = items.items.first, case let .custom(_, additional) = firstItem, additional {
if case var .list(itemList) = items.content, let firstItem = itemList.first, case let .custom(_, additional) = firstItem, additional {
let additionalShadowNode = ASImageNode()
additionalShadowNode.displaysAsynchronously = false
additionalShadowNode.displayWithoutProcessing = true
@ -517,13 +561,16 @@ final class ContextActionsContainerNode: ASDisplayNode {
self.additionalShadowNode = additionalShadowNode
self.additionalActionsNode = InnerActionsContainerNode(presentationData: presentationData, items: [firstItem], getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
items.items.removeFirst()
itemList.removeFirst()
items.content = .list(itemList)
} else {
self.additionalShadowNode = nil
self.additionalActionsNode = nil
}
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items.items, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
switch items.content {
case let .list(itemList):
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: itemList, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
if let tip = items.tip {
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
textSelectionTipNode.isUserInteractionEnabled = false
@ -531,58 +578,64 @@ final class ContextActionsContainerNode: ASDisplayNode {
} else {
self.textSelectionTipNode = nil
}
case let .custom(customContent):
self.actionsNode = InnerCustomActionsContainerNode(content: customContent)
self.textSelectionTipNode = nil
}
self.scrollNode = ASScrollNode()
/*self.scrollNode = ASScrollNode()
self.scrollNode.canCancelAllTouchesInViews = true
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.showsVerticalScrollIndicator = false
self.scrollNode.clipsToBounds = false
if #available(iOS 11.0, *) {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
}*/
super.init()
self.addSubnode(self.shadowNode)
self.additionalShadowNode.flatMap(self.addSubnode)
self.additionalActionsNode.flatMap(self.scrollNode.addSubnode)
self.scrollNode.addSubnode(self.actionsNode)
self.textSelectionTipNode.flatMap(self.scrollNode.addSubnode)
self.addSubnode(self.scrollNode)
self.additionalActionsNode.flatMap(self.contentContainerNode.addSubnode)
self.contentContainerNode.addSubnode(self.actionsNode)
self.textSelectionTipNode.flatMap(self.addSubnode)
self.addSubnode(self.contentContainerNode)
}
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
var widthClass = widthClass
if !self.blurBackground {
widthClass = .regular
}
var contentSize = CGSize()
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, constrainedHeight: constrainedHeight, minimalWidth: nil, transition: transition)
let actionsLayout = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, constrainedHeight: constrainedHeight, bottomInset: bottomInset, minimalWidth: nil, transition: transition)
if let additionalActionsNode = self.additionalActionsNode, let additionalShadowNode = self.additionalShadowNode {
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, constrainedHeight: constrainedHeight, minimalWidth: actionsSize.width, transition: transition)
contentSize = additionalActionsSize
let additionalActionsLayout = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsLayout.cleanSize.width, constrainedHeight: constrainedHeight, bottomInset: 0.0, minimalWidth: actionsLayout.cleanSize.width, transition: transition)
contentSize = additionalActionsLayout.cleanSize
let bounds = CGRect(origin: CGPoint(), size: additionalActionsSize)
let bounds = CGRect(origin: CGPoint(), size: additionalActionsLayout.cleanSize)
transition.updateFrame(node: additionalShadowNode, frame: bounds.insetBy(dx: -30.0, dy: -30.0))
additionalShadowNode.isHidden = widthClass == .compact
transition.updateFrame(node: additionalActionsNode, frame: CGRect(origin: CGPoint(), size: additionalActionsSize))
transition.updateFrame(node: additionalActionsNode, frame: CGRect(origin: CGPoint(), size: additionalActionsLayout.cleanSize))
contentSize.height += 8.0
}
let bounds = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: actionsSize)
let bounds = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: actionsLayout.visibleSize)
transition.updateFrame(node: self.shadowNode, frame: bounds.insetBy(dx: -30.0, dy: -30.0))
self.shadowNode.isHidden = widthClass == .compact
contentSize.width = max(contentSize.width, actionsSize.width)
contentSize.height += actionsSize.height
contentSize.width = max(contentSize.width, actionsLayout.cleanSize.width)
contentSize.height += actionsLayout.cleanSize.height
transition.updateFrame(node: self.actionsNode, frame: bounds)
transition.updateFrame(node: self.contentContainerNode, frame: bounds)
if let textSelectionTipNode = self.textSelectionTipNode {
contentSize.height += 8.0
let textSelectionTipSize = textSelectionTipNode.updateLayout(widthClass: widthClass, width: actionsSize.width, transition: transition)
let textSelectionTipSize = textSelectionTipNode.updateLayout(widthClass: widthClass, width: actionsLayout.cleanSize.width, transition: transition)
transition.updateFrame(node: textSelectionTipNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: textSelectionTipSize))
contentSize.height += textSelectionTipSize.height
}
@ -591,8 +644,8 @@ final class ContextActionsContainerNode: ASDisplayNode {
}
func updateSize(containerSize: CGSize, contentSize: CGSize) {
self.scrollNode.view.contentSize = contentSize
self.scrollNode.frame = CGRect(origin: CGPoint(), size: containerSize)
//self.scrollNode.view.contentSize = contentSize
//self.scrollNode.frame = CGRect(origin: CGPoint(), size: containerSize)
}
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol? {

View File

@ -293,7 +293,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
self.blurBackground = blurBackground
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: ContextController.Items(items: []), getController: { [weak controller] in
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: ContextController.Items(), getController: { [weak controller] in
return controller
}, actionSelected: { result in
beginDismiss(result)
@ -1216,7 +1216,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if let reactionContextNode = self.reactionContextNode {
self.reactionContextNode = nil
reactionContextNode.removeFromSupernode()
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
reactionContextNode?.removeFromSupernode()
})
}
if !items.reactionItems.isEmpty, let context = items.context {
@ -1338,7 +1340,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, bottomInset: 0.0, transition: actionsContainerTransition)
let adjustedActionsSize = realActionsSize
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
@ -1425,7 +1427,17 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
let constrainedActionsHeight: CGFloat
let constrainedActionsBottomInset: CGFloat
if let currentActionsMinHeight = self.currentActionsMinHeight {
constrainedActionsBottomInset = actionsBottomInset + layout.intrinsicInsets.bottom
constrainedActionsHeight = layout.size.height - currentActionsMinHeight.minY - constrainedActionsBottomInset
} else {
constrainedActionsHeight = layout.size.height
constrainedActionsBottomInset = 0.0
}
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: constrainedActionsHeight, bottomInset: constrainedActionsBottomInset, transition: actionsContainerTransition)
let adjustedActionsSize = realActionsSize
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
@ -1590,7 +1602,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
constrainedWidth = floor(layout.size.width / 2.0)
}
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: constrainedWidth - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: constrainedWidth - actionsSideInset * 2.0, constrainedHeight: layout.size.height, bottomInset: 0.0, transition: actionsContainerTransition)
let contentScale = (constrainedWidth - actionsSideInset * 2.0) / constrainedWidth
var contentUnscaledSize: CGSize
if case .compact = layout.metrics.widthClass {
@ -1952,22 +1964,35 @@ public enum ContextContentSource {
case controller(ContextControllerContentSource)
}
public protocol ContextControllerItemsNode: ASDisplayNode {
func update(constrainedWidth: CGFloat, maxHeight: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, visibleSize: CGSize)
}
public protocol ContextControllerItemsContent: AnyObject {
func node() -> ContextControllerItemsNode
}
public final class ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol {
public struct Items {
public var items: [ContextMenuItem]
public enum Content {
case list([ContextMenuItem])
case custom(ContextControllerItemsContent)
}
public var content: Content
public var context: AccountContext?
public var reactionItems: [ReactionContextItem]
public var tip: Tip?
public init(items: [ContextMenuItem], context: AccountContext? = nil, reactionItems: [ReactionContextItem] = [], tip: Tip? = nil) {
self.items = items
public init(content: Content, context: AccountContext? = nil, reactionItems: [ReactionContextItem] = [], tip: Tip? = nil) {
self.content = content
self.context = context
self.reactionItems = reactionItems
self.tip = tip
}
public init() {
self.items = []
self.content = .list([])
self.context = nil
self.reactionItems = []
self.tip = nil

View File

@ -0,0 +1,12 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import TextSelectionNode
import TelegramCore
import SwiftSignalKit
final class ContextControllerExtractedPresentationNode: ASDisplayNode {
}

View File

@ -73,7 +73,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
var feedbackTapImpl: (() -> Void)?
var activatedActionImpl: (() -> Void)?
var requestLayoutImpl: (() -> Void)?
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: ContextController.Items(items: content.menuItems()), getController: { [weak controller] in
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: ContextController.Items(content: .list(content.menuItems())), getController: { [weak controller] in
return controller
}, actionSelected: { result in
activatedActionImpl?()
@ -158,7 +158,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
}
let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: .immediate)
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, bottomInset: 0.0, transition: .immediate)
let containerFrame: CGRect
let actionsFrame: CGRect
@ -341,7 +341,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
self.contentNodeHasValidLayout = false
let previousActionsContainerNode = self.actionsContainerNode
self.actionsContainerNode = ContextActionsContainerNode(presentationData: self.presentationData, items: ContextController.Items(items: content.menuItems()), getController: { [weak self] in
self.actionsContainerNode = ContextActionsContainerNode(presentationData: self.presentationData, items: ContextController.Items(content: .list(content.menuItems())), getController: { [weak self] in
return self?.controller
}, actionSelected: { [weak self] result in
self?.requestDismiss()

View File

@ -2359,7 +2359,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
self.isShowingContextMenuPromise.set(true)
controller.presentInGlobalOverlay(contextController)
@ -2414,7 +2414,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return
}
c.setItems(strongSelf.contextMenuSpeedItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuSpeedItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
if let (message, _, _) = strongSelf.contentInfo() {
@ -2532,7 +2532,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
c.dismiss(completion: nil)
return
}
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
return items

View File

@ -419,7 +419,7 @@ public final class InviteLinkInviteController: ViewController {
})
})))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController)
}, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link

View File

@ -584,7 +584,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, createLink: {
let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: nil, completion: { invite in
@ -783,7 +783,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, openAdmin: { admin in
let controller = inviteLinkListController(context: context, peerId: peerId, admin: admin)

View File

@ -647,7 +647,7 @@ public final class InviteLinkViewController: ViewController {
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController)
})

View File

@ -268,7 +268,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
// dismissPromise.set(true)
// }
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
})
})

View File

@ -456,7 +456,7 @@ public final class InviteRequestsSearchContainerNode: SearchDisplayControllerCon
// dismissPromise.set(true)
// }
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlay(contextController)
})
})

View File

@ -1139,7 +1139,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
})
})))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, manageInviteLinks: {
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)

View File

@ -158,7 +158,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
backAction(c)
})))
}
contextController.setItems(.single(ContextController.Items(items: items)), minHeight: nil)
contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil)
} else {
contextController?.dismiss(completion: nil)
parent.view.endEditing(true)

View File

@ -494,7 +494,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
chatController.canReadHistory.set(false)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: peerNearbyContextMenuItems(context: context, peerId: peer.id, present: { c in
presentControllerImpl?(c, nil)
}) |> map { ContextController.Items(items: $0) }, gesture: gesture)
}) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, expandUsers: {
expandedPromise.set(true)

View File

@ -646,7 +646,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
}, colorContextAction: { isCurrent, reference, accentColor, node, gesture in
@ -883,7 +883,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme
}
}
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
})

View File

@ -680,7 +680,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})))
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
}, colorContextAction: { isCurrent, reference, accentColor, node, gesture in
@ -917,7 +917,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
}
}
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
})

View File

@ -523,7 +523,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
})
})))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
return controller

View File

@ -555,7 +555,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
return
}
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems()
let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
self.presentInGlobalOverlay?(contextController)
}

View File

@ -1791,7 +1791,7 @@ public final class VoiceChatController: ViewController {
dismissPromise.set(true)
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(source), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(source), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
contextController.useComplexItemsTransitionAnimation = true
strongSelf.controller?.presentInGlobalOverlay(contextController)
}, getPeerVideo: { [weak self] endpointId, position in
@ -2463,7 +2463,7 @@ public final class VoiceChatController: ViewController {
private func openSettingsMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems()
if let controller = self.controller {
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: self.darkTheme), source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceNode: self.optionsButton.referenceNode)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: self.darkTheme), source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceNode: self.optionsButton.referenceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
}
@ -2492,7 +2492,7 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else {
return
}
c.setItems(strongSelf.contextMenuDisplayAsItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuDisplayAsItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
items.append(.separator)
break
@ -2525,7 +2525,7 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else {
return
}
c.setItems(strongSelf.contextMenuAudioItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuAudioItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
}
@ -2562,7 +2562,7 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else {
return
}
c.setItems(strongSelf.contextMenuPermissionItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuPermissionItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
}
}
@ -2832,7 +2832,7 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else {
return
}
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
return .single(items)
}
@ -2927,7 +2927,7 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else {
return
}
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
return items
}
@ -2973,7 +2973,7 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else {
return
}
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(strongSelf.contextMenuMainItems() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
})))
}
return .single(items)

View File

@ -46,6 +46,25 @@ extension ReactionsMessageAttribute {
}
}
public func mergedMessageReactionsAndPeers(message: Message) -> (reactions: [MessageReaction], peers: [(String, EnginePeer)]) {
guard let attribute = mergedMessageReactions(attributes: message.attributes) else {
return ([], [])
}
var recentPeers = attribute.recentPeers.compactMap { recentPeer -> (String, EnginePeer)? in
if let peer = message.peers[recentPeer.peerId] {
return (recentPeer.value, EnginePeer(peer))
} else {
return nil
}
}
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
recentPeers.removeAll()
}
return (attribute.reactions, recentPeers)
}
public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsMessageAttribute? {
var current: ReactionsMessageAttribute?
var pending: PendingReactionsMessageAttribute?
@ -59,7 +78,7 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
if let pending = pending {
var reactions = current?.reactions ?? []
let recentPeers = current?.recentPeers ?? []
var recentPeers = current?.recentPeers ?? []
if let value = pending.value {
var found = false
for i in 0 ..< reactions.count {
@ -75,6 +94,17 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
reactions.append(MessageReaction(value: value, count: 1, isSelected: true))
}
}
if let accountPeerId = pending.accountPeerId {
for i in 0 ..< recentPeers.count {
if recentPeers[i].peerId == accountPeerId {
recentPeers.remove(at: i)
break
}
}
if let value = pending.value {
recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: value, peerId: accountPeerId))
}
}
for i in (0 ..< reactions.count).reversed() {
if reactions[i].isSelected, pending.value != reactions[i].value {
if reactions[i].count == 1 {

View File

@ -3240,7 +3240,7 @@ func replayFinalState(
added = true
updatedReactions = attribute.withUpdatedResults(reactions)
if updatedReactions.reactions == attribute.reactions {
if updatedReactions == attribute {
return .skip
}
attributes[j] = updatedReactions

View File

@ -5,8 +5,8 @@ import TelegramApi
import MtProtoKit
public func updateMessageReactionsInteractively(postbox: Postbox, messageId: MessageId, reaction: String?) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: String?) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction())
transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
@ -20,7 +20,7 @@ public func updateMessageReactionsInteractively(postbox: Postbox, messageId: Mes
break loop
}
}
attributes.append(PendingReactionsMessageAttribute(value: reaction))
attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, value: reaction))
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}

View File

@ -54,6 +54,10 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
public let reactions: [MessageReaction]
public let recentPeers: [RecentPeer]
public var associatedPeerIds: [PeerId] {
return self.recentPeers.map(\.peerId)
}
public init(reactions: [MessageReaction], recentPeers: [RecentPeer]) {
self.reactions = reactions
self.recentPeers = recentPeers
@ -81,17 +85,33 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
}
public final class PendingReactionsMessageAttribute: MessageAttribute {
public let accountPeerId: PeerId?
public let value: String?
public init(value: String?) {
public var associatedPeerIds: [PeerId] {
if let accountPeerId = self.accountPeerId {
return [accountPeerId]
} else {
return []
}
}
public init(accountPeerId: PeerId?, value: String?) {
self.accountPeerId = accountPeerId
self.value = value
}
required public init(decoder: PostboxDecoder) {
self.accountPeerId = decoder.decodeOptionalInt64ForKey("ap").flatMap(PeerId.init)
self.value = decoder.decodeOptionalStringForKey("v")
}
public func encode(_ encoder: PostboxEncoder) {
if let accountPeerId = self.accountPeerId {
encoder.encodeInt64(accountPeerId.toInt64(), forKey: "ap")
} else {
encoder.encodeNil(forKey: "ap")
}
if let value = self.value {
encoder.encodeString(value, forKey: "v")
} else {

View File

@ -250,6 +250,7 @@ swift_library(
"//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent",
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
"//submodules/QrCodeUI:QrCodeUI",
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -956,9 +956,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
).start(next: { actions, availableReactions, chatTextSelectionTips in
var actions = actions
guard let strongSelf = self, !actions.items.isEmpty else {
guard let strongSelf = self else {
return
}
switch actions.content {
case let .list(itemList):
if itemList.isEmpty {
return
}
case .custom:
break
}
var tip: ContextController.Tip?
@ -1062,7 +1070,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: message.id, reaction: updatedReaction).start()
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction).start()
}
strongSelf.forEachController({ controller in
@ -1191,7 +1199,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
let _ = updateMessageReactionsInteractively(postbox: strongSelf.context.account.postbox, messageId: message.id, reaction: updatedReaction).start()
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction).start()
}
}, activateMessagePinch: { [weak self] sourceNode in
guard let strongSelf = self else {
@ -2383,7 +2391,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})))
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: true)), items: .single(ContextController.Items(items: actions)), recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
@ -2460,7 +2468,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
f(.dismissWithoutContent)
})))
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: topMessage, selectAll: true)), items: .single(ContextController.Items(items: actions)), recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: topMessage, selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
@ -2891,7 +2899,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
})
}, openMessageReplies: { [weak self] messageId, isChannelPost, displayModalProgress in
@ -3197,7 +3205,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
@ -6179,7 +6187,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(items: $0) })
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)), items: items |> map { ContextController.Items(content: .list($0)) })
contextController.dismissedForCancel = { [weak chatController] in
if let selectedMessageIds = (chatController as? ChatControllerImpl)?.selectedMessageIds {
var forwardMessageIds = strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
@ -6903,7 +6911,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
})))
contextController.setItems(.single(ContextController.Items(items: contextItems)), minHeight: nil)
contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil)
}
return
} else {
@ -6922,7 +6930,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
})))
contextController.setItems(.single(ContextController.Items(items: contextItems)), minHeight: nil)
contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil)
return
} else {
@ -7657,7 +7665,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}, joinGroupCall: { [weak self] activeCall in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
@ -7719,7 +7727,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
items.append(.custom(ChatSendAsPeerTitleContextItem(text: strongSelf.presentationInterfaceState.strings.Conversation_SendMesageAs.uppercased()), false))
items.append(.custom(ChatSendAsPeerListContextItem(context: strongSelf.context, chatPeerId: peerId, peers: peers, selectedPeerId: myPeerId), false))
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceNode: node, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0))), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: strongSelf, sourceNode: node, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(interactive: true, {
@ -11899,7 +11907,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(id: .timestamp(timestamp), highlight: false, timecode: nil), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
)
@ -13439,7 +13447,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if canDisplayContextMenu, let contextController = contextController {
contextController.setItems(.single(ContextController.Items(items: contextItems)), minHeight: nil)
contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil)
} else {
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in

View File

@ -23,6 +23,7 @@ import AnimatedAvatarSetNode
import AvatarNode
import AdUI
import TelegramNotices
import ReactionListContextMenuContent
private struct MessageContextMenuData {
let starStatus: Bool?
@ -357,7 +358,7 @@ func updatedChatEditInterfaceMessageState(state: ChatPresentationInterfaceState,
func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, messages: [Message], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?, readStats: MessageReadStats? = nil) -> Signal<ContextController.Items, NoError> {
guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
return .single(ContextController.Items(items: []))
return .single(ContextController.Items(content: .list([])))
}
if messages.count == 1, let _ = messages[0].adAttribute {
@ -428,7 +429,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
})))
}
return .single(ContextController.Items(items: actions))
return .single(ContextController.Items(content: .list(actions)))
}
var loadStickerSaveStatus: MediaId?
@ -1170,13 +1171,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
if let peer = message.peers[message.id.peerId], canViewReadStats(message: message, isMessageRead: isMessageRead, appConfig: appConfig) {
let canViewStats = canViewReadStats(message: message, isMessageRead: isMessageRead, appConfig: appConfig)
var reactionCount = 0
for reaction in mergedMessageReactionsAndPeers(message: message).reactions {
reactionCount += Int(reaction.count)
}
if let peer = message.peers[message.id.peerId], (canViewStats || reactionCount != 0) {
var hasReadReports = false
if let channel = peer as? TelegramChannel {
if case .group = channel.info {
if let cachedData = cachedData as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 50 {
hasReadReports = true
}
} else {
reactionCount = 0
}
} else if let group = peer as? TelegramGroup {
if group.participantCount <= 50 {
@ -1184,17 +1193,26 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
if hasReadReports {
var readStats = readStats
if !canViewStats {
readStats = MessageReadStats(peers: [])
}
if hasReadReports || reactionCount != 0 {
if !actions.isEmpty {
actions.insert(.separator, at: 0)
}
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, stats: readStats, action: { c, f, stats in
if stats.peers.count == 1 {
if reactionCount == 0 && stats.peers.count == 1 {
c.dismiss(completion: {
controllerInteraction.openPeer(stats.peers[0].id, .default, nil)
})
} else if !stats.peers.isEmpty {
} else if !stats.peers.isEmpty || reactionCount != 0 {
if reactionCount != 0 {
let minHeight = c.getActionsMinHeight()
c.setItems(.single(ContextController.Items(content: .custom(ReactionListContextMenuContent()), tip: nil)), minHeight: minHeight, previousActionsTransition: .slide(forward: true))
} else {
var subActions: [ContextMenuItem] = []
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -1224,7 +1242,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
let minHeight = c.getActionsMinHeight()
c.setItems(.single(ContextController.Items(items: subActions, tip: tip)), minHeight: minHeight, previousActionsTransition: .slide(forward: true))
c.setItems(.single(ContextController.Items(content: .list(subActions), tip: tip)), minHeight: minHeight, previousActionsTransition: .slide(forward: true))
}
} else {
f(.default)
}
@ -1232,7 +1251,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
return ContextController.Items(items: actions, tip: nil)
return ContextController.Items(content: .list(actions), tip: nil)
}
}
@ -1850,10 +1869,15 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
var reactionCount = 0
for reaction in mergedMessageReactionsAndPeers(message: self.item.message).reactions {
reactionCount += Int(reaction.count)
}
if let currentStats = self.currentStats {
self.buttonNode.isUserInteractionEnabled = !currentStats.peers.isEmpty
self.buttonNode.isUserInteractionEnabled = !currentStats.peers.isEmpty || reactionCount != 0
} else {
self.buttonNode.isUserInteractionEnabled = false
self.buttonNode.isUserInteractionEnabled = reactionCount != 0
self.disposable = (item.context.engine.messages.messageReadStats(id: item.message.id)
|> deliverOnMainQueue).start(next: { [weak self] value in
@ -1912,8 +1936,23 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize)
var reactionCount = 0
for reaction in mergedMessageReactionsAndPeers(message: self.item.message).reactions {
reactionCount += Int(reaction.count)
}
if let currentStats = self.currentStats {
if currentStats.peers.isEmpty {
if reactionCount != 0 {
//TODO:localize
let text: String
if reactionCount == 1 {
text = "1 reaction"
} else {
text = "\(reactionCount) reactions"
}
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
} else {
var text = self.presentationData.strings.Conversation_ContextMenuNoViews
for media in self.item.message.media {
if let file = media as? TelegramMediaFile {
@ -1926,8 +1965,34 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
}
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.secondaryColor)
}
} else if currentStats.peers.count == 1 {
if reactionCount != 0 {
//TODO:localize
let text: String
if reactionCount == 1 {
text = "1 reacted"
} else {
text = "\(reactionCount) reacted"
}
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
} else {
self.textNode.attributedText = NSAttributedString(string: currentStats.peers[0].displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
}
} else {
if reactionCount != 0 {
//TODO:localize
let text: String
if reactionCount >= currentStats.peers.count {
if reactionCount == 1 {
text = "1 reacted"
} else {
text = "\(reactionCount) reacted"
}
} else {
text = "\(reactionCount)/\(currentStats.peers.count) reacted"
}
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
} else {
var text = self.presentationData.strings.Conversation_ContextMenuSeen(Int32(currentStats.peers.count))
for media in self.item.message.media {
@ -1942,6 +2007,7 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
}
}
} else {
self.textNode.attributedText = NSAttributedString(string: " ", font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
}

View File

@ -1465,7 +1465,7 @@ final class ChatMediaInputNode: ChatInputNode {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: sourceNode, sourceRect: sourceRect)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: sourceNode, sourceRect: sourceRect)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.controllerInteraction.presentGlobalOverlayController(contextController, nil)
})
}

View File

@ -706,6 +706,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let backgroundNode = self.backgroundNode {
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
}
if let reactionButtonsNode = self.reactionButtonsNode {
var reactionButtonsNodeFrame = reactionButtonsNode.frame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
}
}
@ -713,6 +721,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let backgroundNode = self.backgroundNode {
backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
if let reactionButtonsNode = self.reactionButtonsNode {
reactionButtonsNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
}
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
@ -926,7 +938,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var edited = false
var viewCount: Int? = nil
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let _ = attribute as? EditedMessageAttribute, isEmoji {
edited = true
@ -956,7 +968,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring
@ -1098,14 +1111,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
if !reactions.reactions.isEmpty {
let totalInset = params.leftInset + layoutConstants.bubble.edgeInset * 2.0 + avatarInset + layoutConstants.bubble.contentInsets.left + params.rightInset + layoutConstants.bubble.contentInsets.right
let totalInset = params.leftInset + layoutConstants.bubble.edgeInset * 2.0 + avatarInset + layoutConstants.bubble.contentInsets.left * 2.0 + params.rightInset
let maxReactionsWidth = params.width - totalInset
let (minWidth, buttonsLayout) = reactionButtonsLayout(ChatMessageReactionButtonsNode.Arguments(
context: item.context,
presentationData: item.presentationData,
presentationContext: item.controllerInteraction.presentationContext,
availableReactions: item.associatedData.availableReactions,
reactions: reactions,
message: item.message,
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
constrainedWidth: maxReactionsWidth
))
@ -1400,12 +1415,32 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
}
reactionButtonsNode.frame = reactionButtonsFrame
if let (rect, containerSize) = strongSelf.absoluteRect {
var rect = rect
rect.origin.y = containerSize.height - rect.maxY + strongSelf.insets.top
var reactionButtonsNodeFrame = reactionButtonsFrame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
strongSelf.addSubnode(reactionButtonsNode)
if animation.isAnimated {
reactionButtonsNode.animateIn(animation: animation)
}
} else {
animation.animator.updateFrame(layer: reactionButtonsNode.layer, frame: reactionButtonsFrame, completion: nil)
if let (rect, containerSize) = strongSelf.absoluteRect {
var rect = rect
rect.origin.y = containerSize.height - rect.maxY + strongSelf.insets.top
var reactionButtonsNodeFrame = reactionButtonsFrame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: animation.transition)
}
}
} else if let reactionButtonsNode = strongSelf.reactionButtonsNode {
strongSelf.reactionButtonsNode = nil

View File

@ -322,7 +322,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: message)
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -498,7 +498,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
type: statusType,
edited: edited,
viewCount: viewCount,
dateReactions: dateReactions,
dateReactions: dateReactionsAndPeers.reactions,
dateReactionPeers: dateReactionsAndPeers.peers,
dateReplies: dateReplies,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
dateText: dateText
@ -637,7 +638,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: shouldDisplayInlineDateReactions(message: message) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil),
constrainedSize: textConstrainedSize,
availableReactions: associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring

View File

@ -1543,7 +1543,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: message)
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -1592,7 +1592,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring
@ -1777,8 +1778,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let (minWidth, buttonsLayout) = reactionButtonsLayout(ChatMessageReactionButtonsNode.Arguments(
context: item.context,
presentationData: item.presentationData,
presentationContext: item.controllerInteraction.presentationContext,
availableReactions: item.associatedData.availableReactions,
reactions: bubbleReactions,
message: item.message,
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
constrainedWidth: maximumNodeWidth
))
@ -2826,8 +2829,30 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if animation.isAnimated {
reactionButtonsNode.animateIn(animation: animation)
}
if let (rect, containerSize) = strongSelf.absoluteRect {
var rect = rect
rect.origin.y = containerSize.height - rect.maxY + strongSelf.insets.top
var reactionButtonsNodeFrame = reactionButtonsFrame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
} else {
animation.animator.updateFrame(layer: reactionButtonsNode.layer, frame: reactionButtonsFrame, completion: nil)
if let (rect, containerSize) = strongSelf.absoluteRect {
var rect = rect
rect.origin.y = containerSize.height - rect.maxY + strongSelf.insets.top
var reactionButtonsNodeFrame = reactionButtonsFrame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: animation.transition)
}
}
} else if let reactionButtonsNode = strongSelf.reactionButtonsNode {
strongSelf.reactionButtonsNode = nil
@ -3759,6 +3784,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
for contentNode in self.contentNodes {
contentNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + contentNode.frame.minX, y: rect.minY + contentNode.frame.minY), size: rect.size), within: containerSize)
}
if let reactionButtonsNode = self.reactionButtonsNode {
var reactionButtonsNodeFrame = reactionButtonsNode.frame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
}
override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
@ -3773,6 +3806,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
for contentNode in self.contentNodes {
contentNode.applyAbsoluteOffset(value: value, animationCurve: animationCurve, duration: duration)
}
if let reactionButtonsNode = self.reactionButtonsNode {
reactionButtonsNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
}
private func applyAbsoluteOffsetSpringInternal(value: CGFloat, duration: Double, damping: CGFloat) {
@ -3781,6 +3818,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
for contentNode in self.contentNodes {
contentNode.applyAbsoluteOffsetSpring(value: value, duration: duration, damping: damping)
}
if let reactionButtonsNode = self.reactionButtonsNode {
reactionButtonsNode.offsetSpring(value: value, duration: duration, damping: damping)
}
}
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {

View File

@ -163,7 +163,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -213,7 +213,8 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: nil),
constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring

View File

@ -144,6 +144,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
var constrainedSize: CGSize
var availableReactions: AvailableReactions?
var reactions: [MessageReaction]
var reactionPeers: [(String, EnginePeer)]
var replyCount: Int
var isPinned: Bool
var hasAutoremove: Bool
@ -159,6 +160,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
constrainedSize: CGSize,
availableReactions: AvailableReactions?,
reactions: [MessageReaction],
reactionPeers: [(String, EnginePeer)],
replyCount: Int,
isPinned: Bool,
hasAutoremove: Bool
@ -173,6 +175,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
self.availableReactions = availableReactions
self.constrainedSize = constrainedSize
self.reactions = reactions
self.reactionPeers = reactionPeers
self.replyCount = replyCount
self.isPinned = isPinned
self.hasAutoremove = hasAutoremove
@ -188,7 +191,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
private let dateNode: TextNode
private var impressionIcon: ASImageNode?
private var reactionNodes: [String: StatusReactionNode] = [:]
private let reactionButtonsContainer = ReactionButtonsLayoutContainer()
private let reactionButtonsContainer = ReactionButtonsAsyncLayoutContainer()
private var reactionCountNode: TextNode?
private var reactionButtonNode: HighlightTrackingButtonNode?
private var repliesIcon: ASImageNode?
@ -617,14 +620,14 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
let resultingWidth: CGFloat
let resultingHeight: CGFloat
let reactionButtons: ReactionButtonsLayoutContainer.Result
let reactionButtonsResult: ReactionButtonsAsyncLayoutContainer.Result
switch arguments.layoutInput {
case .standalone:
verticalReactionsInset = 0.0
verticalInset = 0.0
resultingWidth = layoutSize.width
resultingHeight = layoutSize.height
reactionButtons = reactionButtonsContainer.update(
reactionButtonsResult = reactionButtonsContainer.update(
context: arguments.context,
action: { value in
guard let strongSelf = self else {
@ -634,12 +637,11 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
},
reactions: [],
colors: reactionColors,
constrainedWidth: arguments.constrainedSize.width,
transition: .immediate
constrainedWidth: arguments.constrainedSize.width
)
case let .trailingContent(contentWidth, reactionSettings):
if let reactionSettings = reactionSettings, !reactionSettings.displayInline {
reactionButtons = reactionButtonsContainer.update(
reactionButtonsResult = reactionButtonsContainer.update(
context: arguments.context,
action: { value in
guard let strongSelf = self else {
@ -659,21 +661,31 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
}
var peers: [EnginePeer] = []
for (value, peer) in arguments.reactionPeers {
if value == reaction.value {
peers.append(peer)
}
}
if peers.count != Int(reaction.count) {
peers.removeAll()
}
return ReactionButtonsLayoutContainer.Reaction(
reaction: ReactionButtonComponent.Reaction(
value: reaction.value,
iconFile: iconFile
),
count: Int(reaction.count),
peers: peers,
isSelected: reaction.isSelected
)
},
colors: reactionColors,
constrainedWidth: arguments.constrainedSize.width,
transition: .immediate
constrainedWidth: arguments.constrainedSize.width
)
} else {
reactionButtons = reactionButtonsContainer.update(
reactionButtonsResult = reactionButtonsContainer.update(
context: arguments.context,
action: { value in
guard let strongSelf = self else {
@ -683,14 +695,13 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
},
reactions: [],
colors: reactionColors,
constrainedWidth: arguments.constrainedSize.width,
transition: .immediate
constrainedWidth: arguments.constrainedSize.width
)
}
var reactionButtonsSize = CGSize()
var currentRowWidth: CGFloat = 0.0
for item in reactionButtons.items {
for item in reactionButtonsResult.items {
if currentRowWidth + item.size.width > arguments.constrainedSize.width {
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
if !reactionButtonsSize.height.isZero {
@ -705,12 +716,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
currentRowWidth += item.size.width
}
if !currentRowWidth.isZero && !reactionButtons.items.isEmpty {
if !currentRowWidth.isZero && !reactionButtonsResult.items.isEmpty {
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
if !reactionButtonsSize.height.isZero {
reactionButtonsSize.height += 6.0
}
reactionButtonsSize.height += reactionButtons.items[0].size.height
reactionButtonsSize.height += reactionButtonsResult.items[0].size.height
}
if reactionButtonsSize.width.isZero {
@ -758,6 +769,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
strongSelf.type = arguments.type
strongSelf.layoutSize = layoutSize
let reactionButtons = reactionButtonsResult.apply(animation)
var reactionButtonPosition = CGPoint(x: -1.0, y: verticalReactionsInset)
for item in reactionButtons.items {
if reactionButtonPosition.x + item.size.width > boundingWidth {
@ -1136,9 +1149,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
return node.iconView
}
}
for (_, button) in self.reactionButtonsContainer.buttons {
if let result = button.findTaggedView(tag: ReactionButtonComponent.ViewTag(value: value)) as? ReactionButtonComponent.View {
return result.iconView
for (key, button) in self.reactionButtonsContainer.buttons {
if key == value {
return button.iconView
}
}
return nil

View File

@ -556,8 +556,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
let (minWidth, buttonsLayout) = reactionButtonsLayout(ChatMessageReactionButtonsNode.Arguments(
context: item.context,
presentationData: item.presentationData,
presentationContext: item.controllerInteraction.presentationContext,
availableReactions: item.associatedData.availableReactions,
reactions: reactions,
message: item.message,
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
constrainedWidth: maxReactionsWidth
))

View File

@ -430,7 +430,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: topMessage.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: topMessage)
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -458,7 +458,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
layoutInput: .trailingContent(contentWidth: iconFrame == nil ? 1000.0 : controlAreaWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message), preferAdditionalInset: !shouldDisplayInlineDateReactions(message: message))),
constrainedSize: constrainedSize,
availableReactions: associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: isPinned && !associatedData.isInPinnedListMode,
hasAutoremove: message.isSelfExpiring

View File

@ -262,7 +262,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let sentViaBot = false
var viewCount: Int? = nil
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -299,7 +299,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring

View File

@ -69,6 +69,7 @@ struct ChatMessageDateAndStatus {
var edited: Bool
var viewCount: Int?
var dateReactions: [MessageReaction]
var dateReactionPeers: [(String, EnginePeer)]
var dateReplies: Int
var isPinned: Bool
var dateText: String
@ -518,6 +519,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
constrainedSize: CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude),
availableReactions: associatedData.availableReactions,
reactions: dateAndStatus.dateReactions,
reactionPeers: dateAndStatus.dateReactionPeers,
replyCount: dateAndStatus.dateReplies,
isPinned: dateAndStatus.isPinned,
hasAutoremove: message.isSelfExpiring

View File

@ -183,7 +183,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -255,7 +255,8 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring

View File

@ -159,7 +159,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
if case .mosaic = preparePosition {
@ -207,7 +207,8 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
type: statusType,
edited: edited,
viewCount: viewCount,
dateReactions: dateReactions,
dateReactions: dateReactionsAndPeers.reactions,
dateReactionPeers: dateReactionsAndPeers.peers,
dateReplies: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
dateText: dateText

View File

@ -1024,7 +1024,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -1075,7 +1075,8 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: message) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring

View File

@ -11,6 +11,7 @@ import AnimatedCountLabelNode
import AnimatedAvatarSetNode
import ReactionButtonListComponent
import AccountContext
import WallpaperBackgroundNode
final class MessageReactionButtonsNode: ASDisplayNode {
enum DisplayType {
@ -24,20 +25,29 @@ final class MessageReactionButtonsNode: ASDisplayNode {
case right
}
private let container: ReactionButtonsLayoutContainer
private var bubbleBackgroundNode: WallpaperBubbleBackgroundNode?
private let container: ReactionButtonsAsyncLayoutContainer
private let backgroundMaskView: UIView
private var backgroundMaskButtons: [String: UIView] = [:]
var reactionSelected: ((String) -> Void)?
override init() {
self.container = ReactionButtonsLayoutContainer()
self.container = ReactionButtonsAsyncLayoutContainer()
self.backgroundMaskView = UIView()
super.init()
}
func update() {
}
func prepareUpdate(
context: AccountContext,
presentationData: ChatPresentationData,
presentationContext: ChatPresentationContext,
availableReactions: AvailableReactions?,
reactions: ReactionsMessageAttribute,
message: Message,
alignment: DisplayAlignment,
constrainedWidth: CGFloat,
type: DisplayType
@ -61,13 +71,13 @@ final class MessageReactionButtonsNode: ASDisplayNode {
case .freeform:
reactionColors = ReactionButtonComponent.Colors(
deselectedBackground: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper).argb,
selectedBackground: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper).argb,
deselectedForeground: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.incoming.actionButtonsTextColor, wallpaper: presentationData.theme.wallpaper).argb,
selectedForeground: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.incoming.actionButtonsTextColor, wallpaper: presentationData.theme.wallpaper).argb
selectedBackground: UIColor(white: 1.0, alpha: 0.8).argb,
deselectedForeground: UIColor(white: 1.0, alpha: 1.0).argb,
selectedForeground: UIColor(white: 0.0, alpha: 0.1).argb
)
}
let reactionButtons = self.container.update(
let reactionButtonsResult = self.container.update(
context: context,
action: { [weak self] value in
guard let strongSelf = self else {
@ -87,23 +97,39 @@ final class MessageReactionButtonsNode: ASDisplayNode {
}
}
var peers: [EnginePeer] = []
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
} else {
for recentPeer in reactions.recentPeers {
if recentPeer.value == reaction.value {
if let peer = message.peers[recentPeer.peerId] {
peers.append(EnginePeer(peer))
}
}
}
}
if peers.count != Int(reaction.count) {
peers.removeAll()
}
return ReactionButtonsLayoutContainer.Reaction(
reaction: ReactionButtonComponent.Reaction(
value: reaction.value,
iconFile: iconFile
),
count: Int(reaction.count),
peers: peers,
isSelected: reaction.isSelected
)
},
colors: reactionColors,
constrainedWidth: constrainedWidth,
transition: .immediate
constrainedWidth: constrainedWidth
)
var reactionButtonsSize = CGSize()
var currentRowWidth: CGFloat = 0.0
for item in reactionButtons.items {
for item in reactionButtonsResult.items {
if currentRowWidth + item.size.width > constrainedWidth {
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
if !reactionButtonsSize.height.isZero {
@ -118,12 +144,12 @@ final class MessageReactionButtonsNode: ASDisplayNode {
}
currentRowWidth += item.size.width
}
if !currentRowWidth.isZero && !reactionButtons.items.isEmpty {
if !currentRowWidth.isZero && !reactionButtonsResult.items.isEmpty {
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
if !reactionButtonsSize.height.isZero {
reactionButtonsSize.height += 6.0
}
reactionButtonsSize.height += reactionButtons.items[0].size.height
reactionButtonsSize.height += reactionButtonsResult.items[0].size.height
}
let topInset: CGFloat = 0.0
@ -136,6 +162,38 @@ final class MessageReactionButtonsNode: ASDisplayNode {
return
}
let backgroundInsets: CGFloat = 10.0
switch type {
case .freeform:
if let backgroundNode = presentationContext.backgroundNode, backgroundNode.hasBubbleBackground(for: .free) {
let bubbleBackgroundFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -backgroundInsets, dy: -backgroundInsets)
if let bubbleBackgroundNode = strongSelf.bubbleBackgroundNode {
animation.animator.updateFrame(layer: bubbleBackgroundNode.layer, frame: bubbleBackgroundFrame, completion: nil)
if let (rect, containerSize) = strongSelf.absoluteRect {
bubbleBackgroundNode.update(rect: rect, within: containerSize, transition: animation.transition)
}
} else if strongSelf.bubbleBackgroundNode == nil {
if let bubbleBackgroundNode = backgroundNode.makeBubbleBackground(for: .free) {
strongSelf.bubbleBackgroundNode = bubbleBackgroundNode
bubbleBackgroundNode.view.mask = strongSelf.backgroundMaskView
strongSelf.insertSubnode(bubbleBackgroundNode, at: 0)
bubbleBackgroundNode.frame = bubbleBackgroundFrame
}
}
} else {
if let bubbleBackgroundNode = strongSelf.bubbleBackgroundNode {
strongSelf.bubbleBackgroundNode = nil
bubbleBackgroundNode.removeFromSupernode()
}
}
case .incoming, .outgoing:
if let bubbleBackgroundNode = strongSelf.bubbleBackgroundNode {
strongSelf.bubbleBackgroundNode = nil
bubbleBackgroundNode.removeFromSupernode()
}
}
var reactionButtonPosition: CGPoint
switch alignment {
case .left:
@ -143,7 +201,13 @@ final class MessageReactionButtonsNode: ASDisplayNode {
case .right:
reactionButtonPosition = CGPoint(x: size.width + 1.0, y: topInset)
}
let reactionButtons = reactionButtonsResult.apply(animation)
var validIds = Set<String>()
for item in reactionButtons.items {
validIds.insert(item.value)
switch alignment {
case .left:
if reactionButtonPosition.x + item.size.width > boundingWidth {
@ -167,6 +231,19 @@ final class MessageReactionButtonsNode: ASDisplayNode {
reactionButtonPosition.x -= item.size.width + 6.0
}
let itemMaskFrame = itemFrame.offsetBy(dx: backgroundInsets, dy: backgroundInsets)
let itemMaskView: UIView
if let current = strongSelf.backgroundMaskButtons[item.value] {
itemMaskView = current
} else {
itemMaskView = UIView()
itemMaskView.backgroundColor = .black
itemMaskView.clipsToBounds = true
itemMaskView.layer.cornerRadius = 15.0
strongSelf.backgroundMaskButtons[item.value] = itemMaskView
}
if item.view.superview == nil {
strongSelf.view.addSubview(item.view)
if animation.isAnimated {
@ -177,6 +254,35 @@ final class MessageReactionButtonsNode: ASDisplayNode {
} else {
animation.animator.updateFrame(layer: item.view.layer, frame: itemFrame, completion: nil)
}
if itemMaskView.superview == nil {
strongSelf.backgroundMaskView.addSubview(itemMaskView)
if animation.isAnimated {
itemMaskView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
itemMaskView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
itemMaskView.frame = itemMaskFrame
} else {
animation.animator.updateFrame(layer: itemMaskView.layer, frame: itemMaskFrame, completion: nil)
}
}
var removeMaskIds: [String] = []
for (id, view) in strongSelf.backgroundMaskButtons {
if !validIds.contains(id) {
removeMaskIds.append(id)
if animation.isAnimated {
view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
view?.removeFromSuperview()
})
} else {
view.removeFromSuperview()
}
}
}
for id in removeMaskIds {
strongSelf.backgroundMaskButtons.removeValue(forKey: id)
}
for view in reactionButtons.removedViews {
@ -193,10 +299,40 @@ final class MessageReactionButtonsNode: ASDisplayNode {
})
}
private var absoluteRect: (CGRect, CGSize)?
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
self.absoluteRect = (rect, containerSize)
if let bubbleBackgroundNode = self.bubbleBackgroundNode {
bubbleBackgroundNode.update(rect: rect, within: containerSize, transition: transition)
}
}
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) {
self.absoluteRect = (rect, containerSize)
if let bubbleBackgroundNode = self.bubbleBackgroundNode {
bubbleBackgroundNode.update(rect: rect, within: containerSize, transition: transition)
}
}
func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
if let bubbleBackgroundNode = self.bubbleBackgroundNode {
bubbleBackgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
}
func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
if let bubbleBackgroundNode = self.bubbleBackgroundNode {
bubbleBackgroundNode.offsetSpring(value: value, duration: duration, damping: damping)
}
}
func reactionTargetView(value: String) -> UIView? {
for (_, button) in self.container.buttons {
if let result = button.findTaggedView(tag: ReactionButtonComponent.ViewTag(value: value)) as? ReactionButtonComponent.View {
return result.iconView
for (key, button) in self.container.buttons {
if key == value {
return button.iconView
}
}
return nil
@ -258,7 +394,8 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
let buttonsUpdate = buttonsNode.prepareUpdate(
context: item.context,
presentationData: item.presentationData,
availableReactions: item.associatedData.availableReactions, reactions: reactionsAttribute, alignment: .left, constrainedWidth: constrainedSize.width, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing)
presentationContext: item.controllerInteraction.presentationContext,
availableReactions: item.associatedData.availableReactions, reactions: reactionsAttribute, message: item.message, alignment: .left, constrainedWidth: constrainedSize.width, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing)
return (layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + buttonsUpdate.proposedWidth, { boundingWidth in
var boundingSize = CGSize()
@ -334,23 +471,29 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode {
final class Arguments {
let context: AccountContext
let presentationData: ChatPresentationData
let presentationContext: ChatPresentationContext
let availableReactions: AvailableReactions?
let reactions: ReactionsMessageAttribute
let message: Message
let isIncoming: Bool
let constrainedWidth: CGFloat
init(
context: AccountContext,
presentationData: ChatPresentationData,
presentationContext: ChatPresentationContext,
availableReactions: AvailableReactions?,
reactions: ReactionsMessageAttribute,
message: Message,
isIncoming: Bool,
constrainedWidth: CGFloat
) {
self.context = context
self.presentationData = presentationData
self.presentationContext = presentationContext
self.availableReactions = availableReactions
self.reactions = reactions
self.message = message
self.isIncoming = isIncoming
self.constrainedWidth = constrainedWidth
}
@ -378,8 +521,10 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode {
let buttonsUpdate = node.buttonsNode.prepareUpdate(
context: arguments.context,
presentationData: arguments.presentationData,
presentationContext: arguments.presentationContext,
availableReactions: arguments.availableReactions,
reactions: arguments.reactions,
message: arguments.message,
alignment: arguments.isIncoming ? .left : .right,
constrainedWidth: arguments.constrainedWidth,
type: .freeform
@ -421,4 +566,20 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode {
}
return nil
}
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
self.buttonsNode.update(rect: rect, within: containerSize, transition: transition)
}
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) {
self.buttonsNode.update(rect: rect, within: containerSize, transition: transition)
}
func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
self.buttonsNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
self.buttonsNode.offsetSpring(value: value, duration: duration, damping: damping)
}
}

View File

@ -53,7 +53,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
var viewCount: Int?
var rawText = ""
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
@ -123,7 +123,8 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message), preferAdditionalInset: false)),
constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring

View File

@ -253,6 +253,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let backgroundNode = self.backgroundNode {
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
}
if let reactionButtonsNode = self.reactionButtonsNode {
var reactionButtonsNodeFrame = reactionButtonsNode.frame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
}
}
@ -260,6 +268,10 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let backgroundNode = self.backgroundNode {
backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
if let reactionButtonsNode = self.reactionButtonsNode {
reactionButtonsNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
}
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
@ -465,7 +477,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
var edited = false
var viewCount: Int? = nil
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let _ = attribute as? EditedMessageAttribute, isEmoji {
edited = true
@ -495,7 +507,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring
@ -642,14 +655,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
if !reactions.reactions.isEmpty {
let totalInset = params.leftInset + layoutConstants.bubble.edgeInset * 2.0 + avatarInset + layoutConstants.bubble.contentInsets.left + params.rightInset + layoutConstants.bubble.contentInsets.right
let totalInset = params.leftInset + layoutConstants.bubble.edgeInset * 2.0 + avatarInset + layoutConstants.bubble.contentInsets.left * 2.0 + params.rightInset
let maxReactionsWidth = params.width - totalInset
let (minWidth, buttonsLayout) = reactionButtonsLayout(ChatMessageReactionButtonsNode.Arguments(
context: item.context,
presentationData: item.presentationData,
presentationContext: item.controllerInteraction.presentationContext,
availableReactions: item.associatedData.availableReactions,
reactions: reactions,
message: item.message,
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
constrainedWidth: maxReactionsWidth
))
@ -978,8 +993,30 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if animation.isAnimated {
reactionButtonsNode.animateIn(animation: animation)
}
if let (rect, containerSize) = strongSelf.absoluteRect {
var rect = rect
rect.origin.y = containerSize.height - rect.maxY + strongSelf.insets.top
var reactionButtonsNodeFrame = reactionButtonsFrame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
} else {
animation.animator.updateFrame(layer: reactionButtonsNode.layer, frame: reactionButtonsFrame, completion: nil)
if let (rect, containerSize) = strongSelf.absoluteRect {
var rect = rect
rect.origin.y = containerSize.height - rect.maxY + strongSelf.insets.top
var reactionButtonsNodeFrame = reactionButtonsFrame
reactionButtonsNodeFrame.origin.x += rect.minX
reactionButtonsNodeFrame.origin.y += rect.minY
reactionButtonsNode.update(rect: rect, within: containerSize, transition: animation.transition)
}
}
} else if let reactionButtonsNode = strongSelf.reactionButtonsNode {
strongSelf.reactionButtonsNode = nil

View File

@ -119,7 +119,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: item.message.attributes)?.reactions ?? []
let dateReactionsAndPeers = mergedMessageReactionsAndPeers(message: item.message)
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -305,7 +305,8 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
layoutInput: dateLayoutInput,
constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring

View File

@ -234,7 +234,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
if let message = messages.first {
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(items: [])), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list([]))), gesture: gesture)
presentInGlobalOverlay(contextController)
} else {
gesture?.cancel()

View File

@ -1826,7 +1826,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
return ContextController.Items(items: items)
return ContextController.Items(content: .list(items))
}, minHeight: nil)
})))
}
@ -1843,7 +1843,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})))
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
strongSelf.controller?.window?.presentInGlobalOverlay(controller)
})
}, updateMessageReaction: { _, _ in
@ -1963,7 +1963,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
return ContextController.Items(items: items)
return ContextController.Items(content: .list(items))
}, minHeight: nil)
})))
}
@ -1986,7 +1986,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
switch previewData {
case let .gallery(gallery):
gallery.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node, sourceRect: rect)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node, sourceRect: rect)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
case .instantPage:
break
@ -2213,7 +2213,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self?.chatInterfaceInteraction.openPeer(peer.id, .default, nil)
}))
]
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@ -2845,7 +2845,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, synchronousLoad: true)
galleryController.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
}
@ -3491,7 +3491,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self.view.endEditing(true)
if let sourceNode = self.headerNode.buttonNodes[.mute]?.referenceNode {
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -3717,7 +3717,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, action: { [weak self] c, f in
self?.openReport(user: false, contextController: c, backAction: { c in
if let mainItemsImpl = mainItemsImpl {
c.setItems(mainItemsImpl() |> map { ContextController.Items(items: $0) }, minHeight: nil)
c.setItems(mainItemsImpl() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil)
}
})
})))
@ -3773,7 +3773,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if let sourceNode = self.headerNode.buttonNodes[.more]?.referenceNode {
let items = mainItemsImpl?() ?? .single([])
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -4434,7 +4434,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})))
if let contextController = contextController {
contextController.setItems(.single(ContextController.Items(items: items)), minHeight: nil)
contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil)
} else {
strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat)
if let (layout, navigationHeight) = strongSelf.validLayout {
@ -4442,7 +4442,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller {
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -4530,7 +4530,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
if let contextController = contextController {
contextController.setItems(.single(ContextController.Items(items: items)), minHeight: nil)
contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil)
} else {
strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat)
if let (layout, navigationHeight) = strongSelf.validLayout {
@ -4538,7 +4538,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller {
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.dismissed = { [weak self] in
if let strongSelf = self {
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
@ -5620,7 +5620,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let contextController = ContextController(account: accountContext.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: accountContextMenuItems(context: accountContext, logout: { [weak self] in
self?.logoutAccount(id: id)
}) |> map { ContextController.Items(items: $0) }, gesture: gesture)
}) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
self.controller?.presentInGlobalOverlay(contextController)
} else {
gesture?.cancel()
@ -6108,7 +6108,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})))
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.passthroughTouchEvent = { sourceView, point in
guard let strongSelf = self else {
return .ignore
@ -6223,7 +6223,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(id: .id(index.id), highlight: false, timecode: nil), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(items: items)), gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
}
)
@ -7307,7 +7307,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
})))
}
let controller = ContextController(account: primary.0.account, presentationData: self.presentationData, source: .extracted(SettingsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
let controller = ContextController(account: primary.0.account, presentationData: self.presentationData, source: .extracted(SettingsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
}