mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
648c9807bf
commit
649b7e4ee6
@ -236,6 +236,59 @@ private func mergedResult(_ state: SearchMessagesState) -> SearchMessagesResult
|
||||
return SearchMessagesResult(messages: messages, readStates: readStates, threadInfo: threadInfo, totalCount: state.main.totalCount + (state.additional?.totalCount ?? 0), completed: state.main.completed && (state.additional?.completed ?? true))
|
||||
}
|
||||
|
||||
func _internal_getSearchMessageCount(account: Account, location: SearchMessagesLocation, query: String) -> Signal<Int?, NoError> {
|
||||
guard case let .peer(peerId, fromId, _, _, threadId, _, _) = location else {
|
||||
return .single(nil)
|
||||
}
|
||||
return account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputPeer?) in
|
||||
var chatPeer = transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
var fromPeer: Api.InputPeer?
|
||||
if let fromId {
|
||||
if let value = transaction.getPeer(fromId).flatMap(apiInputPeer) {
|
||||
fromPeer = value
|
||||
} else {
|
||||
chatPeer = nil
|
||||
}
|
||||
}
|
||||
|
||||
return (chatPeer, fromPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer, fromPeer -> Signal<Int?, NoError> in
|
||||
guard let inputPeer else {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
|
||||
if let _ = fromPeer {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
|
||||
var topMsgId: Int32?
|
||||
if let threadId = threadId {
|
||||
flags |= (1 << 1)
|
||||
topMsgId = Int32(clamping: threadId)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromPeer, savedPeerId: nil, savedReaction: nil, topMsgId: topMsgId, filter: .inputMessagesFilterEmpty, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0))
|
||||
|> map { result -> Int? in
|
||||
switch result {
|
||||
case let .channelMessages(_, _, count, _, _, _, _, _):
|
||||
return Int(count)
|
||||
case let .messages(messages, _, _):
|
||||
return messages.count
|
||||
case let .messagesNotModified(count):
|
||||
return Int(count)
|
||||
case let .messagesSlice(_, count, _, _, _, _, _):
|
||||
return Int(count)
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<Int?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_searchMessages(account: Account, location: SearchMessagesLocation, query: String, state: SearchMessagesState?, centerId: MessageId?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> {
|
||||
if case let .peer(peerId, fromId, tags, reactions, threadId, minDate, maxDate) = location, fromId == nil, tags == nil, peerId == account.peerId, let reactions, let reaction = reactions.first, (minDate == nil || minDate == 0), (maxDate == nil || maxDate == 0) {
|
||||
return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in
|
||||
|
@ -76,6 +76,10 @@ public extension TelegramEngine {
|
||||
return _internal_searchMessages(account: self.account, location: location, query: query, state: state, centerId: centerId, limit: limit)
|
||||
}
|
||||
|
||||
public func getSearchMessageCount(location: SearchMessagesLocation, query: String) -> Signal<Int?, NoError> {
|
||||
return _internal_getSearchMessageCount(account: self.account, location: location, query: query)
|
||||
}
|
||||
|
||||
public func searchHashtagPosts(hashtag: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> {
|
||||
return _internal_searchHashtagPosts(account: self.account, hashtag: hashtag, state: state, limit: limit)
|
||||
}
|
||||
|
@ -194,6 +194,7 @@ private final class AdminUserActionsSheetComponent: Component {
|
||||
let chatPeer: EnginePeer
|
||||
let peers: [RenderedChannelParticipant]
|
||||
let messageCount: Int
|
||||
let deleteAllMessageCount: Int?
|
||||
let completion: (AdminUserActionsSheet.Result) -> Void
|
||||
|
||||
init(
|
||||
@ -201,12 +202,14 @@ private final class AdminUserActionsSheetComponent: Component {
|
||||
chatPeer: EnginePeer,
|
||||
peers: [RenderedChannelParticipant],
|
||||
messageCount: Int,
|
||||
deleteAllMessageCount: Int?,
|
||||
completion: @escaping (AdminUserActionsSheet.Result) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.chatPeer = chatPeer
|
||||
self.peers = peers
|
||||
self.messageCount = messageCount
|
||||
self.deleteAllMessageCount = deleteAllMessageCount
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
@ -223,6 +226,9 @@ private final class AdminUserActionsSheetComponent: Component {
|
||||
if lhs.messageCount != rhs.messageCount {
|
||||
return false
|
||||
}
|
||||
if lhs.deleteAllMessageCount != rhs.deleteAllMessageCount {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -642,7 +648,7 @@ private final class AdminUserActionsSheetComponent: Component {
|
||||
let sectionId: AnyHashable
|
||||
let selectedPeers: Set<EnginePeer.Id>
|
||||
let isExpanded: Bool
|
||||
let title: String
|
||||
var title: String
|
||||
|
||||
switch section {
|
||||
case .report:
|
||||
@ -870,7 +876,14 @@ private final class AdminUserActionsSheetComponent: Component {
|
||||
)))
|
||||
}
|
||||
|
||||
let titleString: String = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(component.messageCount))
|
||||
var titleString: String = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(component.messageCount))
|
||||
|
||||
if let deleteAllMessageCount = component.deleteAllMessageCount {
|
||||
if self.optionDeleteAllSelectedPeers == Set(component.peers.map(\.peer.id)) {
|
||||
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount))
|
||||
}
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
@ -884,7 +897,9 @@ private final class AdminUserActionsSheetComponent: Component {
|
||||
if titleView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
//transition.setPosition(view: titleView, position: titleFrame.center)
|
||||
titleView.center = titleFrame.center
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
}
|
||||
|
||||
let navigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: 54.0))
|
||||
@ -1424,10 +1439,10 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer {
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
public init(context: AccountContext, chatPeer: EnginePeer, peers: [RenderedChannelParticipant], messageCount: Int, completion: @escaping (Result) -> Void) {
|
||||
public init(context: AccountContext, chatPeer: EnginePeer, peers: [RenderedChannelParticipant], messageCount: Int, deleteAllMessageCount: Int?, completion: @escaping (Result) -> Void) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, messageCount: messageCount, completion: completion), navigationBarAppearance: .none)
|
||||
super.init(context: context, component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, messageCount: messageCount, deleteAllMessageCount: deleteAllMessageCount, completion: completion), navigationBarAppearance: .none)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.navigationPresentation = .flatModal
|
||||
|
@ -4747,25 +4747,31 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
|
||||
if let item = self.item, let forwardInfo = item.message.forwardInfo {
|
||||
let performAction: () -> Void = {
|
||||
let performAction: () -> Void = { [weak forwardInfoNode] in
|
||||
if let sourceMessageId = forwardInfo.sourceMessageId {
|
||||
if let channel = forwardInfo.author as? TelegramChannel, channel.addressName == nil {
|
||||
if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
|
||||
} else if case .member = channel.participationStatus {
|
||||
} else if !item.message.id.peerId.isReplies {
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, false, forwardInfoNode, nil)
|
||||
if let forwardInfoNode {
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, false, forwardInfoNode, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
if let forwardInfoNode {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId, NavigateToMessageParams(timestamp: nil, quote: nil, progress: forwardInfoNode.makeActivate()?()))
|
||||
}
|
||||
} else if let peer = forwardInfo.source ?? forwardInfo.author {
|
||||
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info(nil) : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
} else if let _ = forwardInfo.authorSignature {
|
||||
var subRect: CGRect?
|
||||
if let textNode = forwardInfoNode.nameNode {
|
||||
subRect = textNode.frame
|
||||
if let forwardInfoNode {
|
||||
var subRect: CGRect?
|
||||
if let textNode = forwardInfoNode.nameNode {
|
||||
subRect = textNode.frame
|
||||
}
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, forwardInfoNode, subRect)
|
||||
}
|
||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, forwardInfoNode, subRect)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
||||
"//submodules/AvatarNode",
|
||||
],
|
||||
visibility = [
|
||||
|
@ -8,6 +8,8 @@ import TelegramPresentationData
|
||||
import LocalizedPeerData
|
||||
import AccountContext
|
||||
import AvatarNode
|
||||
import TextLoadingEffect
|
||||
import SwiftSignalKit
|
||||
|
||||
public enum ChatMessageForwardInfoType: Equatable {
|
||||
case bubble(incoming: Bool)
|
||||
@ -85,6 +87,10 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
private var highlightColor: UIColor?
|
||||
private var linkHighlightingNode: LinkHighlightingNode?
|
||||
|
||||
private var hasLinkProgress: Bool = false
|
||||
private var linkProgressView: TextLoadingEffectView?
|
||||
private var linkProgressDisposable: Disposable?
|
||||
|
||||
private var previousPeer: Peer?
|
||||
|
||||
public var openPsa: ((String, ASDisplayNode) -> Void)?
|
||||
@ -93,6 +99,10 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.linkProgressDisposable?.dispose()
|
||||
}
|
||||
|
||||
public func hasAction(at point: CGPoint) -> Bool {
|
||||
if let infoNode = self.infoNode, infoNode.frame.contains(point) {
|
||||
return true
|
||||
@ -172,7 +182,6 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
|
||||
if isHighlighted, !initialRects.isEmpty, let highlightColor = self.highlightColor {
|
||||
let rects = initialRects
|
||||
|
||||
let linkHighlightingNode: LinkHighlightingNode
|
||||
if let current = self.linkHighlightingNode {
|
||||
linkHighlightingNode = current
|
||||
@ -191,6 +200,85 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func makeActivate() -> (() -> Promise<Bool>?)? {
|
||||
return { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let promise = Promise<Bool>()
|
||||
self.linkProgressDisposable?.dispose()
|
||||
|
||||
if self.hasLinkProgress {
|
||||
self.hasLinkProgress = false
|
||||
self.updateLinkProgressState()
|
||||
}
|
||||
|
||||
self.linkProgressDisposable = (promise.get() |> deliverOnMainQueue).startStrict(next: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.hasLinkProgress != value {
|
||||
self.hasLinkProgress = value
|
||||
self.updateLinkProgressState()
|
||||
}
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
|
||||
private func updateLinkProgressState() {
|
||||
guard let highlightColor = self.highlightColor else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.hasLinkProgress, let titleNode = self.titleNode, let nameNode = self.nameNode {
|
||||
var initialRects: [CGRect] = []
|
||||
let addRects: (TextNode, CGPoint, CGFloat) -> Void = { textNode, offset, additionalWidth in
|
||||
guard let cachedLayout = textNode.cachedLayout else {
|
||||
return
|
||||
}
|
||||
for rect in cachedLayout.linesRects() {
|
||||
var rect = rect
|
||||
rect.size.width += rect.origin.x + additionalWidth
|
||||
rect.origin.x = 0.0
|
||||
initialRects.append(rect.offsetBy(dx: offset.x, dy: offset.y))
|
||||
}
|
||||
}
|
||||
|
||||
let offsetY: CGFloat = -12.0
|
||||
if let titleNode = self.titleNode {
|
||||
addRects(titleNode, CGPoint(x: titleNode.frame.minX, y: offsetY + titleNode.frame.minY), 0.0)
|
||||
|
||||
if let nameNode = self.nameNode {
|
||||
addRects(nameNode, CGPoint(x: titleNode.frame.minX, y: offsetY + nameNode.frame.minY), nameNode.frame.minX - titleNode.frame.minX)
|
||||
}
|
||||
}
|
||||
|
||||
let linkProgressView: TextLoadingEffectView
|
||||
if let current = self.linkProgressView {
|
||||
linkProgressView = current
|
||||
} else {
|
||||
linkProgressView = TextLoadingEffectView(frame: CGRect())
|
||||
self.linkProgressView = linkProgressView
|
||||
self.view.addSubview(linkProgressView)
|
||||
}
|
||||
linkProgressView.frame = titleNode.frame
|
||||
|
||||
let progressColor: UIColor = highlightColor
|
||||
|
||||
linkProgressView.update(color: progressColor, size: CGRectUnion(titleNode.frame, nameNode.frame).size, rects: initialRects)
|
||||
} else {
|
||||
if let linkProgressView = self.linkProgressView {
|
||||
self.linkProgressView = nil
|
||||
linkProgressView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak linkProgressView] _ in
|
||||
linkProgressView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer?, _ authorName: String?, _ psaType: String?, _ storyData: StoryData?, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) {
|
||||
let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode)
|
||||
let nameNodeLayout = TextNode.asyncLayout(maybeNode?.nameNode)
|
||||
|
@ -370,6 +370,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
self.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:))))
|
||||
}
|
||||
|
||||
override public func animateIn() {
|
||||
@ -491,9 +492,9 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
/*@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.interfaceInteraction?.navigateToMessage(self.messageId, false, true, .generic)
|
||||
@objc func longPressGesture(_ recognizer: UILongPressGestureRecognizer) {
|
||||
if case .began = recognizer.state {
|
||||
self.interfaceInteraction?.navigateToMessage(self.messageId, false, true, ChatLoadingMessageSubject.generic)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@ -868,7 +868,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
|
||||
}
|
||||
contentHeight += reactionCountSectionSize.height
|
||||
|
||||
if "".isEmpty {
|
||||
if !"".isEmpty {
|
||||
contentHeight += 32.0
|
||||
|
||||
let paidReactionsSection: ComponentView<Empty>
|
||||
|
@ -171,4 +171,34 @@ public final class TextLoadingEffectView: UIView {
|
||||
self.updateAnimations(size: maskFrame.size)
|
||||
}
|
||||
}
|
||||
|
||||
public func update(color: UIColor, size: CGSize, rects: [CGRect]) {
|
||||
let rectsSet: [CGRect] = rects
|
||||
|
||||
let maskFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -4.0, dy: -4.0)
|
||||
|
||||
self.maskContentsView.backgroundColor = color.withAlphaComponent(0.1)
|
||||
self.maskBorderContentsView.backgroundColor = color.withAlphaComponent(0.12)
|
||||
|
||||
self.backgroundView.tintColor = color
|
||||
self.borderBackgroundView.tintColor = color
|
||||
|
||||
self.maskContentsView.frame = maskFrame
|
||||
self.maskBorderContentsView.frame = maskFrame
|
||||
|
||||
self.maskHighlightNode.updateRects(rectsSet)
|
||||
self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
|
||||
|
||||
self.maskBorderHighlightNode.updateRects(rectsSet)
|
||||
self.maskBorderHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
|
||||
|
||||
if self.size != maskFrame.size {
|
||||
self.size = maskFrame.size
|
||||
|
||||
self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
|
||||
self.borderBackgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
|
||||
|
||||
self.updateAnimations(size: maskFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ extension ChatControllerImpl {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId && !params.forceNew, forceNew: params.forceNew)
|
||||
self.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId && !params.forceNew, forceNew: params.forceNew, progress: params.progress)
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.data.get(
|
||||
@ -77,6 +77,7 @@ extension ChatControllerImpl {
|
||||
animated: Bool = true,
|
||||
completion: (() -> Void)? = nil,
|
||||
customPresentProgress: ((ViewController, Any?) -> Void)? = nil,
|
||||
progress: Promise<Bool>? = nil,
|
||||
statusSubject: ChatLoadingMessageSubject = .generic
|
||||
) {
|
||||
if !self.isNodeLoaded {
|
||||
@ -160,31 +161,156 @@ extension ChatControllerImpl {
|
||||
guard let self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
if let navigationController = self.effectiveNavigationController {
|
||||
var chatLocation: NavigateToChatControllerParams.Location = .peer(peer)
|
||||
var displayMessageNotFoundToast = false
|
||||
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||
if let message = message, let threadId = message.threadId {
|
||||
chatLocation = .replyThread(ChatReplyThreadMessage(peerId: peer.id, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
||||
|
||||
var quote: ChatControllerSubject.MessageHighlight.Quote?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
||||
}
|
||||
var progressValue: Promise<Bool>?
|
||||
if let value = progress {
|
||||
progressValue = value
|
||||
} else if case let .id(_, params) = messageLocation {
|
||||
progressValue = params.progress
|
||||
}
|
||||
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
|
||||
|
||||
var chatLocation: NavigateToChatControllerParams.Location = .peer(peer)
|
||||
var preloadChatLocation: ChatLocation = .peer(id: peer.id)
|
||||
var displayMessageNotFoundToast = false
|
||||
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||
if let message = message, let threadId = message.threadId {
|
||||
let replyThreadMessage = ChatReplyThreadMessage(peerId: peer.id, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)
|
||||
chatLocation = .replyThread(replyThreadMessage)
|
||||
preloadChatLocation = .replyThread(message: replyThreadMessage)
|
||||
} else {
|
||||
displayMessageNotFoundToast = true
|
||||
}
|
||||
}
|
||||
|
||||
let searchLocation: ChatHistoryInitialSearchLocation
|
||||
switch messageLocation {
|
||||
case let .id(id, _):
|
||||
if case let .replyThread(message) = chatLocation, id == message.effectiveMessageId {
|
||||
searchLocation = .index(.absoluteLowerBound())
|
||||
} else {
|
||||
searchLocation = .id(id)
|
||||
}
|
||||
case let .index(index):
|
||||
searchLocation = .index(index)
|
||||
case .upperBound:
|
||||
searchLocation = .index(MessageIndex.upperBound(peerId: chatLocation.peerId))
|
||||
}
|
||||
var historyView: Signal<ChatHistoryViewUpdate, NoError>
|
||||
|
||||
let subject: ChatControllerSubject = .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil)
|
||||
|
||||
historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: nil), count: 50, highlight: true), id: 0), context: self.context, chatLocation: preloadChatLocation, subject: subject, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), fixedCombinedReadStates: nil, tag: nil, additionalData: [])
|
||||
|
||||
var signal: Signal<(MessageIndex?, Bool), NoError>
|
||||
signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||
switch historyView {
|
||||
case .Loading:
|
||||
return .single((nil, true))
|
||||
case let .HistoryView(view, _, _, _, _, _, _):
|
||||
for entry in view.entries {
|
||||
if entry.message.id == messageLocation.messageId {
|
||||
return .single((entry.message.index, false))
|
||||
}
|
||||
}
|
||||
if case let .index(index) = searchLocation {
|
||||
return .single((index, false))
|
||||
}
|
||||
return .single((nil, false))
|
||||
}
|
||||
}
|
||||
|> take(until: { index in
|
||||
return SignalTakeAction(passthrough: true, complete: !index.1)
|
||||
})
|
||||
|
||||
/*#if DEBUG
|
||||
signal = .single((nil, true)) |> then(signal |> delay(2.0, queue: .mainQueue()))
|
||||
#endif*/
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = self.presentationData
|
||||
let displayTime = CACurrentMediaTime()
|
||||
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
||||
if let progressValue {
|
||||
progressValue.set(.single(true))
|
||||
return ActionDisposable {
|
||||
Queue.mainQueue().async() {
|
||||
progressValue.set(.single(false))
|
||||
}
|
||||
}
|
||||
} else if case .generic = statusSubject {
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
if CACurrentMediaTime() - displayTime > 1.5 {
|
||||
cancelImpl?()
|
||||
}
|
||||
}))
|
||||
if let customPresentProgress = customPresentProgress {
|
||||
customPresentProgress(controller, nil)
|
||||
} else {
|
||||
displayMessageNotFoundToast = true
|
||||
self?.present(controller, in: .window(.root))
|
||||
}
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(progressValue == nil ? 0.05 : 0.0, queue: Queue.mainQueue())
|
||||
let progressDisposable = MetaDisposable()
|
||||
var progressStarted = false
|
||||
self.messageIndexDisposable.set((signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] index in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var quote: ChatControllerSubject.MessageHighlight.Quote?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
||||
if let index = index.0 {
|
||||
let _ = index
|
||||
//strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, quote: quote, scrollPosition: scrollPosition)
|
||||
} else if index.1 {
|
||||
if !progressStarted {
|
||||
progressStarted = true
|
||||
progressDisposable.set(progressSignal.start())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil, setupReply: false), keepStack: .always, chatListCompletion: { chatListController in
|
||||
if displayMessageNotFoundToast {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_MessageDoesntExist, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
}
|
||||
}))
|
||||
if let navigationController = self.effectiveNavigationController {
|
||||
let context = self.context
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: subject, keepStack: .always, chatListCompletion: { chatListController in
|
||||
if displayMessageNotFoundToast {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_MessageDoesntExist, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}, completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.loadingMessage.set(.single(nil))
|
||||
}
|
||||
completion?()
|
||||
}))
|
||||
cancelImpl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.loadingMessage.set(.single(nil))
|
||||
strongSelf.messageIndexDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
|
||||
completion?()
|
||||
|
@ -125,6 +125,14 @@ extension ChatControllerImpl {
|
||||
return
|
||||
}
|
||||
|
||||
var deleteAllMessageCount: Signal<Int?, NoError> = .single(nil)
|
||||
if authors.count == 1 {
|
||||
deleteAllMessageCount = self.context.engine.messages.searchMessages(location: .peer(peerId: peerId, fromId: authors[0].id, tags: nil, reactions: nil, threadId: self.chatLocation.threadId, minDate: nil, maxDate: nil), query: "", state: nil)
|
||||
|> map { result, _ -> Int? in
|
||||
return Int(result.totalCount)
|
||||
}
|
||||
}
|
||||
|
||||
var signal = combineLatest(authors.map { author in
|
||||
self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id)
|
||||
|> map { result -> (Peer, ChannelParticipant?) in
|
||||
@ -161,8 +169,8 @@ extension ChatControllerImpl {
|
||||
disposables.set(nil)
|
||||
}
|
||||
|
||||
disposables.set((signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] authorsAndParticipants in
|
||||
disposables.set((combineLatest(signal, deleteAllMessageCount)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] authorsAndParticipants, deleteAllMessageCount in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -212,6 +220,7 @@ extension ChatControllerImpl {
|
||||
chatPeer: chatPeer,
|
||||
peers: renderedParticipants,
|
||||
messageCount: messageIds.count,
|
||||
deleteAllMessageCount: deleteAllMessageCount,
|
||||
completion: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
@ -259,8 +268,16 @@ extension ChatControllerImpl {
|
||||
disposables.set(nil)
|
||||
}
|
||||
|
||||
disposables.set((signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] maybeParticipant in
|
||||
var deleteAllMessageCount: Signal<Int?, NoError> = .single(nil)
|
||||
do {
|
||||
deleteAllMessageCount = self.context.engine.messages.getSearchMessageCount(location: .peer(peerId: peerId, fromId: author.id, tags: nil, reactions: nil, threadId: self.chatLocation.threadId, minDate: nil, maxDate: nil), query: "")
|
||||
|> map { result -> Int? in
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
disposables.set((combineLatest(signal, deleteAllMessageCount)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] maybeParticipant, deleteAllMessageCount in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -310,6 +327,7 @@ extension ChatControllerImpl {
|
||||
peer: authorPeer._asPeer()
|
||||
)],
|
||||
messageCount: messageIds.count,
|
||||
deleteAllMessageCount: deleteAllMessageCount,
|
||||
completion: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -257,10 +257,10 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
if let value = URL(string: "ipfs:/" + parsedUrl.path) {
|
||||
parsedUrl = value
|
||||
}
|
||||
}
|
||||
} else if let scheme = parsedUrl.scheme, scheme == "https", parsedUrl.host == "t.me", parsedUrl.path.hasPrefix("/ipfs/") {
|
||||
if let value = URL(string: "ipfs://" + String(parsedUrl.path[parsedUrl.path.index(parsedUrl.path.startIndex, offsetBy: "/ipfs/".count)...])) {
|
||||
parsedUrl = value
|
||||
} else if parsedUrl.host == "ton" {
|
||||
if let value = URL(string: "ton:/" + parsedUrl.path) {
|
||||
parsedUrl = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1009,7 +1009,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
isInternetUrl = true
|
||||
}
|
||||
if context.sharedContext.immediateExperimentalUISettings.browserExperiment {
|
||||
if parsedUrl.scheme == "ipfs" || parsedUrl.scheme == "ipns" {
|
||||
if parsedUrl.scheme == "ipfs" || parsedUrl.scheme == "ipns" || parsedUrl.scheme == "ton" {
|
||||
isInternetUrl = true
|
||||
}
|
||||
}
|
||||
|
@ -251,6 +251,10 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
}
|
||||
|
||||
if displayImage {
|
||||
if captureProtected {
|
||||
setLayerDisableScreenshots(self.imageNode.layer, captureProtected)
|
||||
}
|
||||
|
||||
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: userLocation, videoReference: fileReference, imageReference: imageReference, onlyFullSize: onlyFullSizeThumbnail, useLargeThumbnail: useLargeThumbnail, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail || fileReference.media.isInstantVideo) |> map { [weak self] getSize, getData in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self, strongSelf.dimensions == nil {
|
||||
|
@ -134,6 +134,9 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
|
||||
if query.hasPrefix("ipfs/") {
|
||||
return .externalUrl(url: "ipfs://" + String(query[query.index(query.startIndex, offsetBy: "ipfs/".count)...]))
|
||||
}
|
||||
if query.hasPrefix("ton/") {
|
||||
return .externalUrl(url: "ton://" + String(query[query.index(query.startIndex, offsetBy: "ton/".count)...]))
|
||||
}
|
||||
}
|
||||
|
||||
if pathComponents[0].hasPrefix("+") || pathComponents[0].hasPrefix("%20") {
|
||||
|
Loading…
x
Reference in New Issue
Block a user