Support links for video timecodes

This commit is contained in:
Ilya Laktyushin 2021-07-07 02:29:50 +03:00
parent d4f1eedc3c
commit ad98e74477
23 changed files with 142 additions and 75 deletions

View File

@ -171,7 +171,7 @@ public enum ResolvedUrl {
case inaccessiblePeer
case botStart(peerId: PeerId, payload: String)
case groupBotStart(peerId: PeerId, payload: String)
case channelMessage(peerId: PeerId, messageId: MessageId)
case channelMessage(peerId: PeerId, messageId: MessageId, timecode: Double?)
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
case stickerPack(name: String)
case instantView(TelegramMediaWebpage, String?)

View File

@ -339,7 +339,7 @@ public struct ChatTextInputStateText: PostboxCoding, Equatable {
}
public enum ChatControllerSubject: Equatable {
case message(id: MessageId, highlight: Bool)
case message(id: MessageId, highlight: Bool, timecode: Double?)
case scheduledMessages
case pinnedMessages(id: MessageId?)
}

View File

@ -692,7 +692,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
scrollToEndIfExists = true
}
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(id: messageId, highlight: true), purposefulAction: {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(id: messageId, highlight: true, timecode: nil), purposefulAction: {
if deactivateOnAction {
self?.deactivateSearch(animated: false)
}
@ -862,7 +862,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} else {
var subject: ChatControllerSubject?
if case let .search(messageId) = source, let id = messageId {
subject = .message(id: id, highlight: false)
subject = .message(id: id, highlight: false, timecode: nil)
}
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)

View File

@ -965,7 +965,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let resolvedMessage = .single(nil)
|> then(context.sharedContext.resolveUrl(context: context, peerId: nil, url: finalQuery, skipUrlAuth: true)
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in
if case let .channelMessage(_, messageId) = resolvedUrl {
if case let .channelMessage(_, messageId, _) = resolvedUrl {
return context.engine.messages.downloadMessage(messageId: messageId)
} else {
return .single(nil)

View File

@ -57,7 +57,7 @@ public final class HashtagSearchController: TelegramBaseController {
if let strongSelf = self {
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.context.account, peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: message.id.peerId == actualPeerId ? .message(id: message.id, highlight: true) : nil, keepStack: .always))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: message.id.peerId == actualPeerId ? .message(id: message.id, highlight: true, timecode: nil) : nil, keepStack: .always))
}
}))
strongSelf.controllerNode.listNode.clearHighlightAnimated(true)

View File

@ -518,7 +518,7 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
items.append(.action(ContextMenuActionItem(text: presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, _ in
c.dismiss(completion: {
if let navigationController = controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true)))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true, timecode: nil)))
}
})
})))

View File

@ -264,7 +264,7 @@ public func messageStatsController(context: AccountContext, messageId: MessageId
}
navigateToMessageImpl = { [weak controller] messageId in
if let navigationController = controller?.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
}
}
return controller

View File

@ -759,7 +759,7 @@ final class AuthorizedApplicationContext {
}
let navigateToMessage = {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true)))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil)))
}
if chatIsVisible {
@ -838,7 +838,7 @@ final class AuthorizedApplicationContext {
if visiblePeerId != peerId || messageId != nil {
if self.rootController.rootTabController != nil {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), subject: messageId.flatMap { .message(id: $0, highlight: true) }, activateInput: activateInput))
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: .peer(peerId), subject: messageId.flatMap { .message(id: $0, highlight: true, timecode: nil) }, activateInput: activateInput))
} else {
self.scheduledOpenChatWithPeerId = (peerId, messageId, activateInput)
}

View File

