mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Various Improvements
This commit is contained in:
parent
be6adb3b06
commit
21d5e6452c
@ -7075,3 +7075,5 @@ Sorry for the inconvenience.";
|
||||
"AuthSessions.View.AcceptSecretChatsTitle" = "Incoming Secret Chats";
|
||||
"AuthSessions.View.AcceptSecretChats" = "Accept on This Device";
|
||||
"AuthSessions.View.AcceptSecretChatsInfo" = "You can disable the acception of incoming secret chats on this device.";
|
||||
|
||||
"Conversation.SendMesageAs" = "Send Message As...";
|
||||
|
@ -4,7 +4,7 @@ import Display
|
||||
import TelegramPresentationData
|
||||
import SwiftSignalKit
|
||||
|
||||
enum ContextActionSibling {
|
||||
public enum ContextActionSibling {
|
||||
case none
|
||||
case item
|
||||
case separator
|
||||
@ -13,10 +13,11 @@ enum ContextActionSibling {
|
||||
public protocol ContextActionNodeProtocol: ASDisplayNode {
|
||||
func setIsHighlighted(_ value: Bool)
|
||||
func performAction()
|
||||
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol
|
||||
var isActionEnabled: Bool { get }
|
||||
}
|
||||
|
||||
final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
private var presentationData: PresentationData
|
||||
private(set) var action: ContextMenuActionItem
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
@ -37,11 +38,11 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
|
||||
private var pointerInteraction: PointerInteraction?
|
||||
|
||||
var isActionEnabled: Bool {
|
||||
public var isActionEnabled: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) {
|
||||
public init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.action = action
|
||||
self.getController = getController
|
||||
@ -81,7 +82,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
titleFont = customFont
|
||||
}
|
||||
|
||||
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor)
|
||||
|
||||
@ -181,7 +182,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
self.iconDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
|
||||
@ -195,7 +196,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
})
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, previous: ContextActionSibling, next: ContextActionSibling) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
public func updateLayout(constrainedWidth: CGFloat, previous: ContextActionSibling, next: ContextActionSibling) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let verticalInset: CGFloat = 12.0
|
||||
@ -272,7 +273,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
public func updateTheme(presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||
@ -350,7 +351,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
self.requestLayout()
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
public func performAction() {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
@ -368,11 +369,15 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
))
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
public func setIsHighlighted(_ value: Bool) {
|
||||
if value && self.buttonNode.isUserInteractionEnabled {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
} else {
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
public func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
gesture.isEnabled = self.panSelectionGestureEnabled
|
||||
}
|
||||
|
||||
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var minActionsWidth: CGFloat = 250.0
|
||||
if let minimalWidth = minimalWidth, minimalWidth > minActionsWidth {
|
||||
minActionsWidth = minimalWidth
|
||||
@ -250,7 +250,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
heightsAndCompletions.append((minSize.height, complete))
|
||||
contentHeight += minSize.height
|
||||
case let .custom(itemNode):
|
||||
let (minSize, complete) = itemNode.updateLayout(constrainedWidth: constrainedWidth)
|
||||
let (minSize, complete) = itemNode.updateLayout(constrainedWidth: constrainedWidth, constrainedHeight: constrainedHeight)
|
||||
maxWidth = max(maxWidth, minSize.width)
|
||||
heightsAndCompletions.append((minSize.height, complete))
|
||||
contentHeight += minSize.height
|
||||
@ -327,7 +327,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
}
|
||||
case let .custom(node):
|
||||
if let node = node as? ContextActionNodeProtocol, node.frame.contains(point) {
|
||||
return node
|
||||
return node.actionNode(at: self.convert(point, to: node))
|
||||
}
|
||||
default:
|
||||
break
|
||||
@ -550,17 +550,17 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
self.addSubnode(self.scrollNode)
|
||||
}
|
||||
|
||||
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var widthClass = widthClass
|
||||
if !self.blurBackground {
|
||||
widthClass = .regular
|
||||
}
|
||||
|
||||
var contentSize = CGSize()
|
||||
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, minimalWidth: nil, transition: transition)
|
||||
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, constrainedHeight: constrainedHeight, minimalWidth: nil, transition: transition)
|
||||
|
||||
if let additionalActionsNode = self.additionalActionsNode, let additionalShadowNode = self.additionalShadowNode {
|
||||
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, minimalWidth: actionsSize.width, transition: transition)
|
||||
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, constrainedHeight: constrainedHeight, minimalWidth: actionsSize.width, transition: transition)
|
||||
contentSize = additionalActionsSize
|
||||
|
||||
let bounds = CGRect(origin: CGPoint(), size: additionalActionsSize)
|
||||
|
@ -144,7 +144,7 @@ public final class ContextMenuActionItem {
|
||||
}
|
||||
|
||||
public protocol ContextMenuCustomNode: ASDisplayNode {
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void)
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void)
|
||||
func updateTheme(presentationData: PresentationData)
|
||||
}
|
||||
|
||||
@ -1303,7 +1303,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, transition: actionsContainerTransition)
|
||||
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
|
||||
let adjustedActionsSize = realActionsSize
|
||||
|
||||
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
|
||||
@ -1390,7 +1390,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, transition: actionsContainerTransition)
|
||||
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
|
||||
let adjustedActionsSize = realActionsSize
|
||||
|
||||
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
|
||||
@ -1555,7 +1555,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, transition: actionsContainerTransition)
|
||||
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: constrainedWidth - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition)
|
||||
let contentScale = (constrainedWidth - actionsSideInset * 2.0) / constrainedWidth
|
||||
var contentUnscaledSize: CGSize
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
|
@ -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, transition: .immediate)
|
||||
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: .immediate)
|
||||
|
||||
let containerFrame: CGRect
|
||||
let actionsFrame: CGRect
|
||||
|
@ -258,6 +258,8 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
|
||||
self.terminateButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: self.presentationData.theme.list.itemDestructiveColor), font: .regular, height: 44.0, cornerRadius: 11.0, gloss: false)
|
||||
|
||||
var hasSecretChats = false
|
||||
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let title: String
|
||||
let subtitle: String
|
||||
@ -314,6 +316,10 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
}
|
||||
|
||||
self.secretChatsSwitchNode.isOn = session.flags.contains(.acceptsSecretChats)
|
||||
|
||||
if !session.flags.contains(.passwordPending) && ![2040, 2496].contains(session.apiId) {
|
||||
hasSecretChats = true
|
||||
}
|
||||
case let .website(website, peer):
|
||||
self.terminateButton.title = self.presentationData.strings.AuthSessions_View_Logout
|
||||
|
||||
@ -421,7 +427,7 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
self.animationNode.flatMap { self.contentContainerNode.addSubnode($0) }
|
||||
self.avatarNode.flatMap { self.contentContainerNode.addSubnode($0) }
|
||||
|
||||
if case .session = subject {
|
||||
if hasSecretChats {
|
||||
self.contentContainerNode.addSubnode(self.secretChatsBackgroundNode)
|
||||
self.contentContainerNode.addSubnode(self.secretChatsHeaderNode)
|
||||
self.contentContainerNode.addSubnode(self.secretChatsTitleNode)
|
||||
|
@ -63,7 +63,7 @@ private final class VoiceChatInfoContextItemNode: ASDisplayNode, ContextMenuCust
|
||||
self.addSubnode(self.iconNode)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let verticalInset: CGFloat = 12.0
|
||||
|
@ -210,7 +210,7 @@ private final class VoiceChatRecordingContextItemNode: ASDisplayNode, ContextMen
|
||||
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: sideInset, y: self.statusNode.frame.minY), size: statusSize))
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let verticalInset: CGFloat = 12.0
|
||||
|
@ -117,7 +117,7 @@ private final class VoiceChatShareScreenContextItemNode: ASDisplayNode, ContextM
|
||||
}
|
||||
|
||||
private var validLayout: CGSize?
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let verticalInset: CGFloat = 12.0
|
||||
|
@ -126,7 +126,7 @@ private final class VoiceChatVolumeContextItemNode: ASDisplayNode, ContextMenuCu
|
||||
let _ = self.foregroundTextNode.updateLayout(CGSize(width: 70.0, height: .greatestFiniteMagnitude))
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let valueWidth: CGFloat = 70.0
|
||||
let height: CGFloat = 45.0
|
||||
|
||||
|
@ -7534,62 +7534,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
let myPeerId = strongSelf.presentationInterfaceState.currentSendAsPeerId ?? defaultMyPeerId
|
||||
|
||||
let avatarSize = CGSize(width: 28.0, height: 28.0)
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.custom(ChatSendAsPeerTitleContextItem(text: "SEND MESSAGE AS..."), false))
|
||||
|
||||
for peer in peers {
|
||||
var subtitle: String?
|
||||
if peer.peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount
|
||||
} else if let subscribers = peer.subscribers {
|
||||
if let peer = peer.peer as? TelegramChannel {
|
||||
if case .broadcast = peer.info {
|
||||
subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers)
|
||||
} else {
|
||||
subtitle = strongSelf.presentationData.strings.VoiceChat_DiscussionGroup
|
||||
}
|
||||
} else {
|
||||
subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers)
|
||||
}
|
||||
}
|
||||
|
||||
let isSelected = peer.peer.id == myPeerId
|
||||
let extendedAvatarSize = CGSize(width: 35.0, height: 35.0)
|
||||
let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)
|
||||
|> map { image -> UIImage? in
|
||||
if isSelected, let image = image {
|
||||
return generateImage(extendedAvatarSize, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
context.draw(image.cgImage!, in: CGRect(x: (extendedAvatarSize.width - avatarSize.width) / 2.0, y: (extendedAvatarSize.height - avatarSize.height) / 2.0, width: avatarSize.width, height: avatarSize.height))
|
||||
|
||||
let lineWidth = 1.0 + UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(strongSelf.presentationData.theme.actionSheet.controlAccentColor.cgColor)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||
})
|
||||
} else {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { _, f in
|
||||
f(.default)
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if peer.peer.id != myPeerId {
|
||||
let _ = strongSelf.context.engine.peers.updatePeerSendAsPeer(peerId: peerId, sendAs: peer.peer.id).start()
|
||||
}
|
||||
})))
|
||||
}
|
||||
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)), items: .single(ContextController.Items(items: items)), gesture: gesture)
|
||||
contextController.dismissed = { [weak self] in
|
||||
|
@ -1648,7 +1648,7 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
|
||||
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: self.statusNode.frame.minX, y: self.statusNode.frame.minY), size: statusSize))
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let verticalInset: CGFloat = 12.0
|
||||
@ -1717,6 +1717,10 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatReadReportContextItem: ContextMenuCustomItem {
|
||||
@ -1878,11 +1882,11 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
|
||||
|
||||
self.currentStats = stats
|
||||
|
||||
let (_, apply) = self.updateLayout(constrainedWidth: calculatedWidth)
|
||||
let (_, apply) = self.updateLayout(constrainedWidth: calculatedWidth, constrainedHeight: size.height)
|
||||
apply(size, transition)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 14.0
|
||||
let verticalInset: CGFloat = 12.0
|
||||
|
||||
@ -2039,6 +2043,10 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
private func stringForRemainingTime(_ duration: Int32, strings: PresentationStrings) -> String {
|
||||
|
@ -0,0 +1,231 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AppBundle
|
||||
import ContextUI
|
||||
import TelegramStringFormatting
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
|
||||
final class ChatSendAsPeerListContextItem: ContextMenuCustomItem {
|
||||
let context: AccountContext
|
||||
let chatPeerId: PeerId
|
||||
let peers: [FoundPeer]
|
||||
let selectedPeerId: PeerId?
|
||||
|
||||
init(context: AccountContext, chatPeerId: PeerId, peers: [FoundPeer], selectedPeerId: PeerId?) {
|
||||
self.context = context
|
||||
self.chatPeerId = chatPeerId
|
||||
self.peers = peers
|
||||
self.selectedPeerId = selectedPeerId
|
||||
}
|
||||
|
||||
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return ChatSendAsPeerListContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, UIScrollViewDelegate {
|
||||
private let item: ChatSendAsPeerListContextItem
|
||||
private let presentationData: PresentationData
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private let actionNodes: [ContextActionNode]
|
||||
private let separatorNodes: [ASDisplayNode]
|
||||
private let selectedItemIndex: Int
|
||||
private var initialized = false
|
||||
|
||||
init(presentationData: PresentationData, item: ChatSendAsPeerListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
let avatarSize = CGSize(width: 30.0, height: 30.0)
|
||||
|
||||
var actionNodes: [ContextActionNode] = []
|
||||
var separatorNodes: [ASDisplayNode] = []
|
||||
|
||||
var selectedItemIndex = 0
|
||||
var i = 0
|
||||
for peer in item.peers {
|
||||
var subtitle: String?
|
||||
if peer.peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
subtitle = presentationData.strings.VoiceChat_PersonalAccount
|
||||
} else if let subscribers = peer.subscribers {
|
||||
if let peer = peer.peer as? TelegramChannel {
|
||||
if case .broadcast = peer.info {
|
||||
subtitle = presentationData.strings.Conversation_StatusSubscribers(subscribers)
|
||||
} else {
|
||||
subtitle = presentationData.strings.VoiceChat_DiscussionGroup
|
||||
}
|
||||
} else {
|
||||
subtitle = presentationData.strings.Conversation_StatusMembers(subscribers)
|
||||
}
|
||||
}
|
||||
|
||||
let isSelected = peer.peer.id == item.selectedPeerId
|
||||
if isSelected {
|
||||
selectedItemIndex = i
|
||||
}
|
||||
let extendedAvatarSize = CGSize(width: 35.0, height: 35.0)
|
||||
let avatarSignal = peerAvatarCompleteImage(account: item.context.account, peer: EnginePeer(peer.peer), size: avatarSize)
|
||||
|> map { image -> UIImage? in
|
||||
if isSelected, let image = image {
|
||||
return generateImage(extendedAvatarSize, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
context.draw(image.cgImage!, in: CGRect(x: (extendedAvatarSize.width - avatarSize.width) / 2.0, y: (extendedAvatarSize.height - avatarSize.height) / 2.0, width: avatarSize.width, height: avatarSize.height))
|
||||
|
||||
let lineWidth = 1.0 + UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(presentationData.theme.actionSheet.controlAccentColor.cgColor)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||
})
|
||||
} else {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
let action = ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { _, f in
|
||||
f(.default)
|
||||
|
||||
if peer.peer.id != item.selectedPeerId {
|
||||
let _ = item.context.engine.peers.updatePeerSendAsPeer(peerId: item.chatPeerId, sendAs: peer.peer.id).start()
|
||||
}
|
||||
})
|
||||
let actionNode = ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected, requestLayout: {}, requestUpdateAction: { _, _ in
|
||||
})
|
||||
actionNodes.append(actionNode)
|
||||
if actionNodes.count != item.peers.count {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
separatorNodes.append(separatorNode)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
self.actionNodes = actionNodes
|
||||
self.separatorNodes = separatorNodes
|
||||
self.selectedItemIndex = selectedItemIndex
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
for separatorNode in self.separatorNodes {
|
||||
self.scrollNode.addSubnode(separatorNode)
|
||||
}
|
||||
for actionNode in self.actionNodes {
|
||||
self.scrollNode.addSubnode(actionNode)
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.scrollNode.view.delegate = self
|
||||
self.scrollNode.view.alwaysBounceVertical = false
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let minActionsWidth: CGFloat = 250.0
|
||||
var maxWidth: CGFloat = 0.0
|
||||
var contentHeight: CGFloat = 0.0
|
||||
var heightsAndCompletions: [(CGFloat, (CGSize, ContainedViewLayoutTransition) -> Void)?] = []
|
||||
for i in 0 ..< self.actionNodes.count {
|
||||
let itemNode = self.actionNodes[i]
|
||||
let previous: ContextActionSibling
|
||||
let next: ContextActionSibling
|
||||
if i == 0 {
|
||||
previous = .none
|
||||
} else {
|
||||
previous = .item
|
||||
}
|
||||
if i == self.actionNodes.count - 1 {
|
||||
next = .none
|
||||
} else {
|
||||
next = .item
|
||||
}
|
||||
let (minSize, complete) = itemNode.updateLayout(constrainedWidth: constrainedWidth, previous: previous, next: next)
|
||||
maxWidth = max(maxWidth, minSize.width)
|
||||
heightsAndCompletions.append((minSize.height, complete))
|
||||
contentHeight += minSize.height
|
||||
}
|
||||
|
||||
maxWidth = max(maxWidth, minActionsWidth)
|
||||
|
||||
let maxHeight: CGFloat = min(380.0, constrainedHeight - 108.0)
|
||||
|
||||
return (CGSize(width: maxWidth, height: min(maxHeight, contentHeight)), { size, transition in
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
for i in 0 ..< heightsAndCompletions.count {
|
||||
let itemNode = self.actionNodes[i]
|
||||
if let (itemHeight, itemCompletion) = heightsAndCompletions[i] {
|
||||
let itemSize = CGSize(width: maxWidth, height: itemHeight)
|
||||
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: itemSize))
|
||||
itemCompletion(itemSize, transition)
|
||||
verticalOffset += itemHeight
|
||||
}
|
||||
|
||||
if i < self.actionNodes.count - 1 {
|
||||
let separatorNode = self.separatorNodes[i]
|
||||
separatorNode.frame = CGRect(x: 0, y: verticalOffset, width: size.width, height: UIScreenPixel)
|
||||
}
|
||||
}
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||
|
||||
if !self.initialized {
|
||||
self.initialized = true
|
||||
|
||||
let rect = self.actionNodes[self.selectedItemIndex].frame.insetBy(dx: 0.0, dy: -20.0)
|
||||
self.scrollNode.view.scrollRectToVisible(rect, animated: false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(presentationData: presentationData)
|
||||
}
|
||||
}
|
||||
|
||||
var isActionEnabled: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
}
|
||||
|
||||
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||
for actionNode in self.actionNodes {
|
||||
let frame = actionNode.convert(actionNode.bounds, to: self)
|
||||
if frame.contains(point) {
|
||||
return actionNode
|
||||
}
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.setIsHighlighted(false)
|
||||
}
|
||||
}
|
||||
}
|
@ -54,7 +54,7 @@ private final class ChatSendAsPeerTitleContextItemNode: ASDisplayNode, ContextMe
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - sideInset, height: .greatestFiniteMagnitude))
|
||||
|
Loading…
x
Reference in New Issue
Block a user