mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-17 08:41:10 +00:00
Various improvements
This commit is contained in:
parent
1b888befa1
commit
c5223959b2
@ -14449,3 +14449,7 @@ Sorry for the inconvenience.";
|
||||
"SuggestPost.SetTimeFormat.Date" = "%@";
|
||||
"SuggestPost.SetTimeFormat.TodayAt" = "Today at %@";
|
||||
"SuggestPost.SetTimeFormat.TomorrowAt" = "Tomorrow at %@";
|
||||
|
||||
"Chat.TodoItemCompletionTimestamp.Date" = "completed %@";
|
||||
"Chat.TodoItemCompletionTimestamp.TodayAt" = "completed today at %@";
|
||||
"Chat.TodoItemCompletionTimestamp.YesterdayAt" = "completed yesterday at %@";
|
||||
|
@ -1272,7 +1272,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
||||
}, openBoostToUnrestrict: {
|
||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _ in
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
@ -664,6 +664,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
if #available(iOS 13.0, *) {
|
||||
let appleIdProvider = ASAuthorizationAppleIDProvider()
|
||||
let request = appleIdProvider.createRequest()
|
||||
request.requestedScopes = [.email]
|
||||
request.user = number
|
||||
|
||||
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
|
||||
|
@ -101,6 +101,7 @@ public final class BrowserBookmarksScreen: ViewController {
|
||||
}, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, longTap: { _, _ in
|
||||
}, todoItemLongTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _, _ in
|
||||
}, openSearch: {
|
||||
}, setupReply: { _ in
|
||||
|
@ -179,7 +179,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
public let openBoostToUnrestrict: () -> Void
|
||||
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
|
||||
public let dismissAllTooltips: () -> Void
|
||||
public let editTodoMessage: (MessageId, Bool) -> Void
|
||||
public let editTodoMessage: (MessageId, Int32?, Bool) -> Void
|
||||
public let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||
public let chatController: () -> ViewController?
|
||||
public let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||
@ -298,7 +298,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
openBoostToUnrestrict: @escaping () -> Void,
|
||||
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
|
||||
dismissAllTooltips: @escaping () -> Void,
|
||||
editTodoMessage: @escaping (MessageId, Bool) -> Void,
|
||||
editTodoMessage: @escaping (MessageId, Int32?, Bool) -> Void,
|
||||
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
|
||||
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
|
||||
toggleChatSidebarMode: @escaping () -> Void,
|
||||
@ -551,7 +551,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
}, openBoostToUnrestrict: {
|
||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _ in
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
@ -132,7 +132,7 @@ public final class TelegramMediaTodo: Media, Equatable {
|
||||
return true
|
||||
}
|
||||
|
||||
func withUpdated(items: [TelegramMediaTodo.Item]) -> TelegramMediaTodo {
|
||||
public func withUpdated(items: [TelegramMediaTodo.Item]) -> TelegramMediaTodo {
|
||||
return TelegramMediaTodo(
|
||||
flags: self.flags,
|
||||
text: self.text,
|
||||
|
@ -28,6 +28,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/Chat/ShimmeringLinkNode",
|
||||
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -17,6 +17,7 @@ import ChatMessageItemCommon
|
||||
import PollBubbleTimerNode
|
||||
import TextNodeWithEntities
|
||||
import ShimmeringLinkNode
|
||||
import ChatControllerInteraction
|
||||
|
||||
private final class ChatMessageTaskOptionRadioNodeParameters: NSObject {
|
||||
let timestamp: Double
|
||||
@ -372,16 +373,20 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio
|
||||
}
|
||||
|
||||
private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
fileprivate let highlightedBackgroundNode: ASDisplayNode
|
||||
private var avatarNode: AvatarNode?
|
||||
private(set) var radioNode: ChatMessageTaskOptionRadioNode?
|
||||
private var iconNode: ASImageNode?
|
||||
fileprivate var titleNode: TextNodeWithEntities?
|
||||
fileprivate var nameNode: TextNode?
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
let separatorNode: ASDisplayNode
|
||||
var option: TelegramMediaTodo.Item?
|
||||
var pressed: (() -> Void)?
|
||||
var selectionUpdated: (() -> Void)?
|
||||
|
||||
var longTapped: (() -> Void)?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
weak var previousOptionNode: ChatMessageTodoItemNode?
|
||||
@ -389,6 +394,8 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
private var canMark = false
|
||||
private var isPremium = false
|
||||
|
||||
private var ignoreNextTap = false
|
||||
|
||||
var visibilityRect: CGRect? {
|
||||
didSet {
|
||||
if self.visibilityRect != oldValue {
|
||||
@ -438,6 +445,13 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
|
||||
strongSelf.previousOptionNode?.separatorNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.previousOptionNode?.separatorNode.alpha = 0.0
|
||||
|
||||
Queue.mainQueue().after(0.8) {
|
||||
if strongSelf.highlightedBackgroundNode.alpha == 1.0 {
|
||||
strongSelf.ignoreNextTap = true
|
||||
strongSelf.longTapped?()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { finished in
|
||||
@ -459,6 +473,10 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
guard !self.ignoreNextTap else {
|
||||
self.ignoreNextTap = false
|
||||
return
|
||||
}
|
||||
if let radioNode = self.radioNode, let isChecked = radioNode.isChecked, self.canMark, self.isPremium {
|
||||
radioNode.updateIsChecked(!isChecked, animated: true)
|
||||
self.selectionUpdated?()
|
||||
@ -469,6 +487,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
|
||||
static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) {
|
||||
let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode)
|
||||
let makeNameLayout = TextNode.asyncLayout(maybeNode?.nameNode)
|
||||
|
||||
return { context, presentationData, message, todo, option, completion, translation, constrainedWidth in
|
||||
var canMark = false
|
||||
@ -480,6 +499,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
let rightInset: CGFloat = 12.0
|
||||
|
||||
let incoming = message.effectivelyIncoming(context.account.peerId)
|
||||
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
|
||||
|
||||
var optionText = option.text
|
||||
var optionEntities = option.entities
|
||||
@ -492,7 +512,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
optionEntities.append(MessageTextEntity(range: 0 ..< (optionText as NSString).length, type: .Strikethrough))
|
||||
}
|
||||
|
||||
let optionTextColor: UIColor = incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||
let optionTextColor: UIColor = messageTheme.primaryTextColor
|
||||
let optionAttributedText = stringWithAppliedEntities(
|
||||
optionText,
|
||||
entities: optionEntities,
|
||||
@ -510,6 +530,13 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: optionAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: max(1.0, constrainedWidth - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
|
||||
|
||||
let nameLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
if let completion, let peer = message.peers[completion.completedBy], todo.flags.contains(.othersCanComplete) {
|
||||
nameLayoutAndApply = makeNameLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.regular(11.0), textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: max(1.0, constrainedWidth - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
|
||||
} else {
|
||||
nameLayoutAndApply = nil
|
||||
}
|
||||
|
||||
let contentHeight: CGFloat = max(46.0, titleLayout.size.height + 22.0)
|
||||
|
||||
let isSelectable: Bool = true
|
||||
@ -558,12 +585,16 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
placeholderColor: incoming ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor,
|
||||
attemptSynchronous: attemptSynchronous
|
||||
))
|
||||
let titleNodeFrame: CGRect
|
||||
var titleNodeFrame: CGRect
|
||||
if titleLayout.hasRTL {
|
||||
titleNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - titleLayout.size.width, y: 12.0), size: titleLayout.size)
|
||||
} else {
|
||||
titleNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size)
|
||||
}
|
||||
if let _ = completion, canMark && todo.flags.contains(.othersCanComplete) {
|
||||
titleNodeFrame = titleNodeFrame.offsetBy(dx: 0.0, dy: -6.0)
|
||||
}
|
||||
|
||||
if node.titleNode !== titleNode {
|
||||
node.titleNode = titleNode
|
||||
node.addSubnode(titleNode.textNode)
|
||||
@ -573,8 +604,43 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
titleNode.visibilityRect = visibilityRect.offsetBy(dx: 0.0, dy: titleNodeFrame.minY)
|
||||
}
|
||||
}
|
||||
|
||||
let previousFrame = titleNode.textNode.frame
|
||||
titleNode.textNode.frame = titleNodeFrame
|
||||
|
||||
if animated, previousFrame != titleNodeFrame {
|
||||
titleNode.textNode.layer.animateFrame(from: previousFrame, to: titleNodeFrame, duration: 0.2)
|
||||
}
|
||||
|
||||
if let (nameLayout, nameApply) = nameLayoutAndApply {
|
||||
var nameNodeFrame: CGRect
|
||||
if titleLayout.hasRTL {
|
||||
nameNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - nameLayout.size.width, y: 26.0), size: nameLayout.size)
|
||||
} else {
|
||||
nameNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 26.0), size: nameLayout.size)
|
||||
}
|
||||
let nameNode = nameApply()
|
||||
if node.nameNode !== nameNode {
|
||||
node.nameNode = nameNode
|
||||
node.addSubnode(nameNode)
|
||||
nameNode.isUserInteractionEnabled = false
|
||||
|
||||
if animated {
|
||||
nameNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
nameNode.frame = nameNodeFrame
|
||||
} else if let nameNode = node.nameNode {
|
||||
node.nameNode = nil
|
||||
if animated {
|
||||
nameNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak nameNode] _ in
|
||||
nameNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
nameNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let completion, canMark && todo.flags.contains(.othersCanComplete) {
|
||||
let avatarNode: AvatarNode
|
||||
if let current = node.avatarNode {
|
||||
@ -882,9 +948,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
|
||||
|
||||
|
||||
var pollTitleText = todo?.text ?? ""
|
||||
var pollTitleEntities = todo?.textEntities ?? []
|
||||
var todoTitleText = todo?.text ?? ""
|
||||
var todoTitleEntities = todo?.textEntities ?? []
|
||||
var pollOptions: [TranslationMessageAttribute.Additional] = []
|
||||
|
||||
var isTranslating = false
|
||||
@ -892,8 +957,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
isTranslating = true
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage {
|
||||
pollTitleText = attribute.text
|
||||
pollTitleEntities = attribute.entities
|
||||
todoTitleText = attribute.text
|
||||
todoTitleEntities = attribute.entities
|
||||
pollOptions = attribute.additional
|
||||
isTranslating = false
|
||||
break
|
||||
@ -902,8 +967,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
let attributedText = stringWithAppliedEntities(
|
||||
pollTitleText,
|
||||
entities: pollTitleEntities,
|
||||
todoTitleText,
|
||||
entities: todoTitleEntities,
|
||||
baseColor: messageTheme.primaryTextColor,
|
||||
linkColor: messageTheme.linkTextColor,
|
||||
baseFont: item.presentationData.messageBoldFont,
|
||||
@ -1062,9 +1127,6 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var isRequesting = false
|
||||
if let todo, i < todo.items.count {
|
||||
isRequesting = false
|
||||
// if let inProgressOpaqueIds = item.controllerInteraction.pollActionState.pollMessageIdsInProgress[item.message.id] {
|
||||
// isRequesting = inProgressOpaqueIds.contains(poll.options[i].opaqueIdentifier)
|
||||
// }
|
||||
}
|
||||
let optionNode = apply(animation.isAnimated, isRequesting, synchronousLoad)
|
||||
let optionNodeFrame = CGRect(origin: CGPoint(x: layoutConstants.bubble.borderInset, y: verticalOffset), size: size)
|
||||
@ -1083,6 +1145,12 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
item.controllerInteraction.displayTodoToggleUnavailable(item.message.id)
|
||||
}
|
||||
optionNode.longTapped = { [weak optionNode] in
|
||||
guard let strongSelf = self, let item = strongSelf.item, let todoItem, let optionNode, let contentNode = strongSelf.contextContentNodeForItem(itemNode: optionNode) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.todoItemLongTap(todoItem.id, ChatControllerInteraction.LongTapParams(message: message, contentNode: contentNode, messageNode: strongSelf, progress: nil))
|
||||
}
|
||||
optionNode.frame = optionNodeFrame
|
||||
} else {
|
||||
animation.animator.updateFrame(layer: optionNode.layer, frame: optionNodeFrame, completion: nil)
|
||||
@ -1313,4 +1381,42 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func contextContentNodeForItem(itemNode: ChatMessageTodoItemNode) -> ContextExtractedContentContainingNode? {
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
let containingNode = ContextExtractedContentContainingNode()
|
||||
|
||||
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
||||
|
||||
itemNode.highlightedBackgroundNode.alpha = 0.0
|
||||
guard let snapshotView = itemNode.view.snapshotContentTree() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let backgroundNode = ASDisplayNode()
|
||||
backgroundNode.backgroundColor = (incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill).first ?? .black
|
||||
backgroundNode.clipsToBounds = true
|
||||
backgroundNode.cornerRadius = 10.0
|
||||
|
||||
let insets = UIEdgeInsets.zero
|
||||
let backgroundSize = CGSize(width: snapshotView.frame.width + insets.left + insets.right, height: snapshotView.frame.height + insets.top + insets.bottom)
|
||||
backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize)
|
||||
snapshotView.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: snapshotView.frame.size)
|
||||
backgroundNode.view.addSubview(snapshotView)
|
||||
|
||||
let origin = CGPoint(x: 3.0, y: 1.0) //self.backgroundNode.frame.minX + 3.0, y: 1.0)
|
||||
|
||||
containingNode.frame = CGRect(origin: origin, size: CGSize(width: backgroundSize.width, height: backgroundSize.height + 20.0))
|
||||
containingNode.contentNode.frame = CGRect(origin: .zero, size: backgroundSize)
|
||||
containingNode.contentRect = CGRect(origin: .zero, size: backgroundSize)
|
||||
containingNode.contentNode.addSubnode(backgroundNode)
|
||||
|
||||
containingNode.contentNode.alpha = 0.0
|
||||
|
||||
self.addSubnode(containingNode)
|
||||
|
||||
return containingNode
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, openBoostToUnrestrict: {
|
||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _ in
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
@ -569,6 +569,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
break
|
||||
}
|
||||
}
|
||||
}, todoItemLongTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _, _ in
|
||||
}, openSearch: {
|
||||
}, setupReply: { _ in
|
||||
|
@ -429,7 +429,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
||||
}, chatControllerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
|
||||
}, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
|
||||
}, longTap: { _, _ in }, todoItemLongTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
|
||||
}, canSetupReply: { _ in
|
||||
return .none
|
||||
}, canSendMessages: {
|
||||
|
@ -215,6 +215,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
public let callPeer: (PeerId, Bool) -> Void
|
||||
public let openConferenceCall: (Message) -> Void
|
||||
public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void
|
||||
public let todoItemLongTap: (Int32, LongTapParams?) -> Void
|
||||
public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void
|
||||
public let openSearch: () -> Void
|
||||
public let setupReply: (MessageId) -> Void
|
||||
@ -379,6 +380,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
callPeer: @escaping (PeerId, Bool) -> Void,
|
||||
openConferenceCall: @escaping (Message) -> Void,
|
||||
longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void,
|
||||
todoItemLongTap: @escaping (Int32, LongTapParams?) -> Void,
|
||||
openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void,
|
||||
openSearch: @escaping () -> Void,
|
||||
setupReply: @escaping (MessageId) -> Void,
|
||||
@ -499,6 +501,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
self.callPeer = callPeer
|
||||
self.openConferenceCall = openConferenceCall
|
||||
self.longTap = longTap
|
||||
self.todoItemLongTap = todoItemLongTap
|
||||
self.openCheckoutOrReceipt = openCheckoutOrReceipt
|
||||
self.openSearch = openSearch
|
||||
self.setupReply = setupReply
|
||||
|
@ -982,6 +982,11 @@ final class ComposeTodoScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
var focusedIndex: Int?
|
||||
if isFirstTime, let focusedId = component.initialData.focusedId {
|
||||
focusedIndex = self.todoItems.firstIndex(where: { $0.id == focusedId })
|
||||
}
|
||||
|
||||
for i in 0 ..< todoItemsSectionReadyItems.count {
|
||||
var activate = false
|
||||
let placeholder: String
|
||||
@ -994,6 +999,10 @@ final class ComposeTodoScreenComponent: Component {
|
||||
placeholder = "Task"
|
||||
}
|
||||
|
||||
if let focusedIndex, i == focusedIndex {
|
||||
activate = true
|
||||
}
|
||||
|
||||
if let itemView = todoItemsSectionReadyItems[i].itemView.contents.view as? ListComposePollOptionComponent.View {
|
||||
itemView.updateCustomPlaceholder(value: placeholder, size: todoItemsSectionReadyItems[i].size, transition: todoItemsSectionReadyItems[i].transition)
|
||||
|
||||
@ -1527,6 +1536,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
|
||||
fileprivate let maxTodoItemLength: Int
|
||||
fileprivate let maxTodoItemsCount: Int
|
||||
fileprivate let existingTodo: TelegramMediaTodo?
|
||||
fileprivate let focusedId: Int32?
|
||||
fileprivate let append: Bool
|
||||
fileprivate let canEdit: Bool
|
||||
|
||||
@ -1535,6 +1545,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
|
||||
maxTodoItemLength: Int,
|
||||
maxTodoItemsCount: Int,
|
||||
existingTodo: TelegramMediaTodo?,
|
||||
focusedId: Int32?,
|
||||
append: Bool,
|
||||
canEdit: Bool
|
||||
) {
|
||||
@ -1542,6 +1553,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
|
||||
self.maxTodoItemLength = maxTodoItemLength
|
||||
self.maxTodoItemsCount = maxTodoItemsCount
|
||||
self.existingTodo = existingTodo
|
||||
self.focusedId = focusedId
|
||||
self.append = append
|
||||
self.canEdit = canEdit
|
||||
}
|
||||
@ -1639,7 +1651,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
|
||||
deinit {
|
||||
}
|
||||
|
||||
public static func initialData(context: AccountContext, existingTodo: TelegramMediaTodo? = nil, append: Bool = false, canEdit: Bool = false) -> InitialData {
|
||||
public static func initialData(context: AccountContext, existingTodo: TelegramMediaTodo? = nil, focusedId: Int32? = nil, append: Bool = false, canEdit: Bool = false) -> InitialData {
|
||||
var maxTodoTextLength: Int = 32
|
||||
var maxTodoItemLength: Int = 64
|
||||
var maxTodoItemsCount: Int = 30
|
||||
@ -1659,6 +1671,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont
|
||||
maxTodoItemLength: maxTodoItemLength,
|
||||
maxTodoItemsCount: maxTodoItemsCount,
|
||||
existingTodo: existingTodo,
|
||||
focusedId: focusedId,
|
||||
append: append,
|
||||
canEdit: canEdit
|
||||
)
|
||||
|
@ -437,7 +437,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
}, openBoostToUnrestrict: {
|
||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _ in
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
@ -3788,6 +3788,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
default:
|
||||
break
|
||||
}
|
||||
}, todoItemLongTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _, _ in
|
||||
}, openSearch: {
|
||||
}, setupReply: { _ in
|
||||
|
@ -825,7 +825,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}, openBoostToUnrestrict: {
|
||||
}, updateRecordingTrimRange: { _, _, _, _ in
|
||||
}, dismissAllTooltips: {
|
||||
}, editTodoMessage: { _, _ in
|
||||
}, editTodoMessage: { _, _, _ in
|
||||
}, updateHistoryFilter: { _ in
|
||||
}, updateChatLocationThread: { _, _ in
|
||||
}, toggleChatSidebarMode: {
|
||||
|
@ -4173,11 +4173,11 @@ extension ChatControllerImpl {
|
||||
return
|
||||
}
|
||||
self.dismissAllTooltips()
|
||||
}, editTodoMessage: { [weak self] messageId, append in
|
||||
}, editTodoMessage: { [weak self] messageId, itemId, append in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openTodoEditing(messageId: messageId, append: append)
|
||||
self.openTodoEditing(messageId: messageId, itemId: itemId, append: append)
|
||||
}, updateHistoryFilter: { [weak self] update in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -0,0 +1,192 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ContextUI
|
||||
import UndoUI
|
||||
import AccountContext
|
||||
import ChatMessageItemView
|
||||
import ChatMessageItemCommon
|
||||
import AvatarNode
|
||||
import ChatControllerInteraction
|
||||
import Pasteboard
|
||||
import TelegramStringFormatting
|
||||
import TelegramPresentationData
|
||||
|
||||
extension ChatControllerImpl {
|
||||
func openTodoItemContextMenu(todoItemId: Int32, params: ChatControllerInteraction.LongTapParams) -> Void {
|
||||
guard let message = params.message, let todo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo, let todoItem = todo.items.first(where: { $0.id == todoItemId }), let contentNode = params.contentNode else {
|
||||
return
|
||||
}
|
||||
|
||||
let completion = todo.completions.first(where: { $0.id == todoItemId })
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
|
||||
|
||||
var canMark = false
|
||||
if (todo.flags.contains(.othersCanComplete) || message.author?.id == context.account.peerId) {
|
||||
canMark = true
|
||||
}
|
||||
let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message)
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
if let completion {
|
||||
let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: completion.date, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat(
|
||||
dateFormatString: { value in
|
||||
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_Date(value).string, ranges: [])
|
||||
},
|
||||
tomorrowFormatString: { value in
|
||||
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_TodayAt(value).string, ranges: [])
|
||||
},
|
||||
todayFormatString: { value in
|
||||
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_TodayAt(value).string, ranges: [])
|
||||
},
|
||||
yesterdayFormatString: { value in
|
||||
return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_TodoItemCompletionTimestamp_YesterdayAt(value).string, ranges: [])
|
||||
}
|
||||
)).string
|
||||
|
||||
let nop: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
items.append(.action(ContextMenuActionItem(text: dateText, textFont: .small, icon: { _ in return nil }, action: nop)))
|
||||
items.append(.separator)
|
||||
|
||||
if canMark {
|
||||
items.append(.action(ContextMenuActionItem(text: "Uncheck", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [], incompletedIds: [todoItemId]).start()
|
||||
})))
|
||||
}
|
||||
} else {
|
||||
if canMark {
|
||||
items.append(.action(ContextMenuActionItem(text: "Check", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [todoItemId], incompletedIds: []).start()
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Copy", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
storeMessageTextInPasteboard(todoItem.text, entities: todoItem.entities)
|
||||
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
})))
|
||||
|
||||
var isReplyThreadHead = false
|
||||
if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation {
|
||||
isReplyThreadHead = message.id == replyThreadMessage.effectiveTopId
|
||||
}
|
||||
|
||||
if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !channel.isMonoForum, !isReplyThreadHead {
|
||||
items.append(.action(ContextMenuActionItem(text: "Copy Link", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var threadMessageId: MessageId?
|
||||
if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation {
|
||||
threadMessageId = replyThreadMessage.effectiveMessageId
|
||||
}
|
||||
let _ = (self.context.engine.messages.exportMessageLink(peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil)
|
||||
|> map { result -> String? in
|
||||
return result
|
||||
}
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] link in
|
||||
guard let self, let link else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = link + "?task=\(todoItemId)"
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var warnAboutPrivate = false
|
||||
if case .peer = self.presentationInterfaceState.chatLocation {
|
||||
if channel.addressName == nil {
|
||||
warnAboutPrivate = true
|
||||
}
|
||||
}
|
||||
Queue.mainQueue().after(0.2, {
|
||||
if warnAboutPrivate {
|
||||
self.controllerInteraction?.displayUndo(.linkCopied(title: nil, text: presentationData.strings.Conversation_PrivateMessageLinkCopiedLong))
|
||||
} else {
|
||||
self.controllerInteraction?.displayUndo(.linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied))
|
||||
}
|
||||
})
|
||||
})
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
|
||||
if canEdit {
|
||||
items.append(.action(ContextMenuActionItem(text: "Edit Item", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.interfaceInteraction?.editTodoMessage(message.id, todoItemId, false)
|
||||
})))
|
||||
|
||||
if todo.items.count > 1 {
|
||||
items.append(.separator)
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Delete Item", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedItems = todo.items.filter { $0.id != todoItemId }
|
||||
let updatedTodo = todo.withUpdated(items: updatedItems)
|
||||
|
||||
let _ = self.context.engine.messages.requestEditMessage(
|
||||
messageId: message.id,
|
||||
text: "",
|
||||
media: .update(.standalone(media: updatedTodo)),
|
||||
entities: nil,
|
||||
inlineStickers: [:]
|
||||
).start()
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
self.canReadHistory.set(false)
|
||||
|
||||
let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false)
|
||||
controller.dismissed = { [weak self] in
|
||||
self?.canReadHistory.set(true)
|
||||
}
|
||||
|
||||
self.window?.presentInGlobalOverlay(controller)
|
||||
}
|
||||
}
|
@ -15,23 +15,10 @@ import ChatControllerInteraction
|
||||
|
||||
extension ChatControllerImpl {
|
||||
func openMentionContextMenu(username: String, peerId: EnginePeer.Id?, params: ChatControllerInteraction.LongTapParams) -> Void {
|
||||
guard let message = params.message, let contentNode = params.contentNode else {
|
||||
guard let _ = params.message, let contentNode = params.contentNode else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) else {
|
||||
return
|
||||
}
|
||||
|
||||
var updatedMessages = messages
|
||||
for i in 0 ..< updatedMessages.count {
|
||||
if updatedMessages[i].id == message.id {
|
||||
let message = updatedMessages.remove(at: i)
|
||||
updatedMessages.insert(message, at: 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
|
||||
|
@ -2891,12 +2891,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.joinConferenceCall(message: EngineMessage(message))
|
||||
}, longTap: { [weak self] action, params in
|
||||
if let self {
|
||||
self.openLinkLongTap(action, params: params)
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openLinkLongTap(action, params: params)
|
||||
}, todoItemLongTap: { [weak self] todoItemId, params in
|
||||
guard let self, let params else {
|
||||
return
|
||||
}
|
||||
self.openTodoItemContextMenu(todoItemId: todoItemId, params: params)
|
||||
}, openCheckoutOrReceipt: { [weak self] messageId, params in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -2094,7 +2094,7 @@ extension ChatControllerImpl {
|
||||
)
|
||||
}
|
||||
|
||||
func openTodoEditing(messageId: EngineMessage.Id, append: Bool) {
|
||||
func openTodoEditing(messageId: EngineMessage.Id, itemId: Int32?, append: Bool) {
|
||||
guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
@ -2109,6 +2109,7 @@ extension ChatControllerImpl {
|
||||
initialData: ComposeTodoScreen.initialData(
|
||||
context: self.context,
|
||||
existingTodo: existingTodo,
|
||||
focusedId: itemId,
|
||||
append: append,
|
||||
canEdit: canEdit
|
||||
),
|
||||
|
@ -1496,7 +1496,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { c, f in
|
||||
if let _ = activeTodo {
|
||||
interfaceInteraction.editTodoMessage(messages[0].id, false)
|
||||
interfaceInteraction.editTodoMessage(messages[0].id, nil, false)
|
||||
f(.dismissWithoutContent)
|
||||
} else {
|
||||
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
|
||||
@ -1532,7 +1532,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
actions.append(.action(ContextMenuActionItem(text: "Add a Task", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.editTodoMessage(messages[0].id, true)
|
||||
interfaceInteraction.editTodoMessage(messages[0].id, nil, true)
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}, callPeer: { _, _ in
|
||||
}, openConferenceCall: { _ in
|
||||
}, longTap: { _, _ in
|
||||
}, todoItemLongTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _, _ in
|
||||
}, openSearch: {
|
||||
}, setupReply: { _ in
|
||||
|
@ -2327,7 +2327,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, chatControllerNode: {
|
||||
return nil
|
||||
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in
|
||||
}, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
|
||||
}, longTap: { _, _ in
|
||||
}, todoItemLongTap: { _, _ in
|
||||
}, openCheckoutOrReceipt: { _, _ in
|
||||
}, openSearch: {
|
||||
}, setupReply: { _ in
|
||||
}, canSetupReply: { _ in
|
||||
return .none
|
||||
}, canSendMessages: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user