@ -107,13 +107,13 @@ private enum ChatRecordingActivity {
}
public enum NavigateToMessageLocation {
case id(MessageId)
case id(MessageId, Double?)
case index(MessageIndex)
case upperBound(PeerId)
var messageId: MessageId? {
switch self {
case let .id(id):
case let .id(id, _):
return id
case let .index(index):
return index.id
@ -124,7 +124,7 @@ public enum NavigateToMessageLocation {
var peerId: PeerId {
switch self {
case let .id(id):
case let .id(id, _):
return id.peerId
case let .index(index):
return index.id.peerId
@ -572,7 +572,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .pinnedMessageUpdated:
for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId))
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, nil))
break
}
}
@ -581,7 +581,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .gameScore:
for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId))
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, nil))
break
}
}
@ -997,9 +997,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, openMessageContextActions: { message, node, rect, gesture in
gesture?.cancel()
}, navigateToMessage: { [weak self] fromId, id in
self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId)
self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId)
}, navigateToMessageStandalone: { [weak self] id in
self?.navigateToMessage(from: nil, to: .id(id), forceInCurrentChat: false)
self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false)
}, tapMessage: nil, clickThroughMessage: { [weak self] in
self?.chatDisplayNode.dismissInput()
}, toggleMessagesSelection: { [weak self] ids, value in
@ -1876,7 +1876,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let message = message else {
return
}
let context = strongSelf.context
let chatPresentationInterfaceState = strongSelf.presentationInterfaceState
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
let isCopyLink: Bool
if message.id.namespace == Namespaces.Message.Cloud, let _ = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction) {
isCopyLink = true
} else {
isCopyLink = false
}
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: text),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
@ -1885,12 +1895,52 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.controllerInteraction?.seekToTimecode(message, timecode, true)
}
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
ActionSheetButtonItem(title: isCopyLink ? strongSelf.presentationData.strings.Conversation_ContextMenuCopyLink : strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
UIPasteboard.general.string = text
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
if isCopyLink, let channel = message.peers[message.id.peerId] as? TelegramChannel {
var threadMessageId: MessageId?
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
threadMessageId = replyThreadMessage.messageId
}
let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, isThread: threadMessageId != nil)
|> map { result -> String? in
return result
}
|> deliverOnMainQueue).start(next: { link in
if let link = link {
UIPasteboard.general.string = link + "?t=\(Int32(timecode))"
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var warnAboutPrivate = false
if case .peer = chatPresentationInterfaceState.chatLocation {
if channel.addressName == nil {
warnAboutPrivate = true
}
}
Queue.mainQueue().after(0.2, {
let content: UndoOverlayContent
if warnAboutPrivate {
content = .linkCopied(text: presentationData.strings.Conversation_PrivateMessageLinkCopiedLong)
} else {
content = .linkCopied(text: presentationData.strings.Conversation_LinkCopied)
}
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})
} else {
UIPasteboard.general.string = text
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
})
} else {
UIPasteboard.general.string = text
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
})
]), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
@ -2664,7 +2714,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.openMessageReplies(messageId: threadMessageId, displayProgressInMessage: message.id, isChannelPost: true, atMessage: attribute.messageId, displayModalProgress: false)
}
} else {
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId))
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId, nil))
}
break
}
@ -4558,7 +4608,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toIndex, initial in
if let strongSelf = self, case let .message(index) = toIndex {
if case let .message(messageId, _) = strongSelf.subject, initial, messageId != index.id {
if case let .message(messageId, _, _) = strongSelf.subject, initial, messageId != index.id {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist), elevatedLayout: false, action: { _ in return true }), in: .current)
} else if let controllerInteraction = strongSelf.controllerInteraction {
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
@ -4575,6 +4625,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}))
if case let .message(_, _, timecode) = strongSelf.subject, let timecode = timecode, initial {
Queue.mainQueue().after(0.2) {
strongSelf.controllerInteraction?.openMessage(message, .timecode(timecode))
}
}
}
}
}
@ -4859,7 +4915,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatDisplayNode.navigateButtons.downPressed = { [weak self] in
if let strongSelf = self, strongSelf.isNodeLoaded {
if let messageId = strongSelf.historyNavigationStack.removeLast() {
strongSelf.navigateToMessage(from: nil, to: .id(messageId.id), rememberInStack: false)
strongSelf.navigateToMessage(from: nil, to: .id(messageId.id, nil), rememberInStack: false)
} else {
if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
@ -4882,7 +4938,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch result {
case let .result(messageId):
if let messageId = messageId {
strongSelf.navigateToMessage(from: nil, to: .id(messageId))
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil))
}
case .loading:
break
@ -5445,7 +5501,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
strongSelf.loadingMessage.set(.single(nil))
if let messageId = messageId {
strongSelf.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: true)
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), forceInCurrentChat: true)
}
}
}))
@ -5473,7 +5529,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.updateItemNodesSearchTextHighlightStates()
}
}, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat, statusSubject in
self?.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject)
self?.navigateToMessage(from: nil, to: .id(messageId, nil), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject)
}, navigateToChat: { [weak self] peerId in
guard let strongSelf = self else {
return
@ -6621,7 +6677,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if let navigationController = strongSelf.effectiveNavigationController {
let subject: ChatControllerSubject? = sourceMessageId.flatMap { ChatControllerSubject.message(id: $0, highlight: true) }
let subject: ChatControllerSubject? = sourceMessageId.flatMap { ChatControllerSubject.message(id: $0, highlight: true, timecode: nil) }
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadResult), subject: subject, keepStack: .always))
}
}, activatePinnedListPreview: { [weak self] node, gesture in
@ -7084,7 +7140,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId, let previousItem = previousItem?.id as? PeerMessagesMediaPlaylistItemId, previousItem.messageId.peerId == peerId, currentItem.messageId.peerId == peerId, currentItem.messageId != previousItem.messageId {
if strongSelf.chatDisplayNode.historyNode.isMessageVisibleOnScreen(currentItem.messageId) {
strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil)
strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId, nil), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil)
}
}
}
@ -10560,9 +10616,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let subject: ChatControllerSubject?
if let atMessageId = atMessageId {
subject = .message(id: atMessageId, highlight: true)
subject = .message(id: atMessageId, highlight: true, timecode: nil)
} else if let index = result.scrollToLowerBoundMessage {
subject = .message(id: index.id, highlight: false)
subject = .message(id: index.id, highlight: false, timecode: nil)
} else {
subject = nil
}
@ -10626,11 +10682,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if isPinnedMessages, let messageId = messageLocation.messageId {
if let navigationController = self.effectiveNavigationController {
self.dismiss()
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always))
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil), keepStack: .always))
}
} else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
if let navigationController = self.effectiveNavigationController {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always))
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true, timecode: nil), keepStack: .always))
}
} else if forceInCurrentChat {
if let _ = fromId, let fromIndex = fromIndex, rememberInStack {
@ -10652,13 +10708,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.messageIndexDisposable.set(nil)
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
completion?()
if case let .id(_, timecode) = messageLocation, let timecode = timecode {
self.controllerInteraction?.openMessage(message, .timecode(timecode))
}
} else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject {
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
} else {
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
let searchLocation: ChatHistoryInitialSearchLocation
switch messageLocation {
case let .id(id):
case let .id(id, _):
searchLocation = .id(id)
case let .index(index):
searchLocation = .index(index)
@ -10751,7 +10811,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let fromIndex = fromIndex {
let searchLocation: ChatHistoryInitialSearchLocation
switch messageLocation {
case let .id(id):
case let .id(id, _):
searchLocation = .id(id)
case let .index(index):
searchLocation = .index(index)
@ -10785,7 +10845,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
completion?()
} else {
if let navigationController = strongSelf.effectiveNavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true) }))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true, timecode: nil) }))
}
completion?()
}
@ -10797,7 +10857,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
} else {
if let navigationController = self.effectiveNavigationController {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true) }))
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message(id: $0, highlight: true, timecode: nil) }))
}
completion?()
}
@ -11555,8 +11615,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch navigation {
case let .chat(_, subject, peekData):
if case .peer(peerId) = strongSelf.chatLocation {
if let subject = subject, case let .message(messageId, _) = subject {
strongSelf.navigateToMessage(from: nil, to: .id(messageId))
if let subject = subject, case let .message(messageId, _, timecode) = subject {
strongSelf.navigateToMessage(from: nil, to: .id(messageId, timecode))
}
} else if let navigationController = strongSelf.effectiveNavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData))

View File

@ -910,7 +910,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount, highlight: false), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
} else {
if let subject = subject, case let .message(messageId, highlight) = subject {
if let subject = subject, case let .message(messageId, highlight, _) = subject {
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
@ -1088,7 +1088,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
})
if let subject = subject, case let .message(messageId, highlight) = subject {
if let subject = subject, case let .message(messageId, highlight, _) = subject {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: 0)
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: 0)

View File

@ -1278,7 +1278,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
}
}

View File

@ -3012,7 +3012,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
}
}

View File

@ -731,7 +731,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
}
}

View File

@ -884,7 +884,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true), peekData: nil)
navigate = .chat(textInputState: nil, subject: .message(id: attribute.messageId, highlight: true, timecode: nil), peekData: nil)
}
}

View File

@ -905,13 +905,13 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break
case .groupBotStart:
break
case let .channelMessage(peerId, messageId):
case let .channelMessage(peerId, messageId, timecode):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true)))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true, timecode: timecode)))
}
case let .replyThreadMessage(replyThreadMessage, messageId):
if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadMessage), subject: .message(id: messageId, highlight: true)))
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(replyThreadMessage), subject: .message(id: messageId, highlight: true, timecode: nil)))
}
case let .stickerPack(name):
let packReference: StickerPackReference = .name(name)

View File

@ -205,7 +205,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
switch item.content {
case let .peer(peer):
if let message = peer.messages.first {
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peer.peerId), subject: .message(id: message.id, highlight: true), botStart: nil, mode: .standard(previewing: true))
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peer.peerId), subject: .message(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([]), reactionItems: [], gesture: gesture)
presentInGlobalOverlay(contextController)

View File

@ -20,10 +20,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
if let updateTextInputState = params.updateTextInputState {
controller.updateTextInputState(updateTextInputState)
}
if let subject = params.subject, case let .message(messageId, _) = subject {
if let subject = params.subject, case let .message(messageId, _, timecode) = subject {
let navigationController = params.navigationController
let animated = params.animated
controller.navigateToMessage(messageLocation: .id(messageId), animated: isFirst, completion: { [weak navigationController, weak controller] in
controller.navigateToMessage(messageLocation: .id(messageId, timecode), animated: isFirst, completion: { [weak navigationController, weak controller] in
if let navigationController = navigationController, let controller = controller {
let _ = navigationController.popToViewController(controller, animated: animated)
}

View File

@ -97,8 +97,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
dismissInput()
navigationController?.pushViewController(controller)
case let .channelMessage(peerId, messageId):
openPeer(peerId, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true), peekData: nil))
case let .channelMessage(peerId, messageId, timecode):
openPeer(peerId, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true, timecode: timecode), peekData: nil))
case let .replyThreadMessage(replyThreadMessage, messageId):
if let navigationController = navigationController {
let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in

View File

@ -187,7 +187,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
self.isGlobalSearch = false
}
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: initialMessageId, highlight: true), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, source: source, subject: .message(id: initialMessageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
super.init()
@ -528,7 +528,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: messageId, highlight: true), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(id: messageId, highlight: true, timecode: nil), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch))
historyNode.preloadPages = true
historyNode.stackFromBottom = true
historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in

View File

@ -1738,7 +1738,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
c.dismiss(completion: {
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
let currentPeerId = strongSelf.peerId
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true), keepStack: .always, useExisting: false, purposefulAction: {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
var viewControllers = navigationController.viewControllers
var indexesToRemove = Set<Int>()
var keptCurrentChatController = false
@ -1884,7 +1884,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
c.dismiss(completion: {
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
let currentPeerId = strongSelf.peerId
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true), keepStack: .always, useExisting: false, purposefulAction: {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId), subject: .message(id: message.id, highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
var viewControllers = navigationController.viewControllers
var indexesToRemove = Set<Int>()
var keptCurrentChatController = false
@ -2775,7 +2775,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if copyUsername, let username = user.username, !username.isEmpty {
actions.append(ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Settings_CopyUsername, accessibilityLabel: strongSelf.presentationData.strings.Settings_CopyUsername), action: { [weak self] in
UIPasteboard.general.string = username
UIPasteboard.general.string = "@\(username)"
if let strongSelf = self {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }

View File

@ -58,9 +58,9 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
context.sharedContext.applicationBindings.openUrl(url)
case let .peer(peerId, navigation):
openResolvedPeerImpl(peerId, navigation)
case let .channelMessage(peerId, messageId):
case let .channelMessage(peerId, messageId, timecode):
if let navigationController = controller.navigationController as? NavigationController {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true)))
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(id: messageId, highlight: true, timecode: timecode)))
}
case let .replyThreadMessage(replyThreadMessage, messageId):
if let navigationController = controller.navigationController as? NavigationController {

View File

@ -16,7 +16,7 @@ private let baseTelegraPhPaths = ["telegra.ph/", "te.legra.ph/", "graph.org/", "
public enum ParsedInternalPeerUrlParameter {
case botStart(String)
case groupBotStart(String)
case channelMessage(Int32)
case channelMessage(Int32, Double?)
case replyThread(Int32, Int32)
case voiceChat(String?)
}
@ -24,7 +24,7 @@ public enum ParsedInternalPeerUrlParameter {
public enum ParsedInternalUrl {
case peerName(String, ParsedInternalPeerUrlParameter?)
case peerId(PeerId)
case privateMessage(messageId: MessageId, threadId: Int32?)
case privateMessage(messageId: MessageId, threadId: Int32?, timecode: Double?)
case stickerPack(String)
case join(String)
case localization(String)
@ -281,37 +281,44 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} else if pathComponents.count == 3 && pathComponents[0] == "c" {
if let channelId = Int32(pathComponents[1]), let messageId = Int32(pathComponents[2]) {
var threadId: Int32?
var timecode: Double?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "thread" {
if let intValue = Int32(value) {
threadId = intValue
break
}
} else if queryItem.name == "t" {
if let doubleValue = Double(value) {
timecode = doubleValue
}
}
}
}
}
return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt32Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId)
return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt32Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, timecode: timecode)
} else {
return nil
}
} else if let value = Int32(pathComponents[1]) {
var threadId: Int32?
var commentId: Int32?
var timecode: Double?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "thread" {
if let intValue = Int32(value) {
threadId = intValue
break
}
} else if queryItem.name == "comment" {
if let intValue = Int32(value) {
commentId = intValue
break
}
} else if queryItem.name == "t" {
if let doubleValue = Double(value) {
timecode = doubleValue
}
}
}
@ -322,7 +329,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} else if let commentId = commentId {
return .peerName(peerName, .replyThread(value, commentId))
} else {
return .peerName(peerName, .channelMessage(value))
return .peerName(peerName, .channelMessage(value, timecode))
}
} else {
return nil
@ -357,8 +364,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return .single(.botStart(peerId: peer.id, payload: payload))
case let .groupBotStart(payload):
return .single(.groupBotStart(peerId: peer.id, payload: payload))
case let .channelMessage(id):
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)))
case let .channelMessage(id, timecode):
return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))
case let .replyThread(id, replyId):
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
return fetchChannelReplyThreadMessage(account: context.account, messageId: replyThreadMessageId, atMessageId: nil)
@ -368,7 +375,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
}
|> map { result -> ResolvedUrl? in
guard let result = result else {
return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId)
return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId, timecode: nil)
}
return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
}
@ -397,7 +404,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return .single(.inaccessiblePeer)
}
}
case let .privateMessage(messageId, threadId):
case let .privateMessage(messageId, threadId, timecode):
return context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(messageId.peerId)
}
@ -420,12 +427,12 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
}
|> map { result -> ResolvedUrl? in
guard let result = result else {
return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId)
return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId, timecode: timecode)
}
return .replyThreadMessage(replyThreadMessage: result, messageId: messageId)
}
} else {
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true), peekData: nil)))
return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(id: messageId, highlight: true, timecode: timecode), peekData: nil)))
}
} else {
return .single(.inaccessiblePeer)

View File

@ -31,7 +31,7 @@ public func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concea
latin.insert(charactersIn: "a"..."z")
latin.insert(charactersIn: "0"..."9")
var punctuation = CharacterSet()
punctuation.insert(charactersIn: ".-/+_")
punctuation.insert(charactersIn: ".-/+_?=")
var hasLatin = false
var hasNonLatin = false
for c in rawHost {