diff --git a/Random.txt b/Random.txt index 17c61b0d3c..86eb369131 100644 --- a/Random.txt +++ b/Random.txt @@ -1 +1 @@ -268a141d3791e0f6bf2abfab3c268435844d35f72071cae44704f8ba5e7c7dfd +4f0d2d13a70664d3029d9b97935089df0426fe53745965d175408752838b80dd \ No newline at end of file diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index a6d2dfe764..d0cf354fd6 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -24,8 +24,9 @@ public final class ChatMessageItemAssociatedData: Equatable { public let channelDiscussionGroup: ChannelDiscussionGroupStatus public let animatedEmojiStickers: [String: [StickerPackItem]] public let forcedResourceStatus: FileMediaResourceStatus? + public let currentlyPlayingMessageId: MessageIndex? - public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) { + public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: MessageIndex? = nil) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadNetworkType = automaticDownloadNetworkType self.isRecentActions = isRecentActions @@ -34,6 +35,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.channelDiscussionGroup = channelDiscussionGroup self.animatedEmojiStickers = animatedEmojiStickers self.forcedResourceStatus = forcedResourceStatus + self.currentlyPlayingMessageId = currentlyPlayingMessageId } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index b9c754ec2f..7f5ef063b0 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -119,11 +119,11 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { if isGlobalSearch { - return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id)) + return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } else if isRecentActions { - return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } else { - return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } } diff --git a/submodules/AccountContext/Sources/SharedMediaPlayer.swift b/submodules/AccountContext/Sources/SharedMediaPlayer.swift index 4140e3345d..e71f6843b1 100644 --- a/submodules/AccountContext/Sources/SharedMediaPlayer.swift +++ b/submodules/AccountContext/Sources/SharedMediaPlayer.swift @@ -135,14 +135,16 @@ public func areSharedMediaPlaylistItemIdsEqual(_ lhs: SharedMediaPlaylistItemId? public struct PeerMessagesMediaPlaylistItemId: SharedMediaPlaylistItemId { public let messageId: MessageId + public let messageIndex: MessageIndex - public init(messageId: MessageId) { + public init(messageId: MessageId, messageIndex: MessageIndex) { self.messageId = messageId + self.messageIndex = messageIndex } public func isEqual(to: SharedMediaPlaylistItemId) -> Bool { if let to = to as? PeerMessagesMediaPlaylistItemId { - if self.messageId != to.messageId { + if self.messageId != to.messageId || self.messageIndex != to.messageIndex { return false } return true diff --git a/submodules/AudioBlob/Sources/BlobView.swift b/submodules/AudioBlob/Sources/BlobView.swift index af208c1254..64ade477e5 100644 --- a/submodules/AudioBlob/Sources/BlobView.swift +++ b/submodules/AudioBlob/Sources/BlobView.swift @@ -1,12 +1,44 @@ import Foundation import UIKit +import AsyncDisplayKit import Display import LegacyComponents +public final class VoiceBlobNode: ASDisplayNode { + public init( + maxLevel: CGFloat, + smallBlobRange: VoiceBlobView.BlobRange, + mediumBlobRange: VoiceBlobView.BlobRange, + bigBlobRange: VoiceBlobView.BlobRange + ) { + super.init() + + self.setViewBlock({ + return VoiceBlobView(frame: CGRect(), maxLevel: maxLevel, smallBlobRange: smallBlobRange, mediumBlobRange: mediumBlobRange, bigBlobRange: bigBlobRange) + }) + } + + public func startAnimating(immediately: Bool = false) { + (self.view as? VoiceBlobView)?.startAnimating(immediately: immediately) + } + + public func stopAnimating(duration: Double = 0.15) { + (self.view as? VoiceBlobView)?.stopAnimating(duration: duration) + } + + public func setColor(_ color: UIColor, animated: Bool = false) { + (self.view as? VoiceBlobView)?.setColor(color, animated: animated) + } + + public func updateLevel(_ level: CGFloat, immediately: Bool = false) { + (self.view as? VoiceBlobView)?.updateLevel(level, immediately: immediately) + } +} + public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration { - private let smallBlob: BlobView - private let mediumBlob: BlobView - private let bigBlob: BlobView + private let smallBlob: BlobNode + private let mediumBlob: BlobNode + private let bigBlob: BlobNode private let maxLevel: CGFloat @@ -28,7 +60,7 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco ) { self.maxLevel = maxLevel - self.smallBlob = BlobView( + self.smallBlob = BlobNode( pointsCount: 8, minRandomness: 0.1, maxRandomness: 0.5, @@ -39,23 +71,23 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco scaleSpeed: 0.2, isCircle: true ) - self.mediumBlob = BlobView( + self.mediumBlob = BlobNode( pointsCount: 8, minRandomness: 1, maxRandomness: 1, - minSpeed: 1.5, - maxSpeed: 7, + minSpeed: 0.9, + maxSpeed: 4, minScale: mediumBlobRange.min, maxScale: mediumBlobRange.max, scaleSpeed: 0.2, isCircle: false ) - self.bigBlob = BlobView( + self.bigBlob = BlobNode( pointsCount: 8, minRandomness: 1, maxRandomness: 1, - minSpeed: 1.5, - maxSpeed: 7, + minSpeed: 0.9, + maxSpeed: 4, minScale: bigBlobRange.min, maxScale: bigBlobRange.max, scaleSpeed: 0.2, @@ -64,9 +96,9 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco super.init(frame: frame) - addSubview(bigBlob) - addSubview(mediumBlob) - addSubview(smallBlob) + self.addSubnode(self.bigBlob) + self.addSubnode(self.mediumBlob) + self.addSubnode(self.smallBlob) displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in guard let strongSelf = self else { return } @@ -148,8 +180,8 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco } private func updateBlobsState() { - if isAnimating { - if smallBlob.frame.size != .zero { + if self.isAnimating { + if self.smallBlob.frame.size != .zero { smallBlob.startAnimating() mediumBlob.startAnimating() bigBlob.startAnimating() @@ -164,15 +196,15 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco override public func layoutSubviews() { super.layoutSubviews() - smallBlob.frame = bounds - mediumBlob.frame = bounds - bigBlob.frame = bounds + self.smallBlob.frame = bounds + self.mediumBlob.frame = bounds + self.bigBlob.frame = bounds - updateBlobsState() + self.updateBlobsState() } } -final class BlobView: UIView { +final class BlobNode: ASDisplayNode { let pointsCount: Int let smoothness: CGFloat @@ -186,8 +218,6 @@ final class BlobView: UIView { let maxScale: CGFloat let scaleSpeed: CGFloat - var scaleLevelsToBalance = [CGFloat]() - let isCircle: Bool var level: CGFloat = 0 { @@ -261,9 +291,9 @@ final class BlobView: UIView { let angle = (CGFloat.pi * 2) / CGFloat(pointsCount) self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2 - super.init(frame: .zero) + super.init() - layer.addSublayer(shapeLayer) + self.layer.addSublayer(self.shapeLayer) self.shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1) } @@ -282,10 +312,6 @@ final class BlobView: UIView { func updateSpeedLevel(to newSpeedLevel: CGFloat) { self.speedLevel = max(self.speedLevel, newSpeedLevel) - -// if abs(lastSpeedLevel - newSpeedLevel) > 0.5 { -// animateToNewShape() -// } } func startAnimating() { @@ -368,16 +394,16 @@ final class BlobView: UIView { return points } - override func layoutSubviews() { - super.layoutSubviews() + override func layout() { + super.layout() CATransaction.begin() CATransaction.setDisableActions(true) - shapeLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) - if isCircle { - let halfWidth = bounds.width * 0.5 - shapeLayer.path = UIBezierPath( - roundedRect: bounds.offsetBy(dx: -halfWidth, dy: -halfWidth), + self.shapeLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + if self.isCircle { + let halfWidth = self.bounds.width * 0.5 + self.shapeLayer.path = UIBezierPath( + roundedRect: self.bounds.offsetBy(dx: -halfWidth, dy: -halfWidth), cornerRadius: halfWidth ).cgPath } @@ -386,7 +412,6 @@ final class BlobView: UIView { } private extension UIBezierPath { - static func smoothCurve( through points: [CGPoint], length: CGFloat, @@ -439,7 +464,6 @@ private extension UIBezierPath { } struct SmoothPoint { - let point: CGPoint let inAngle: CGFloat @@ -464,4 +488,3 @@ private extension UIBezierPath { } } } - diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index 0640d5ed22..88354bfed8 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -384,6 +384,7 @@ final class CallListControllerNode: ASDisplayNode { let disposable = strongSelf.openGroupCallDisposable let account = strongSelf.context.account + let engine = strongSelf.context.engine var signal: Signal = strongSelf.context.account.postbox.transaction { transaction -> CachedChannelData.ActiveCall? in let cachedData = transaction.getPeerCachedData(peerId: peerId) if let cachedData = cachedData as? CachedChannelData { @@ -397,7 +398,7 @@ final class CallListControllerNode: ASDisplayNode { if let activeCall = activeCall { return .single(activeCall) } else { - return updatedCurrentPeerGroupCall(account: account, peerId: peerId) + return engine.calls.updatedCurrentPeerGroupCall(peerId: peerId) } } diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 5d9c339ba5..b7a57d5cc0 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -49,141 +49,162 @@ enum ChatContextMenuSource { func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: ChatListNodeEntryPromoInfo?, source: ChatContextMenuSource, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let strings = presentationData.strings - return context.account.postbox.transaction { [weak chatListController] transaction -> [ContextMenuItem] in - if promoInfo != nil { - return [] - } - - var items: [ContextMenuItem] = [] - - if case let .search(search) = source { - switch search { - case .recentPeers: - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in - let _ = (context.engine.peers.removeRecentPeer(peerId: peerId) - |> deliverOnMainQueue).start(completed: { - f(.default) - }) - }))) - items.append(.separator) - case .recentSearch: - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in - let _ = (context.engine.peers.removeRecentlySearchedPeer(peerId: peerId) - |> deliverOnMainQueue).start(completed: { - f(.default) - }) - }))) - items.append(.separator) - case .search: - break - } - } - - let isSavedMessages = peerId == context.account.peerId - - let chatPeer = transaction.getPeer(peerId) - var maybePeer: Peer? - if let chatPeer = chatPeer { - if let chatPeer = chatPeer as? TelegramSecretChat { - maybePeer = transaction.getPeer(chatPeer.regularPeerId) + + return context.account.postbox.transaction { transaction -> (PeerGroupId, ChatListIndex)? in + transaction.getPeerChatListIndex(peerId) + } + |> mapToSignal { groupAndIndex -> Signal<[ContextMenuItem], NoError> in + let location: TogglePeerChatPinnedLocation + var chatListFilter: ChatListFilter? + if case let .chatList(filter) = source, let chatFilter = filter { + chatListFilter = chatFilter + location = .filter(chatFilter.id) + } else { + if let (group, _) = groupAndIndex { + location = .group(group) } else { - maybePeer = chatPeer - } - } - - guard let peer = maybePeer else { - return [] - } - - if !isSavedMessages, let peer = peer as? TelegramUser, !peer.flags.contains(.isSupport) && peer.botInfo == nil && !peer.isDeleted { - if !transaction.isPeerContact(peerId: peer.id) { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToContacts, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { _, f in - context.sharedContext.openAddPersonContact(context: context, peerId: peerId, pushController: { controller in - if let navigationController = chatListController?.navigationController as? NavigationController { - navigationController.pushViewController(controller) - } - }, present: { c, a in - if let chatListController = chatListController { - chatListController.present(c, in: .window(.root), with: a) - } - }) - f(.default) - }))) - items.append(.separator) - } - } - - var isMuted = false - if let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { - if case .muted = notificationSettings.muteState { - isMuted = true + location = .group(.root) } } - var isUnread = false - if let readState = transaction.getCombinedPeerReadState(peerId), readState.isUnread { - isUnread = true - } - - let isContact = transaction.isPeerContact(peerId: peerId) - - if case let .chatList(currentFilter) = source { - if let currentFilter = currentFilter { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/RemoveFromFolder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - let _ = (context.account.postbox.transaction { transaction -> Void in - updateChatListFiltersInteractively(transaction: transaction, { filters in - var filters = filters - for i in 0 ..< filters.count { - if filters[i].id == currentFilter.id { - let _ = filters[i].data.addExcludePeer(peerId: peer.id) - break + return combineLatest( + context.engine.peers.updatedChatListFilters() + |> take(1), + context.engine.peers.getPinnedItemIds(location: location) + ) + |> mapToSignal { filters, pinnedItemIds -> Signal<[ContextMenuItem], NoError> in + let isPinned = pinnedItemIds.contains(.peer(peerId)) + + return context.account.postbox.transaction { [weak chatListController] transaction -> [ContextMenuItem] in + if promoInfo != nil { + return [] + } + + var items: [ContextMenuItem] = [] + + if case let .search(search) = source { + switch search { + case .recentPeers: + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in + let _ = (context.engine.peers.removeRecentPeer(peerId: peerId) + |> deliverOnMainQueue).start(completed: { + f(.default) + }) + }))) + items.append(.separator) + case .recentSearch: + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromRecents, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) }, action: { _, f in + let _ = (context.engine.peers.removeRecentlySearchedPeer(peerId: peerId) + |> deliverOnMainQueue).start(completed: { + f(.default) + }) + }))) + items.append(.separator) + case .search: + break + } + } + + let isSavedMessages = peerId == context.account.peerId + + let chatPeer = transaction.getPeer(peerId) + var maybePeer: Peer? + if let chatPeer = chatPeer { + if let chatPeer = chatPeer as? TelegramSecretChat { + maybePeer = transaction.getPeer(chatPeer.regularPeerId) + } else { + maybePeer = chatPeer + } + } + + guard let peer = maybePeer else { + return [] + } + + if !isSavedMessages, let peer = peer as? TelegramUser, !peer.flags.contains(.isSupport) && peer.botInfo == nil && !peer.isDeleted { + if !transaction.isPeerContact(peerId: peer.id) { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToContacts, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { _, f in + context.sharedContext.openAddPersonContact(context: context, peerId: peerId, pushController: { controller in + if let navigationController = chatListController?.navigationController as? NavigationController { + navigationController.pushViewController(controller) } + }, present: { c, a in + if let chatListController = chatListController { + chatListController.present(c, in: .window(.root), with: a) + } + }) + f(.default) + }))) + items.append(.separator) + } + } + + var isMuted = false + if let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { + if case .muted = notificationSettings.muteState { + isMuted = true + } + } + + var isUnread = false + if let readState = transaction.getCombinedPeerReadState(peerId), readState.isUnread { + isUnread = true + } + + let isContact = transaction.isPeerContact(peerId: peerId) + + if case let .chatList(currentFilter) = source { + if let currentFilter = currentFilter { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_RemoveFromFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/RemoveFromFolder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in + var filters = filters + for i in 0 ..< filters.count { + if filters[i].id == currentFilter.id { + let _ = filters[i].data.addExcludePeer(peerId: peer.id) + break + } + } + return filters + } + |> deliverOnMainQueue).start(completed: { + c.dismiss(completion: { + chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatRemovedFromFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: currentFilter.title), elevatedLayout: false, animateInAsReplacement: true, action: { _ in + return false + }), in: .current) + }) + }) + }))) + } else { + var hasFolders = false + + for filter in filters { + let predicate = chatListFilterPredicate(filter: filter.data) + if predicate.includes(peer: peer, groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: false) { + continue + } + + var data = filter.data + if data.addIncludePeer(peerId: peer.id) { + hasFolders = true + break } - return filters - }) - } - |> deliverOnMainQueue).start(completed: { - c.dismiss(completion: { - chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatRemovedFromFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: currentFilter.title), elevatedLayout: false, animateInAsReplacement: true, action: { _ in - return false - }), in: .current) - }) - }) - }))) - } else { - var hasFolders = false - updateChatListFiltersInteractively(transaction: transaction, { filters in - for filter in filters { - let predicate = chatListFilterPredicate(filter: filter.data) - if predicate.includes(peer: peer, groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: false) { - continue } - - var data = filter.data - if data.addIncludePeer(peerId: peer.id) { - hasFolders = true - break - } - } - return filters - }) - - if hasFolders { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - let _ = (context.account.postbox.transaction { transaction -> [ContextMenuItem] in - var updatedItems: [ContextMenuItem] = [] - updateChatListFiltersInteractively(transaction: transaction, { filters in + + if hasFolders { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_AddToFolder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Folder"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + var updatedItems: [ContextMenuItem] = [] + for filter in filters { let predicate = chatListFilterPredicate(filter: filter.data) if predicate.includes(peer: peer, groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: false) { continue } - + var data = filter.data if !data.addIncludePeer(peerId: peer.id) { continue } - + let filterType = chatListFilterType(filter) updatedItems.append(.action(ContextMenuActionItem(text: filter.title, icon: { theme in let imageName: String @@ -208,7 +229,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor) }, action: { c, f in c.dismiss(completion: { - let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var filters = filters for i in 0 ..< filters.count { if filters[i].id == filter.id { @@ -217,180 +238,160 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } } return filters - })).start() - + }).start() + chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatAddedToFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: filter.title), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }) }))) } - - return filters - }) - - updatedItems.append(.separator) - updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, action: { c, _ in - c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined)) + + updatedItems.append(.separator) + updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined)) + }))) + + c.setItems(.single(updatedItems)) }))) - - return updatedItems } - |> deliverOnMainQueue).start(next: { updatedItems in - c.setItems(.single(updatedItems)) - }) + } + } + + if isUnread { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() + f(.default) + }))) + } else { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() + f(.default) }))) } - } - } - - if isUnread { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() - f(.default) - }))) - } else { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() - f(.default) - }))) - } - - let groupAndIndex = transaction.getPeerChatListIndex(peerId) - - let archiveEnabled = !isSavedMessages && peerId != PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(777000)) && peerId == context.account.peerId - if let (group, index) = groupAndIndex { - if archiveEnabled { - let isArchived = group == Namespaces.PeerGroup.archive - items.append(.action(ContextMenuActionItem(text: isArchived ? strings.ChatList_Context_Unarchive : strings.ChatList_Context_Archive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { _, f in - if isArchived { - let _ = (context.account.postbox.transaction { transaction -> Void in - updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root) - } - |> deliverOnMainQueue).start(completed: { - f(.default) - }) - } else { - if let chatListController = chatListController { - chatListController.archiveChats(peerIds: [peerId]) - f(.default) - } else { - let _ = (context.account.postbox.transaction { transaction -> Void in - updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: Namespaces.PeerGroup.archive) + + let archiveEnabled = !isSavedMessages && peerId != PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(777000)) && peerId == context.account.peerId + if let (group, _) = groupAndIndex { + if archiveEnabled { + let isArchived = group == Namespaces.PeerGroup.archive + items.append(.action(ContextMenuActionItem(text: isArchived ? strings.ChatList_Context_Unarchive : strings.ChatList_Context_Archive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { _, f in + if isArchived { + let _ = (context.account.postbox.transaction { transaction -> Void in + updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root) + } + |> deliverOnMainQueue).start(completed: { + f(.default) + }) + } else { + if let chatListController = chatListController { + chatListController.archiveChats(peerIds: [peerId]) + f(.default) + } else { + let _ = (context.account.postbox.transaction { transaction -> Void in + updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: Namespaces.PeerGroup.archive) + } + |> deliverOnMainQueue).start(completed: { + f(.default) + }) + } } + }))) + } + + if isPinned || chatListFilter == nil || peerId.namespace != Namespaces.Peer.SecretChat { + items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: .peer(peerId)) + |> deliverOnMainQueue).start(next: { result in + switch result { + case .done: + break + case .limitExceeded: + break + } + f(.default) + }) + }))) + } + + if !isSavedMessages, let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { + var isMuted = false + if case .muted = notificationSettings.muteState { + isMuted = true + } + items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = (context.engine.peers.togglePeerMuted(peerId: peerId) |> deliverOnMainQueue).start(completed: { f(.default) }) + }))) + } + } else { + if case .search = source { + if let _ = peer as? TelegramChannel { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in + var createSignal = context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peerId, hash: nil) + var cancelImpl: (() -> Void)? + let progressSignal = Signal { subscriber in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + chatListController?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + createSignal = createSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + let joinChannelDisposable = MetaDisposable() + cancelImpl = { + joinChannelDisposable.set(nil) + } + + joinChannelDisposable.set((createSignal + |> deliverOnMainQueue).start(next: { _ in + if let navigationController = (chatListController?.navigationController as? NavigationController) { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId))) + } + }, error: { _ in + if let chatListController = chatListController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + chatListController.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + } + })) + f(.default) + }))) } } - }))) - } - -// if { - let location: TogglePeerChatPinnedLocation - var chatListFilter: ChatListFilter? - if case let .chatList(filter) = source, let chatFilter = filter { - chatListFilter = chatFilter - location = .filter(chatFilter.id) - } else { - location = .group(group) } - - let isPinned = getPinnedItemIds(transaction: transaction, location: location).contains(.peer(peerId)) - - if isPinned || chatListFilter == nil || peerId.namespace != Namespaces.Peer.SecretChat { - items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = (toggleItemPinned(postbox: context.account.postbox, location: location, itemId: .peer(peerId)) - |> deliverOnMainQueue).start(next: { result in - switch result { - case .done: - break - case .limitExceeded: - break - } - f(.default) - }) - }))) - } -// } - - if !isSavedMessages, let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { - var isMuted = false - if case .muted = notificationSettings.muteState { - isMuted = true - } - items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = (context.engine.peers.togglePeerMuted(peerId: peerId) - |> deliverOnMainQueue).start(completed: { - f(.default) - }) - }))) - } - } else { - if case .search = source { - if let _ = peer as? TelegramChannel { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in - var createSignal = context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peerId, hash: nil) - var cancelImpl: (() -> Void)? - let progressSignal = Signal { subscriber in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - chatListController?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } + + if case .chatList = source, groupAndIndex != nil { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in + if let chatListController = chatListController { + chatListController.deletePeerChat(peerId: peerId, joined: joined) } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - createSignal = createSignal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - let joinChannelDisposable = MetaDisposable() - cancelImpl = { - joinChannelDisposable.set(nil) - } - - joinChannelDisposable.set((createSignal - |> deliverOnMainQueue).start(next: { _ in - if let navigationController = (chatListController?.navigationController as? NavigationController) { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId))) - } - }, error: { _ in - if let chatListController = chatListController { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - chatListController.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - } - })) f(.default) }))) } + + if let item = items.last, case .separator = item { + items.removeLast() + } + + return items } } - - if case .chatList = source, groupAndIndex != nil { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in - if let chatListController = chatListController { - chatListController.deletePeerChat(peerId: peerId, joined: joined) - } - f(.default) - }))) - } - - if let item = items.last, case .separator = item { - items.removeLast() - } - - return items } } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index df54e4c982..34c62a399d 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -892,7 +892,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) |> mapToSignal { selectedPeerIdsAndFilterId -> Signal<(ChatListSelectionOptions, Set)?, NoError> in if let (selectedPeerIds, filterId) = selectedPeerIdsAndFilterId { - return chatListSelectionOptions(postbox: context.account.postbox, peerIds: selectedPeerIds, filterId: filterId) + return chatListSelectionOptions(context: context, peerIds: selectedPeerIds, filterId: filterId) |> map { options -> (ChatListSelectionOptions, Set)? in return (options, selectedPeerIds) } @@ -982,7 +982,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let strongSelf = self else { return } - let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + let _ = (strongSelf.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { [weak self] filters in guard let strongSelf = self else { return @@ -996,7 +996,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let strongSelf = self else { return } - let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + let _ = (strongSelf.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { presetList in guard let strongSelf = self else { return @@ -1024,7 +1024,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let strongSelf = self else { return } - let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + let _ = (strongSelf.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { presetList in guard let strongSelf = self else { return @@ -1032,7 +1032,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var found = false for filter in presetList { if filter.id == id { - let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox) + let _ = (strongSelf.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { filters in guard let strongSelf = self else { return @@ -1272,6 +1272,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } if !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing { + var isEditing = false + strongSelf.chatListDisplayNode.containerNode.updateState { state in + isEditing = state.editing + return state + } + if !isEditing { + strongSelf.editPressed() + } strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing = true if let layout = strongSelf.validLayout { strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) @@ -1292,7 +1300,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } |> take(1) - let initializedFilters = updatedChatListFiltersInfo(postbox: self.context.account.postbox) + let initializedFilters = self.context.engine.peers.updatedChatListFiltersInfo() |> mapToSignal { (filters, isInitialized) -> Signal in if isInitialized { return .single(!filters.isEmpty) @@ -1328,7 +1336,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let text: String if hasFilters { text = strongSelf.presentationData.strings.ChatList_TabIconFoldersTooltipNonEmptyFolders - let _ = markChatListFeaturedFiltersAsSeen(postbox: strongSelf.context.account.postbox).start() + let _ = strongSelf.context.engine.peers.markChatListFeaturedFiltersAsSeen().start() return } else { text = strongSelf.presentationData.strings.ChatList_TabIconFoldersTooltipEmptyFolders @@ -1487,7 +1495,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if let reorderedFilterIds = reorderedFilterIdsValue { - let _ = (updateChatListFiltersInteractively(postbox: self.context.account.postbox, { stateFilters in + let _ = (self.context.engine.peers.updateChatListFiltersInteractively { stateFilters in var updatedFilters: [ChatListFilter] = [] for id in reorderedFilterIds { if let index = stateFilters.firstIndex(where: { $0.id == id }) { @@ -1502,7 +1510,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } }) return updatedFilters - }) + } |> deliverOnMainQueue).start(completed: { [weak self] in guard let strongSelf = self else { return @@ -1535,7 +1543,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } |> distinctUntilChanged - let filterItems = chatListFilterItems(postbox: self.context.account.postbox) + let filterItems = chatListFilterItems(context: self.context) var notifiedFirstUpdate = false self.filterDisposable.set((combineLatest(queue: .mainQueue(), context.account.postbox.combinedView(keys: [ @@ -1644,7 +1652,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - let _ = (currentChatListFilters(postbox: self.context.account.postbox) + let _ = (self.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { [weak self] filters in guard let strongSelf = self else { return @@ -1701,7 +1709,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - let _ = updateChatListFiltersInteractively(postbox: strongSelf.context.account.postbox, { filters in + let _ = (strongSelf.context.engine.peers.updateChatListFiltersInteractively { filters in return filters.filter({ $0.id != id }) }).start() } @@ -2757,8 +2765,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) { let _ = (combineLatest(queue: .mainQueue(), - currentChatListFilters(postbox: self.context.account.postbox), - chatListFilterItems(postbox: self.context.account.postbox) + self.context.engine.peers.currentChatListFilters(), + chatListFilterItems(context: self.context) |> take(1) ) |> deliverOnMainQueue).start(next: { [weak self] presetList, filterItemsAndTotalCount in @@ -2766,7 +2774,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - let _ = markChatListFeaturedFiltersAsSeen(postbox: strongSelf.context.account.postbox).start() + let _ = strongSelf.context.engine.peers.markChatListFeaturedFiltersAsSeen().start() let (_, filterItems) = filterItemsAndTotalCount diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 09117ce5ca..8a3e9c436b 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -182,7 +182,7 @@ private final class ChatListShimmerNode: ASDisplayNode { let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let timestamp1: Int32 = 100000 let peers = SimpleDictionary() - let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in + let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 1a4932dd6b..aedd7d3f15 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -624,7 +624,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f } if applyAutomatically { - let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var filters = filters for i in 0 ..< filters.count { if filters[i].id == filter.id { @@ -634,7 +634,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f } } return filters - }) + } |> deliverOnMainQueue).start(next: { _ in controller?.dismiss() }) @@ -702,7 +702,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex excludePeers.sort() if applyAutomatically { - let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var filters = filters for i in 0 ..< filters.count { if filters[i].id == filter.id { @@ -714,7 +714,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex } } return filters - }) + } |> deliverOnMainQueue).start(next: { _ in controller?.dismiss() }) @@ -835,7 +835,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat includePeers.setPeers(state.additionallyIncludePeers) let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) - let _ = (currentChatListFilters(postbox: context.account.postbox) + let _ = (context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { filters in let controller = internalChatListFilterAddChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in skipStateAnimation = true @@ -856,7 +856,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat includePeers.setPeers(state.additionallyIncludePeers) let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) - let _ = (currentChatListFilters(postbox: context.account.postbox) + let _ = (context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { filters in let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in skipStateAnimation = true @@ -985,12 +985,12 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat var attemptNavigationImpl: (() -> Bool)? let applyImpl: (() -> Void)? = { let state = stateValue.with { $0 } - let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var includePeers = ChatListFilterIncludePeers() includePeers.setPeers(state.additionallyIncludePeers) var updatedFilter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) if currentPreset == nil { - updatedFilter.id = generateNewChatListFilterId(filters: filters) + updatedFilter.id = context.engine.peers.generateNewChatListFilterId(filters: filters) } var filters = filters if let _ = currentPreset { @@ -1017,7 +1017,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat filters.append(updatedFilter) } return filters - }) + } |> deliverOnMainQueue).start(next: { filters in updated(filters) dismissImpl?() diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 9142a50166..75cb00bd1f 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -249,12 +249,12 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch let arguments = ChatListFilterPresetListControllerArguments(context: context, addSuggestedPresed: { title, data in - let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var filters = filters - let id = generateNewChatListFilterId(filters: filters) + let id = context.engine.peers.generateNewChatListFilterId(filters: filters) filters.insert(ChatListFilter(id: id, title: title, emoticon: nil, data: data), at: 0) return filters - }) + } |> deliverOnMainQueue).start(next: { _ in }) }, openPreset: { preset in @@ -279,13 +279,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() - let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var filters = filters if let index = filters.firstIndex(where: { $0.id == id }) { filters.remove(at: index) } return filters - }) + } |> deliverOnMainQueue).start() }) ]), @@ -300,30 +300,12 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:]) - let filtersWithCountsSignal = updatedChatListFilters(postbox: context.account.postbox) + let filtersWithCountsSignal = context.engine.peers.updatedChatListFilters() |> distinctUntilChanged |> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in return .single(filters.map { filter -> (ChatListFilter, Int) in return (filter, 0) }) - /*return context.account.postbox.transaction { transaction -> [(ChatListFilter, Int)] in - return filters.map { filter -> (ChatListFilter, Int) in - let count: Int - if let cachedValue = chatCountCache.with({ dict -> Int? in - return dict[filter.data] - }) { - count = cachedValue - } else { - count = transaction.getChatCountMatchingPredicate(chatListFilterPredicate(filter: filter.data)) - let _ = chatCountCache.modify { dict in - var dict = dict - dict[filter.data] = count - return dict - } - } - return (filter, count) - } - }*/ } let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) @@ -368,7 +350,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch |> take(1) |> deliverOnMainQueue).start(next: { [weak updatedFilterOrder] updatedFilterOrderValue in if let updatedFilterOrderValue = updatedFilterOrderValue { - let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var updatedFilters: [ChatListFilter] = [] for id in updatedFilterOrderValue { if let index = filters.firstIndex(where: { $0.id == id }) { @@ -382,7 +364,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch } return updatedFilters - }) + } |> deliverOnMainQueue).start(next: { _ in filtersWithCounts.set(filtersWithCountsSignal) let _ = (filtersWithCounts.get() diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 604f798e6e..085803a21c 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1217,6 +1217,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self?.listNode.clearHighlightAnimated(true) }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in + }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { [weak self] peer, message, _ in interaction.dismissInput() @@ -2324,7 +2325,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { let timestamp1: Int32 = 100000 var peers = SimpleDictionary() peers[peer1.id] = peer1 - let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in + let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/ChatListUI/Sources/ChatListSelection.swift b/submodules/ChatListUI/Sources/ChatListSelection.swift index 869a2fae27..3c0c533c59 100644 --- a/submodules/ChatListUI/Sources/ChatListSelection.swift +++ b/submodules/ChatListUI/Sources/ChatListSelection.swift @@ -4,6 +4,7 @@ import SwiftSignalKit import Postbox import TelegramCore import SyncCore +import AccountContext enum ChatListSelectionReadOption: Equatable { case all(enabled: Bool) @@ -15,10 +16,10 @@ struct ChatListSelectionOptions: Equatable { let delete: Bool } -func chatListSelectionOptions(postbox: Postbox, peerIds: Set, filterId: Int32?) -> Signal { +func chatListSelectionOptions(context: AccountContext, peerIds: Set, filterId: Int32?) -> Signal { if peerIds.isEmpty { if let filterId = filterId { - return chatListFilterItems(postbox: postbox) + return chatListFilterItems(context: context) |> map { filterItems -> ChatListSelectionOptions in for (filter, unreadCount, _) in filterItems.1 { if filter.id == filterId { @@ -30,7 +31,7 @@ func chatListSelectionOptions(postbox: Postbox, peerIds: Set, filterId: |> distinctUntilChanged } else { let key = PostboxViewKey.unreadCounts(items: [.total(nil)]) - return postbox.combinedView(keys: [key]) + return context.account.postbox.combinedView(keys: [key]) |> map { view -> ChatListSelectionOptions in var hasUnread = false if let unreadCounts = view.views[key] as? UnreadMessageCountsView, let total = unreadCounts.total() { @@ -48,7 +49,7 @@ func chatListSelectionOptions(postbox: Postbox, peerIds: Set, filterId: } else { let items: [UnreadMessageCountsItem] = peerIds.map(UnreadMessageCountsItem.peer) let key = PostboxViewKey.unreadCounts(items: items) - return postbox.combinedView(keys: [key]) + return context.account.postbox.combinedView(keys: [key]) |> map { view -> ChatListSelectionOptions in var hasUnread = false if let unreadCounts = view.views[key] as? UnreadMessageCountsView { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index c28188e39f..59c3d3a530 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -696,7 +696,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0)) } - self.contextContainer.isGestureEnabled = enablePreview + self.contextContainer.isGestureEnabled = enablePreview && !item.editing } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { @@ -1509,7 +1509,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.onlineIsVoiceChat = onlineIsVoiceChat strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) - + if case .groupReference = item.content { strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index ca1b21806a..8f430c32b2 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -48,10 +48,16 @@ final class ChatListHighlightedLocation { } public final class ChatListNodeInteraction { + public enum PeerEntry { + case peerId(PeerId) + case peer(Peer) + } + let activateSearch: () -> Void let peerSelected: (Peer, ChatListNodeEntryPromoInfo?) -> Void let disabledPeerSelected: (Peer) -> Void let togglePeerSelected: (Peer) -> Void + let togglePeersSelection: ([PeerEntry], Bool) -> Void let additionalCategorySelected: (Int) -> Void let messageSelected: (Peer, Message, ChatListNodeEntryPromoInfo?) -> Void let groupSelected: (PeerGroupId) -> Void @@ -70,11 +76,12 @@ public final class ChatListNodeInteraction { public var searchTextHighightState: String? var highlightedChatLocation: ChatListHighlightedLocation? - public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (Peer) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (Peer, Message, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId, Bool) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (PeerId) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) { + public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (Peer) -> Void, togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (Peer, Message, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId, Bool) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (PeerId) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) { self.activateSearch = activateSearch self.peerSelected = peerSelected self.disabledPeerSelected = disabledPeerSelected self.togglePeerSelected = togglePeerSelected + self.togglePeersSelection = togglePeersSelection self.additionalCategorySelected = additionalCategorySelected self.messageSelected = messageSelected self.groupSelected = groupSelected @@ -565,6 +572,8 @@ public final class ChatListNode: ListView { var didBeginSelectingChats: (() -> Void)? public var selectionCountChanged: ((Int) -> Void)? + var isSelectionGestureEnabled = true + public init(context: AccountContext, groupId: PeerGroupId, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) { self.context = context self.groupId = groupId @@ -625,6 +634,34 @@ public final class ChatListNode: ListView { if didBeginSelecting { self?.didBeginSelectingChats?() } + }, togglePeersSelection: { [weak self] peers, selected in + self?.updateState { state in + var state = state + if selected { + for peerEntry in peers { + switch peerEntry { + case let .peer(peer): + state.selectedPeerIds.insert(peer.id) + state.selectedPeerMap[peer.id] = peer + case let .peerId(peerId): + state.selectedPeerIds.insert(peerId) + } + } + } else { + for peerEntry in peers { + switch peerEntry { + case let .peer(peer): + state.selectedPeerIds.remove(peer.id) + case let .peerId(peerId): + state.selectedPeerIds.remove(peerId) + } + } + } + return state + } + if selected && !peers.isEmpty { + self?.didBeginSelectingChats?() + } }, additionalCategorySelected: { [weak self] id in self?.additionalCategorySelected?(id) }, messageSelected: { [weak self] peer, message, promoInfo in @@ -666,7 +703,7 @@ public final class ChatListNode: ListView { } else { location = .group(groupId) } - let _ = (toggleItemPinned(postbox: context.account.postbox, location: location, itemId: itemId) + let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: itemId) |> deliverOnMainQueue).start(next: { result in if let strongSelf = self { switch result { @@ -1167,16 +1204,18 @@ public final class ChatListNode: ListView { } if case let .index(index) = fromEntry.sortIndex, let _ = index.pinningIndex { - return strongSelf.context.account.postbox.transaction { transaction -> Bool in - let location: TogglePeerChatPinnedLocation - if let chatListFilter = chatListFilter { - location = .filter(chatListFilter.id) - } else { - location = .group(groupId) - } - - var itemIds = getPinnedItemIds(transaction: transaction, location: location) - + let location: TogglePeerChatPinnedLocation + if let chatListFilter = chatListFilter { + location = .filter(chatListFilter.id) + } else { + location = .group(groupId) + } + + let engine = strongSelf.context.engine + return engine.peers.getPinnedItemIds(location: location) + |> mapToSignal { itemIds -> Signal in + var itemIds = itemIds + var itemId: PinnedItemId? switch fromEntry { case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): @@ -1184,7 +1223,7 @@ public final class ChatListNode: ListView { default: break } - + if let itemId = itemId { itemIds = itemIds.filter({ $0 != itemId }) if let referenceId = referenceId { @@ -1208,9 +1247,9 @@ public final class ChatListNode: ListView { } else { itemIds.append(itemId) } - return reorderPinnedItemIds(transaction: transaction, location: location, itemIds: itemIds) + return engine.peers.reorderPinnedItemIds(location: location, itemIds: itemIds) } else { - return false + return .single(false) } } } @@ -1315,6 +1354,15 @@ public final class ChatListNode: ListView { } self.resetFilter() + + let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:))) + selectionRecognizer.shouldBegin = { [weak self] in + guard let strongSelf = self else { + return false + } + return strongSelf.isSelectionGestureEnabled + } + self.view.addGestureRecognizer(selectionRecognizer) } deinit { @@ -1332,7 +1380,7 @@ public final class ChatListNode: ListView { private func resetFilter() { if let chatListFilter = self.chatListFilter { - self.updatedFilterDisposable.set((updatedChatListFilters(postbox: self.context.account.postbox) + self.updatedFilterDisposable.set((self.context.engine.peers.updatedChatListFilters() |> map { filters -> ChatListFilter? in for filter in filters { if filter.id == chatListFilter.id { @@ -1898,6 +1946,140 @@ public final class ChatListNode: ListView { } return nil } + + private func peerAtPoint(_ point: CGPoint) -> Peer? { + var resultPeer: Peer? + self.forEachVisibleItemNode { itemNode in + if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { + if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { + switch item.content { + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _): + resultPeer = peer.peer + default: + break + } + } + } + } + return resultPeer + } + + private var selectionPanState: (selecting: Bool, initialPeerId: PeerId, toggledPeerIds: [[PeerId]])? + private var selectionScrollActivationTimer: SwiftSignalKit.Timer? + private var selectionScrollDisplayLink: ConstantDisplayLinkAnimator? + private var selectionScrollDelta: CGFloat? + private var selectionLastLocation: CGPoint? + + @objc private func selectionPanGesture(_ recognizer: UIGestureRecognizer) -> Void { + let location = recognizer.location(in: self.view) + switch recognizer.state { + case .began: + if let peer = self.peerAtPoint(location) { + let selecting = !self.currentState.selectedPeerIds.contains(peer.id) + self.selectionPanState = (selecting, peer.id, []) + self.interaction?.togglePeersSelection([.peer(peer)], selecting) + } + case .changed: + self.handlePanSelection(location: location) + self.selectionLastLocation = location + case .ended, .failed, .cancelled: + self.selectionPanState = nil + self.selectionScrollDisplayLink = nil + self.selectionScrollActivationTimer?.invalidate() + self.selectionScrollActivationTimer = nil + self.selectionScrollDelta = nil + self.selectionLastLocation = nil + self.selectionScrollSkipUpdate = false + case .possible: + break + @unknown default: + fatalError() + } + } + + private func handlePanSelection(location: CGPoint) { + if let state = self.selectionPanState { + if let peer = self.peerAtPoint(location) { + if peer.id == state.initialPeerId { + if !state.toggledPeerIds.isEmpty { + self.interaction?.togglePeersSelection(state.toggledPeerIds.flatMap { $0.compactMap({ .peerId($0) }) }, !state.selecting) + self.selectionPanState = (state.selecting, state.initialPeerId, []) + } + } else if state.toggledPeerIds.last?.first != peer.id { + var updatedToggledPeerIds: [[PeerId]] = [] + var previouslyToggled = false + for i in (0 ..< state.toggledPeerIds.count) { + if let peerId = state.toggledPeerIds[i].first { + if peerId == peer.id { + previouslyToggled = true + updatedToggledPeerIds = Array(state.toggledPeerIds.prefix(i + 1)) + + let peerIdsToToggle = Array(state.toggledPeerIds.suffix(state.toggledPeerIds.count - i - 1)).flatMap { $0 } + self.interaction?.togglePeersSelection(peerIdsToToggle.compactMap { .peerId($0) }, !state.selecting) + break + } + } + } + + if !previouslyToggled { + updatedToggledPeerIds = state.toggledPeerIds + let isSelected = self.currentState.selectedPeerIds.contains(peer.id) + if state.selecting != isSelected { + updatedToggledPeerIds.append([peer.id]) + self.interaction?.togglePeersSelection([.peer(peer)], state.selecting) + } + } + + self.selectionPanState = (state.selecting, state.initialPeerId, updatedToggledPeerIds) + } + } + + let scrollingAreaHeight: CGFloat = 50.0 + if location.y < scrollingAreaHeight + self.insets.top || location.y > self.frame.height - scrollingAreaHeight - self.insets.bottom { + if location.y < self.frame.height / 2.0 { + self.selectionScrollDelta = (scrollingAreaHeight - (location.y - self.insets.top)) / scrollingAreaHeight + } else { + self.selectionScrollDelta = -(scrollingAreaHeight - min(scrollingAreaHeight, max(0.0, (self.frame.height - self.insets.bottom - location.y)))) / scrollingAreaHeight + } + if let displayLink = self.selectionScrollDisplayLink { + displayLink.isPaused = false + } else { + if let _ = self.selectionScrollActivationTimer { + } else { + let timer = SwiftSignalKit.Timer(timeout: 0.45, repeat: false, completion: { [weak self] in + self?.setupSelectionScrolling() + }, queue: .mainQueue()) + timer.start() + self.selectionScrollActivationTimer = timer + } + } + } else { + self.selectionScrollDisplayLink?.isPaused = true + self.selectionScrollActivationTimer?.invalidate() + self.selectionScrollActivationTimer = nil + } + } + } + + private var selectionScrollSkipUpdate = false + private func setupSelectionScrolling() { + self.selectionScrollDisplayLink = ConstantDisplayLinkAnimator(update: { [weak self] in + self?.selectionScrollActivationTimer = nil + if let strongSelf = self, let delta = strongSelf.selectionScrollDelta { + let distance: CGFloat = 15.0 * min(1.0, 0.15 + abs(delta * delta)) + let direction: ListViewScrollDirection = delta > 0.0 ? .up : .down + let _ = strongSelf.scrollWithDirection(direction, distance: distance) + + if let location = strongSelf.selectionLastLocation { + if !strongSelf.selectionScrollSkipUpdate { + strongSelf.handlePanSelection(location: location) + } + strongSelf.selectionScrollSkipUpdate = !strongSelf.selectionScrollSkipUpdate + } + } + }) + self.selectionScrollDisplayLink?.isPaused = false + } } private func statusStringForPeerType(accountPeerId: PeerId, strings: PresentationStrings, peer: Peer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> (String, Bool)? { @@ -1951,3 +2133,65 @@ private func statusStringForPeerType(accountPeerId: PeerId, strings: Presentatio } return (strings.ChatList_PeerTypeNonContact, false) } + +public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { + private let selectionGestureActivationThreshold: CGFloat = 5.0 + + var recognized: Bool? = nil + var initialLocation: CGPoint = CGPoint() + + public var shouldBegin: (() -> Bool)? + + public override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.minimumNumberOfTouches = 2 + self.maximumNumberOfTouches = 2 + } + + public override func reset() { + super.reset() + + self.recognized = nil + } + + public override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if let shouldBegin = self.shouldBegin, !shouldBegin() { + self.state = .failed + } else { + let touch = touches.first! + self.initialLocation = touch.location(in: self.view) + } + } + + public override func touchesMoved(_ touches: Set, with event: UIEvent) { + let location = touches.first!.location(in: self.view) + let translation = location.offsetBy(dx: -self.initialLocation.x, dy: -self.initialLocation.y) + + let touchesArray = Array(touches) + if self.recognized == nil, touchesArray.count == 2 { + if let firstTouch = touchesArray.first, let secondTouch = touchesArray.last { + let firstLocation = firstTouch.location(in: self.view) + let secondLocation = secondTouch.location(in: self.view) + + func distance(_ v1: CGPoint, _ v2: CGPoint) -> CGFloat { + let dx = v1.x - v2.x + let dy = v1.y - v2.y + return sqrt(dx * dx + dy * dy) + } + if distance(firstLocation, secondLocation) > 200.0 { + self.state = .failed + } + } + if self.state != .failed && (abs(translation.y) >= selectionGestureActivationThreshold) { + self.recognized = true + } + } + + if let recognized = self.recognized, recognized { + super.touchesMoved(touches, with: event) + } + } +} diff --git a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift index ab390011a9..e930adb883 100644 --- a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift +++ b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift @@ -10,8 +10,8 @@ import Postbox import TelegramUIPreferences import TelegramCore -func chatListFilterItems(postbox: Postbox) -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> { - return updatedChatListFilters(postbox: postbox) +func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> { + return context.engine.peers.updatedChatListFilters() |> distinctUntilChanged |> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> in var unreadCountItems: [UnreadMessageCountsItem] = [] @@ -40,8 +40,8 @@ func chatListFilterItems(postbox: Postbox) -> Signal<(Int, [(ChatListFilter, Int keys.append(.basicPeer(peerId)) } - return combineLatest(queue: postbox.queue, - postbox.combinedView(keys: keys), + return combineLatest(queue: context.account.postbox.queue, + context.account.postbox.combinedView(keys: keys), Signal.single(true) ) |> map { view, _ -> (Int, [(ChatListFilter, Int, Bool)]) in @@ -63,7 +63,7 @@ func chatListFilterItems(postbox: Postbox) -> Signal<(Int, [(ChatListFilter, Int case let .peer(peerId, state): if let state = state, state.isUnread { if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer { - let tag = postbox.seedConfiguration.peerSummaryCounterTags(peer, peerView.isContact) + let tag = context.account.postbox.seedConfiguration.peerSummaryCounterTags(peer, peerView.isContact) var peerCount = Int(state.count) if state.isUnread { diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 2e2c6c20ee..8a796df36a 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -574,10 +574,11 @@ private enum DebugControllerEntry: ItemListNodeEntry { if let context = arguments.context { let _ = (context.account.postbox.transaction { transaction -> Void in transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedPollResults) - unmarkChatListFeaturedFiltersAsSeen(transaction: transaction) transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks) }).start() + + let _ = context.engine.peers.unmarkChatListFeaturedFiltersAsSeen() } }) case .crash: diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index 2e59bccfd9..0322282174 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -234,6 +234,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { } var apparentHeight: CGFloat = 0.0 + public private(set) var apparentHeightTransition: (CGFloat, CGFloat)? private var _bounds: CGRect = CGRect() private var _position: CGPoint = CGPoint() @@ -478,6 +479,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { } public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { + self.apparentHeightTransition = (self.apparentHeight, value) let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { strongSelf.apparentHeight = currentValue diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 6df461f382..3d36be6ece 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -1025,7 +1025,14 @@ public class TextNode: ASDisplayNode { layoutSize.height += fontLineSpacing } - let lineRange = CFRangeMake(lastLineCharacterIndex, lineCharacterCount) + var lineRange = CFRangeMake(lastLineCharacterIndex, lineCharacterCount) + if lineRange.location + lineRange.length > attributedString.length { + lineRange.length = attributedString.length - lineRange.location + } + if lineRange.length < 0 { + break + } + let coreTextLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 100.0) lastLineCharacterIndex += lineCharacterCount diff --git a/submodules/FFMpegBinding/Sources/FFMpegSWResample.m b/submodules/FFMpegBinding/Sources/FFMpegSWResample.m index fbecf0555b..16c2a888dc 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegSWResample.m +++ b/submodules/FFMpegBinding/Sources/FFMpegSWResample.m @@ -6,6 +6,7 @@ #import "libswresample/swresample.h" @interface FFMpegSWResample () { + int _sourceChannelCount; SwrContext *_context; NSUInteger _ratio; NSInteger _destinationChannelCount; @@ -21,6 +22,7 @@ - (instancetype)initWithSourceChannelCount:(NSInteger)sourceChannelCount sourceSampleRate:(NSInteger)sourceSampleRate sourceSampleFormat:(enum FFMpegAVSampleFormat)sourceSampleFormat destinationChannelCount:(NSInteger)destinationChannelCount destinationSampleRate:(NSInteger)destinationSampleRate destinationSampleFormat:(enum FFMpegAVSampleFormat)destinationSampleFormat { self = [super init]; if (self != nil) { + _sourceChannelCount = sourceChannelCount; _destinationChannelCount = destinationChannelCount; _destinationSampleFormat = destinationSampleFormat; _context = swr_alloc_set_opts(NULL, @@ -47,6 +49,12 @@ - (NSData * _Nullable)resample:(FFMpegAVFrame *)frame { AVFrame *frameImpl = (AVFrame *)[frame impl]; + + int numChannels = frameImpl->channels; + if (numChannels != _sourceChannelCount) { + return nil; + } + int bufSize = av_samples_get_buffer_size(NULL, (int)_destinationChannelCount, frameImpl->nb_samples * (int)_ratio, diff --git a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift index ec9f878c38..21fcde858a 100644 --- a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift +++ b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift @@ -71,7 +71,9 @@ public func adjustSaturationInContext(context: DrawingContext, saturation: CGFlo vImageMatrixMultiply_ARGB8888(&buffer, &buffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile)) } -private func generateGradient(size: CGSize, colors: [UIColor], positions: [CGPoint], adjustSaturation: CGFloat = 1.0) -> UIImage { +private func generateGradient(size: CGSize, colors inputColors: [UIColor], positions: [CGPoint], adjustSaturation: CGFloat = 1.0) -> UIImage { + let colors: [UIColor] = inputColors.count == 1 ? [inputColors[0], inputColors[0], inputColors[0]] : inputColors + let width = Int(size.width) let height = Int(size.height) @@ -146,10 +148,29 @@ private func generateGradient(size: CGSize, colors: [UIColor], positions: [CGPoi b = b + distance * rgb[i * 3 + 2] } + if distanceSum < 0.00001 { + distanceSum = 0.00001 + } + + var pixelB = b / distanceSum * 255.0 + if pixelB > 255.0 { + pixelB = 255.0 + } + + var pixelG = g / distanceSum * 255.0 + if pixelG > 255.0 { + pixelG = 255.0 + } + + var pixelR = r / distanceSum * 255.0 + if pixelR > 255.0 { + pixelR = 255.0 + } + let pixelBytes = lineBytes.advanced(by: x * 4) - pixelBytes.advanced(by: 0).pointee = UInt8(b / distanceSum * 255.0) - pixelBytes.advanced(by: 1).pointee = UInt8(g / distanceSum * 255.0) - pixelBytes.advanced(by: 2).pointee = UInt8(r / distanceSum * 255.0) + pixelBytes.advanced(by: 0).pointee = UInt8(pixelB) + pixelBytes.advanced(by: 1).pointee = UInt8(pixelG) + pixelBytes.advanced(by: 2).pointee = UInt8(pixelR) pixelBytes.advanced(by: 3).pointee = 0xff } } diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 5be46fde59..982a409f23 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -52,6 +52,7 @@ public final class HashtagSearchController: TelegramBaseController { }, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in + }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { [weak self] peer, message, _ in if let strongSelf = self { diff --git a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift index 08e410893a..2502a6a12c 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift @@ -387,7 +387,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in dismissAction() dismissImpl?() - let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) + let _ = (context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: invite.link) |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic)) |> deliverOnMainQueue).start(next: { invite in switch invite { @@ -444,7 +444,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in let usageLimit = state.usage.value if invite == nil { - let _ = (createPeerExportedInvitation(account: context.account, peerId: peerId, expireDate: expireDate, usageLimit: usageLimit) + let _ = (context.engine.peers.createPeerExportedInvitation(peerId: peerId, expireDate: expireDate, usageLimit: usageLimit) |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic)) |> deliverOnMainQueue).start(next: { invite in completion?(invite) @@ -458,7 +458,7 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }) } else if let invite = invite { - let _ = (editPeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link, expireDate: expireDate, usageLimit: usageLimit) + let _ = (context.engine.peers.editPeerExportedInvitation(peerId: peerId, link: invite.link, expireDate: expireDate, usageLimit: usageLimit) |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic)) |> deliverOnMainQueue).start(next: { invite in completion?(invite) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift index 68d2764e4b..02985f64fe 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift @@ -285,7 +285,7 @@ public final class InviteLinkInviteController: ViewController { self.presentationDataPromise = Promise(self.presentationData) self.controller = controller - self.invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: false) + self.invitesContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: false) self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) @@ -396,7 +396,7 @@ public final class InviteLinkInviteController: ViewController { dismissAction() if let invite = invite { - let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in + let _ = (context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in if let result = result, case let .replace(_, invite) = result { mainInvitePromise.set(invite) } diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index 9314f186b7..973d154cd1 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -418,12 +418,12 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad var getControllerImpl: (() -> ViewController?)? let adminId = admin?.peer.peer?.id - let invitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: false, forceUpdate: true) - let revokedInvitesContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true) + let invitesContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: adminId, revoked: false, forceUpdate: true) + let revokedInvitesContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: adminId, revoked: true, forceUpdate: true) let creators: Signal<[ExportedInvitationCreator], NoError> if adminId == nil { - creators = .single([]) |> then(peerExportedInvitationsCreators(account: context.account, peerId: peerId)) + creators = .single([]) |> then(context.engine.peers.peerExportedInvitationsCreators(peerId: peerId)) } else { creators = .single([]) } @@ -520,7 +520,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad } } if revoke { - revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in + revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in updateState { state in var updatedState = state updatedState.revokingPrivateLink = false @@ -661,7 +661,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteLinkAlert_Action, color: .destructive, action: { dismissAction() - revokeLinkDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { + revokeLinkDisposable.set((context.engine.peers.deletePeerExportedInvitation(peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { })) revokedInvitesContext.remove(invite) @@ -695,7 +695,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { dismissAction() - revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in + revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in if case let .replace(_, newInvite) = result { invitesContext.add(newInvite) } @@ -732,7 +732,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad ActionSheetButtonItem(title: presentationData.strings.InviteLink_DeleteAllRevokedLinksAlert_Action, color: .destructive, action: { dismissAction() - deleteAllRevokedLinksDisposable.set((deleteAllRevokedPeerExportedInvitations(account: context.account, peerId: peerId, adminId: adminId ?? context.account.peerId) |> deliverOnMainQueue).start(completed: { + deleteAllRevokedLinksDisposable.set((context.engine.peers.deleteAllRevokedPeerExportedInvitations(peerId: peerId, adminId: adminId ?? context.account.peerId) |> deliverOnMainQueue).start(completed: { })) revokedInvitesContext.clear() @@ -770,7 +770,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad |> distinctUntilChanged |> deliverOnMainQueue |> map { invite -> PeerInvitationImportersContext? in - return invite.flatMap { PeerInvitationImportersContext(account: context.account, peerId: peerId, invite: $0) } + return invite.flatMap { context.engine.peers.peerInvitationImporters(peerId: peerId, invite: $0) } } |> afterNext { context in if let context = context { importersState.set(context.state |> map(Optional.init)) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index 8e1eb4dcc3..3078d20011 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -374,7 +374,7 @@ public final class InviteLinkViewController: ViewController { self.presentationDataPromise = Promise(self.presentationData) self.controller = controller - self.importersContext = importersContext ?? PeerInvitationImportersContext(account: context.account, peerId: peerId, invite: invite) + self.importersContext = importersContext ?? context.engine.peers.peerInvitationImporters(peerId: peerId, invite: invite) self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) @@ -483,7 +483,7 @@ public final class InviteLinkViewController: ViewController { dismissAction() self?.controller?.dismiss() - let _ = (deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { + let _ = (context.engine.peers.deletePeerExportedInvitation(peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: { }) self?.controller?.revokedInvitationsContext?.remove(invite) @@ -537,7 +537,7 @@ public final class InviteLinkViewController: ViewController { dismissAction() self?.controller?.dismiss() - let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in + let _ = (context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in if case let .replace(_, newInvite) = result { self?.controller?.invitationsContext?.add(newInvite) } diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift index e44ea25125..d7cbb6817b 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift @@ -70,7 +70,7 @@ public final class JoinLinkPreviewController: ViewController { if let resolvedState = self.resolvedState { signal = .single(resolvedState) } else { - signal = joinLinkInformation(self.link, account: self.context.account) + signal = self.context.engine.peers.joinLinkInformation(self.link) } self.disposable.set((signal @@ -121,7 +121,7 @@ public final class JoinLinkPreviewController: ViewController { } private func join() { - self.disposable.set((joinChatInteractively(with: self.link, account: self.context.account) |> deliverOnMainQueue).start(next: { [weak self] peerId in + self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] peerId in if let strongSelf = self { if let peerId = peerId { strongSelf.navigateToPeer(peerId, nil) diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 5f17313390..65b20a3dd2 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -1414,7 +1414,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, return 64; case TGMediaVideoConversionPresetVideoMessage: - return 32; + return 64; case TGMediaVideoConversionPresetAnimation: case TGMediaVideoConversionPresetProfile: diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m index c0ba0e2c91..fa503a47a3 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m @@ -1414,7 +1414,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (CGFloat)_brushWeightForSize:(CGFloat)size { - return [self _brushBaseWeightForCurrentPainting] + [self _brushWeightRangeForCurrentPainting] * size; + return ([self _brushBaseWeightForCurrentPainting] + [self _brushWeightRangeForCurrentPainting] * size) / _scrollView.zoomScale; } + (CGSize)maximumPaintingSize @@ -1739,6 +1739,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; { [self adjustZoom]; + TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch; + [_canvasView setBrushWeight:[self _brushWeightForSize:currentSwatch.brushWeight]]; + if (_scrollView.zoomScale < _scrollView.normalZoomScale - FLT_EPSILON) { [TGHacks setAnimationDurationFactor:0.5f]; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift index 59af73ea1a..dd9295eceb 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift @@ -5,6 +5,7 @@ import TelegramCore import SyncCore import LegacyComponents import SwiftSignalKit +import AccountContext public class VideoConversionWatcher: TGMediaVideoFileWatcher { private let update: (String, Int) -> Void @@ -44,7 +45,7 @@ public final class LegacyLiveUploadInterfaceResult: NSObject { } public final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUploadInterface { - private let account: Account + private let context: AccountContext private let id: Int64 private var path: String? private var size: Int? @@ -52,8 +53,8 @@ public final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUplo private let data = Promise() private let dataValue = Atomic(value: nil) - public init(account: Account) { - self.account = account + public init(context: AccountContext) { + self.context = context self.id = Int64.random(in: Int64.min ... Int64.max) var updateImpl: ((String, Int) -> Void)? @@ -65,7 +66,7 @@ public final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUplo if let strongSelf = self { if strongSelf.path == nil { strongSelf.path = path - strongSelf.account.messageMediaPreuploadManager.add(network: strongSelf.account.network, postbox: strongSelf.account.postbox, id: strongSelf.id, encrypt: false, tag: nil, source: strongSelf.data.get()) + strongSelf.context.engine.resources.preUpload(id: strongSelf.id, encrypt: false, tag: nil, source: strongSelf.data.get()) } strongSelf.size = size diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacySuggestionContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacySuggestionContext.swift index 0499bb60d5..cc7dcb02ff 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacySuggestionContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacySuggestionContext.swift @@ -46,7 +46,7 @@ public func legacySuggestionContext(context: AccountContext, peerId: PeerId, cha } suggestionContext.hashtagListSignal = { query in return SSignal { subscriber in - let disposable = (recentlyUsedHashtags(postbox: context.account.postbox) |> map { hashtags -> [String] in + let disposable = (context.engine.messages.recentlyUsedHashtags() |> map { hashtags -> [String] in let normalizedQuery = query?.lowercased() var result: [String] = [] if let normalizedQuery = normalizedQuery { diff --git a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift index d407c26456..14e1fa6448 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift @@ -44,11 +44,11 @@ public final class LiveLocationManagerImpl: LiveLocationManager { private var invalidationTimer: (SwiftSignalKit.Timer, Int32)? - public init(account: Account, locationManager: DeviceLocationManager, inForeground: Signal) { + public init(engine: TelegramEngine, account: Account, locationManager: DeviceLocationManager, inForeground: Signal) { self.account = account self.locationManager = locationManager - self.summaryManagerImpl = LiveLocationSummaryManagerImpl(queue: self.queue, postbox: account.postbox, accountPeerId: account.peerId, viewTracker: account.viewTracker) + self.summaryManagerImpl = LiveLocationSummaryManagerImpl(queue: self.queue, engine: engine, postbox: account.postbox, accountPeerId: account.peerId, viewTracker: account.viewTracker) let viewKey: PostboxViewKey = .localMessageTag(.OutgoingLiveLocation) self.messagesDisposable = (account.postbox.combinedView(keys: [viewKey]) diff --git a/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift index 93440b5d88..db91916199 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift @@ -79,6 +79,7 @@ private final class LiveLocationSummaryContext { private final class LiveLocationPeerSummaryContext { private let queue: Queue + private let engine: TelegramEngine private let accountPeerId: PeerId private let viewTracker: AccountViewTracker private let peerId: PeerId @@ -116,8 +117,9 @@ private final class LiveLocationPeerSummaryContext { private let peerDisposable = MetaDisposable() - init(queue: Queue, accountPeerId: PeerId, viewTracker: AccountViewTracker, peerId: PeerId, becameEmpty: @escaping () -> Void) { + init(queue: Queue, engine: TelegramEngine, accountPeerId: PeerId, viewTracker: AccountViewTracker, peerId: PeerId, becameEmpty: @escaping () -> Void) { self.queue = queue + self.engine = engine self.accountPeerId = accountPeerId self.viewTracker = viewTracker self.peerId = peerId @@ -160,7 +162,7 @@ private final class LiveLocationPeerSummaryContext { private func updateSubscription() { if self.isActive || !self.subscribers.isEmpty { - self.peerDisposable.set((topPeerActiveLiveLocationMessages(viewTracker: self.viewTracker, accountPeerId: self.accountPeerId, peerId: self.peerId) + self.peerDisposable.set((self.engine.messages.topPeerActiveLiveLocationMessages(peerId: self.peerId) |> deliverOn(self.queue)).start(next: { [weak self] accountPeer, messages in if let strongSelf = self { var peersAndMessages: [(Peer, Message)] = [] @@ -187,6 +189,7 @@ private final class LiveLocationPeerSummaryContext { public final class LiveLocationSummaryManagerImpl: LiveLocationSummaryManager { private let queue: Queue + private let engine: TelegramEngine private let postbox: Postbox private let accountPeerId: PeerId private let viewTracker: AccountViewTracker @@ -194,9 +197,10 @@ public final class LiveLocationSummaryManagerImpl: LiveLocationSummaryManager { private let globalContext: LiveLocationSummaryContext private var peerContexts: [PeerId: LiveLocationPeerSummaryContext] = [:] - init(queue: Queue, postbox: Postbox, accountPeerId: PeerId, viewTracker: AccountViewTracker) { + init(queue: Queue, engine: TelegramEngine, postbox: Postbox, accountPeerId: PeerId, viewTracker: AccountViewTracker) { assert(queue.isCurrent()) self.queue = queue + self.engine = engine self.postbox = postbox self.accountPeerId = accountPeerId self.viewTracker = viewTracker @@ -212,7 +216,7 @@ public final class LiveLocationSummaryManagerImpl: LiveLocationSummaryManager { for peerId in peerIds { if self.peerContexts[peerId] == nil { - let context = LiveLocationPeerSummaryContext(queue: self.queue, accountPeerId: self.accountPeerId, viewTracker: self.viewTracker, peerId: peerId, becameEmpty: { [weak self] in + let context = LiveLocationPeerSummaryContext(queue: self.queue, engine: self.engine, accountPeerId: self.accountPeerId, viewTracker: self.viewTracker, peerId: peerId, becameEmpty: { [weak self] in if let strongSelf = self, let context = strongSelf.peerContexts[peerId], context.isEmpty { strongSelf.peerContexts.removeValue(forKey: peerId) } @@ -242,7 +246,7 @@ public final class LiveLocationSummaryManagerImpl: LiveLocationSummaryManager { if let current = strongSelf.peerContexts[peerId] { context = current } else { - context = LiveLocationPeerSummaryContext(queue: strongSelf.queue, accountPeerId: strongSelf.accountPeerId, viewTracker: strongSelf.viewTracker, peerId: peerId, becameEmpty: { + context = LiveLocationPeerSummaryContext(queue: strongSelf.queue, engine: strongSelf.engine, accountPeerId: strongSelf.accountPeerId, viewTracker: strongSelf.viewTracker, peerId: peerId, becameEmpty: { if let strongSelf = self, let context = strongSelf.peerContexts[peerId], context.isEmpty { strongSelf.peerContexts.removeValue(forKey: peerId) } diff --git a/submodules/LocationUI/Sources/LocationUtils.swift b/submodules/LocationUI/Sources/LocationUtils.swift index 31d343c199..604c803418 100644 --- a/submodules/LocationUI/Sources/LocationUtils.swift +++ b/submodules/LocationUI/Sources/LocationUtils.swift @@ -43,7 +43,7 @@ public func nearbyVenues(context: AccountContext, latitude: Double, longitude: D guard let peerId = peerId else { return .single(nil) } - return requestChatContextResults(account: context.account, botId: peerId, peerId: context.account.peerId, query: query ?? "", location: .single((latitude, longitude)), offset: "") + return context.engine.messages.requestChatContextResults(botId: peerId, peerId: context.account.peerId, query: query ?? "", location: .single((latitude, longitude)), offset: "") |> map { results -> ChatContextResultCollection? in return results?.results } diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index 0bd8ad9564..179270366f 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -279,7 +279,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan } } - let liveLocations = topPeerActiveLiveLocationMessages(viewTracker: context.account.viewTracker, accountPeerId: context.account.peerId, peerId: subject.id.peerId) + let liveLocations = context.engine.messages.topPeerActiveLiveLocationMessages(peerId: subject.id.peerId) |> map { _, messages -> [Message] in return messages } diff --git a/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift b/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift index 9f5bf6a521..1dde44442a 100644 --- a/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift +++ b/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift @@ -132,6 +132,11 @@ open class ManagedAnimationNode: ASDisplayNode { private let imageNode: ASImageNode private let displayLink: CADisplayLink + public var imageUpdated: ((UIImage) -> Void)? + public var image: UIImage? { + return self.imageNode.image + } + public var state: ManagedAnimationState? public var trackStack: [ManagedAnimationItem] = [] public var didTryAdvancingState = false @@ -260,6 +265,7 @@ open class ManagedAnimationNode: ASDisplayNode { } else { self.imageNode.image = image } + self.imageUpdated?(image) } for (callbackFrame, callback) in state.item.callbacks { diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index b7ce647975..b97a260cb9 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -317,13 +317,17 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { if value != self._statusValue { if let value = value, value.seekId == self.ignoreSeekId { } else { + let previousStatusValue = self._statusValue self._statusValue = value self.updateProgressAnimations() - let playbackStatus = value?.status + var playbackStatus = value?.status if self.playbackStatusValue != playbackStatus { self.playbackStatusValue = playbackStatus if let playbackStatusUpdated = self.playbackStatusUpdated { + if playbackStatus == .paused, previousStatusValue?.status == .playing, let value = value, value.timestamp > value.duration - 0.1 { + playbackStatus = .playing + } playbackStatusUpdated(playbackStatus) } } diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift index 088cb5b7c0..0050da048a 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift @@ -362,6 +362,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode { if !iconFrame.isEmpty { self.iconNode.animateIn(fromScale: 0.416) self.iconNode.layer.animatePosition(from: iconFrame.center.offsetBy(dx: 6.0, dy: 6.0), to: self.iconNode.layer.position, duration: 0.45) + + Queue.mainQueue().after(0.45) { + self.hapticFeedback.impact(.medium) + } } self.subtitleNode.isHidden = true diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index 3290d91cd4..92e4fa5323 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -1014,7 +1014,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega if self.isFailed != oldValue { UIView.transition(with: self.view, duration: 0.2, options: [.transitionCrossDissolve, .curveEaseInOut]) { self.inputNode.textField.textColor = self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.list.freePlainInputField.primaryColor - self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.actionSheet.inputClearButtonColor, on: !self.inputNode.textField.isSecureTextEntry), for: []) + self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.list.freePlainInputField.controlColor, on: !self.inputNode.textField.isSecureTextEntry), for: []) self.backgroundNode.image = self.isFailed ? generateStretchableFilledCircleImage(diameter: 20.0, color: self.theme.list.itemDestructiveColor.withAlphaComponent(0.1)) : generateStretchableFilledCircleImage(diameter: 20.0, color: self.theme.list.freePlainInputField.backgroundColor) } completion: { _ in @@ -1194,7 +1194,7 @@ private final class TwoFactorDataInputTextNode: ASDisplayNode, UITextFieldDelega } func updateTextHidden(_ value: Bool) { - self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.actionSheet.inputClearButtonColor, on: !value), for: []) + self.hideButtonNode.setImage(generateTextHiddenImage(color: self.isFailed ? self.theme.list.itemDestructiveColor : self.theme.list.freePlainInputField.controlColor, on: !value), for: []) let text = self.inputNode.textField.text ?? "" self.inputNode.textField.isSecureTextEntry = value if value { diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index eb88e7475f..89171cffca 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -43,7 +43,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Si return .single((true, [])) } } else { - return fetchAndUpdateCachedPeerData(accountPeerId: context.account.peerId, peerId: peerId, network: context.account.network, postbox: context.account.postbox) + return context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peerId) |> map { _ -> (Bool, [AvatarGalleryEntry])? in return nil } @@ -748,7 +748,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr case let .image(_, reference, _, _, _, _, _, _, _, _): if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) { if let reference = reference { - let _ = (updatePeerPhotoExisting(network: self.context.account.network, reference: reference) + let _ = (self.context.engine.accountData.updatePeerPhotoExisting(reference: reference) |> deliverOnMainQueue).start(next: { [weak self] photo in if let strongSelf = self, let photo = photo, let firstEntry = strongSelf.entries.first, case let .image(image) = firstEntry { let updatedEntry = AvatarGalleryEntry.image(photo.imageId, photo.reference, photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), strongSelf.peer, image.5, image.6, image.7, photo.immediateThumbnailData, image.9) @@ -860,7 +860,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr if self.peer.id == self.context.account.peerId { } else { if entry == self.entries.first { - let _ = updatePeerPhoto(postbox: self.context.account.postbox, network: self.context.account.network, stateManager: self.context.account.stateManager, accountPeerId: self.context.account.peerId, peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() + let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() dismiss = true } else { if let index = self.entries.firstIndex(of: entry) { @@ -872,7 +872,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr case let .image(_, reference, _, _, _, _, _, messageId, _, _): if self.peer.id == self.context.account.peerId { if let reference = reference { - let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start() + let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start() } if entry == self.entries.first { dismiss = true @@ -889,7 +889,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } if entry == self.entries.first { - let _ = updatePeerPhoto(postbox: self.context.account.postbox, network: self.context.account.network, stateManager: self.context.account.stateManager, accountPeerId: self.context.account.peerId, peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() + let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() dismiss = true } else { if let index = self.entries.firstIndex(of: entry) { diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index ad0955305b..5b725066b0 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -114,6 +114,18 @@ public enum PeerInfoAvatarListItem: Equatable { } } + var representations: [ImageRepresentationWithReference] { + switch self { + case .custom: + return [] + case let .topImage(representations, _, _): + return representations + case let .image(_, representations, _, _): + return representations + } + } + + var videoRepresentations: [VideoRepresentationWithReference] { switch self { case .custom: @@ -125,11 +137,14 @@ public enum PeerInfoAvatarListItem: Equatable { } } - public init(entry: AvatarGalleryEntry) { + public init?(entry: AvatarGalleryEntry) { switch entry { case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _): self = .topImage(representations, videoRepresentations, immediateThumbnailData) case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): + if representations.isEmpty { + return nil + } self = .image(reference, representations, videoRepresentations, immediateThumbnailData) } } @@ -876,6 +891,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { entries.append(entry) items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): + if representations.isEmpty { + continue + } if image.0 == reference { entries.insert(entry, at: 0) items.insert(.image(reference, representations, videoRepresentations, immediateThumbnailData), at: 0) @@ -916,6 +934,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { entries.append(entry) items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): + if representations.isEmpty { + continue + } if image.0 != reference { entries.append(entry) items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData)) @@ -1014,7 +1035,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { items.append(.custom(customNode)) } for entry in entries { - items.append(PeerInfoAvatarListItem(entry: entry)) + if let item = PeerInfoAvatarListItem(entry: entry) { + items.append(item) + } } strongSelf.galleryEntries = entries strongSelf.items = items @@ -1117,6 +1140,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { if self.currentIndex >= 0 && self.currentIndex < self.items.count { let preloadSpan: Int = 2 for i in max(0, self.currentIndex - preloadSpan) ... min(self.currentIndex + preloadSpan, self.items.count - 1) { + if self.items[i].representations.isEmpty { + continue + } validIds.append(self.items[i].id) var itemNode: PeerInfoAvatarListItemNode? var wasAdded = false diff --git a/submodules/PeerInfoUI/Sources/ChannelInfoController.swift b/submodules/PeerInfoUI/Sources/ChannelInfoController.swift index 85b62dcaf9..ce61260895 100644 --- a/submodules/PeerInfoUI/Sources/ChannelInfoController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelInfoController.swift @@ -738,7 +738,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi updateState { $0.withUpdatedUpdatingAvatar(.image(representation, true)) } - updateAvatarDisposable.set((updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + updateAvatarDisposable.set((context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) }) |> deliverOnMainQueue).start(next: { result in @@ -777,7 +777,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi return $0.withUpdatedUpdatingAvatar(ItemListAvatarAndNameInfoItemUpdatingAvatar.none) } } - updateAvatarDisposable.set((updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in + updateAvatarDisposable.set((context.engine.peers.updatePeerPhoto(peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) }) |> deliverOnMainQueue).start(next: { result in switch result { @@ -993,7 +993,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi let updateTitle: Signal if let titleValue = updateValues.title { - updateTitle = updatePeerTitle(account: context.account, peerId: peerId, title: titleValue) + updateTitle = context.engine.peers.updatePeerTitle(peerId: peerId, title: titleValue) |> mapError { _ in return Void() } } else { updateTitle = .complete() @@ -1001,7 +1001,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi let updateDescription: Signal if let descriptionValue = updateValues.description { - updateDescription = updatePeerDescription(account: context.account, peerId: peerId, description: descriptionValue.isEmpty ? nil : descriptionValue) + updateDescription = context.engine.peers.updatePeerDescription(peerId: peerId, description: descriptionValue.isEmpty ? nil : descriptionValue) |> mapError { _ in return Void() } } else { updateDescription = .complete() diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index d00f4c957e..d73f4ef204 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -1027,7 +1027,7 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId, } } if revoke { - revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: link) |> deliverOnMainQueue).start(completed: { + revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: link) |> deliverOnMainQueue).start(completed: { updateState { $0.withUpdatedRevokingPrivateLink(false) } diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index f8b020c907..0230cca372 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -170,7 +170,7 @@ private final class TimeBasedCleanupImpl { let generalPaths = self.generalPaths let shortLivedPaths = self.shortLivedPaths let scanOnce = Signal { subscriber in - DispatchQueue.global(qos: .utility).async { + DispatchQueue.global(qos: .background).async { var removedShortLivedCount: Int = 0 var removedGeneralCount: Int = 0 var removedGeneralLimitCount: Int = 0 diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index 8dc1fed2ed..6f569eac70 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -148,12 +148,18 @@ public func combineLatest(queue: Que }, initialValues: [:], queue: queue) } -public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), E> { +public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), E> { return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11)], combine: { values in return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11) }, initialValues: [:], queue: queue) } +public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/SemanticStatusNode/BUILD b/submodules/SemanticStatusNode/BUILD index c8372f6f20..43a601eeee 100644 --- a/submodules/SemanticStatusNode/BUILD +++ b/submodules/SemanticStatusNode/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/GZip:GZip", "//submodules/rlottie:RLottieBinding", "//submodules/AppBundle:AppBundle", + "//submodules/ManagedAnimationNode:ManagedAnimationNode" ], visibility = [ "//visibility:public", diff --git a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift index f147c3365a..dcf38e6d72 100644 --- a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift +++ b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import RLottieBinding import GZip import AppBundle +import ManagedAnimationNode public enum SemanticStatusNodeState: Equatable { public struct ProgressAppearance: Equatable { @@ -41,6 +42,7 @@ private protocol SemanticStatusNodeStateDrawingState: NSObjectProtocol { private protocol SemanticStatusNodeStateContext: class { var isAnimating: Bool { get } + var requestUpdate: () -> Void { get set } func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState } @@ -90,10 +92,12 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex final class DrawingState: NSObject, SemanticStatusNodeStateDrawingState { let transitionFraction: CGFloat let icon: SemanticStatusNodeIcon + let iconImage: UIImage? - init(transitionFraction: CGFloat, icon: SemanticStatusNodeIcon) { + init(transitionFraction: CGFloat, icon: SemanticStatusNodeIcon, iconImage: UIImage?) { self.transitionFraction = transitionFraction self.icon = icon + self.iconImage = iconImage super.init() } @@ -119,38 +123,65 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex break case .play: let diameter = size.width - let factor = diameter / 50.0 - - let size = CGSize(width: 15.0, height: 18.0) - context.translateBy(x: (diameter - size.width) / 2.0 + 1.5, y: (diameter - size.height) / 2.0) + + + let size: CGSize + var offset: CGFloat = 0.0 + if let iconImage = self.iconImage { + size = iconImage.size + } else { + offset = 1.5 + size = CGSize(width: 15.0, height: 18.0) + } + context.translateBy(x: (diameter - size.width) / 2.0 + offset, y: (diameter - size.height) / 2.0) if (diameter < 40.0) { context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.scaleBy(x: factor, y: factor) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) } - let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ") - context.fillPath() + if let iconImage = self.iconImage { + context.saveGState() + let iconRect = CGRect(origin: CGPoint(), size: iconImage.size) + context.clip(to: iconRect, mask: iconImage.cgImage!) + context.fill(iconRect) + context.restoreGState() + } else { + let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ") + context.fillPath() + } if (diameter < 40.0) { context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) } - context.translateBy(x: -(diameter - size.width) / 2.0 - 1.5, y: -(diameter - size.height) / 2.0) + context.translateBy(x: -(diameter - size.width) / 2.0 - offset, y: -(diameter - size.height) / 2.0) case .pause: let diameter = size.width - let factor = diameter / 50.0 - let size = CGSize(width: 15.0, height: 16.0) + let size: CGSize + if let iconImage = self.iconImage { + size = iconImage.size + } else { + size = CGSize(width: 15.0, height: 16.0) + } context.translateBy(x: (diameter - size.width) / 2.0, y: (diameter - size.height) / 2.0) if (diameter < 40.0) { context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.scaleBy(x: factor, y: factor) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) } - let _ = try? drawSvgPath(context, path: "M0,1.00087166 C0,0.448105505 0.443716645,0 0.999807492,0 L4.00019251,0 C4.55237094,0 5,0.444630861 5,1.00087166 L5,14.9991283 C5,15.5518945 4.55628335,16 4.00019251,16 L0.999807492,16 C0.447629061,16 0,15.5553691 0,14.9991283 L0,1.00087166 Z M10,1.00087166 C10,0.448105505 10.4437166,0 10.9998075,0 L14.0001925,0 C14.5523709,0 15,0.444630861 15,1.00087166 L15,14.9991283 C15,15.5518945 14.5562834,16 14.0001925,16 L10.9998075,16 C10.4476291,16 10,15.5553691 10,14.9991283 L10,1.00087166 ") - context.fillPath() + if let iconImage = self.iconImage { + context.saveGState() + let iconRect = CGRect(origin: CGPoint(), size: iconImage.size) + context.clip(to: iconRect, mask: iconImage.cgImage!) + context.fill(iconRect) + context.restoreGState() + } else { + let _ = try? drawSvgPath(context, path: "M0,1.00087166 C0,0.448105505 0.443716645,0 0.999807492,0 L4.00019251,0 C4.55237094,0 5,0.444630861 5,1.00087166 L5,14.9991283 C5,15.5518945 4.55628335,16 4.00019251,16 L0.999807492,16 C0.447629061,16 0,15.5553691 0,14.9991283 L0,1.00087166 Z M10,1.00087166 C10,0.448105505 10.4437166,0 10.9998075,0 L14.0001925,0 C14.5523709,0 15,0.444630861 15,1.00087166 L15,14.9991283 C15,15.5518945 14.5562834,16 14.0001925,16 L10.9998075,16 C10.4476291,16 10,15.5553691 10,14.9991283 L10,1.00087166 ") + context.fillPath() + } if (diameter < 40.0) { context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8) @@ -159,7 +190,6 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex context.translateBy(x: -(diameter - size.width) / 2.0, y: -(diameter - size.height) / 2.0) case let .custom(image): let diameter = size.width - let imageRect = CGRect(origin: CGPoint(x: floor((diameter - image.size.width) / 2.0), y: floor((diameter - image.size.height) / 2.0)), size: image.size) context.saveGState() @@ -210,18 +240,36 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex } } - let icon: SemanticStatusNodeIcon + var icon: SemanticStatusNodeIcon { + didSet { + self.animationNode?.enqueueState(self.icon == .play ? .play : .pause, animated: self.iconImage != nil) + } + } + + var animationNode: PlayPauseIconNode? + var iconImage: UIImage? init(icon: SemanticStatusNodeIcon) { self.icon = icon + + if [.play, .pause].contains(icon) { + self.animationNode = PlayPauseIconNode() + self.animationNode?.imageUpdated = { [weak self] image in + self?.iconImage = image + self?.requestUpdate() + } + self.iconImage = self.animationNode?.image + } } var isAnimating: Bool { return false } + var requestUpdate: () -> Void = {} + func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState { - return DrawingState(transitionFraction: transitionFraction, icon: self.icon) + return DrawingState(transitionFraction: transitionFraction, icon: self.icon, iconImage: self.iconImage) } } @@ -376,6 +424,8 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo return true } + var requestUpdate: () -> Void = {} + init(value: CGFloat?, displayCancel: Bool, appearance: SemanticStatusNodeState.ProgressAppearance?) { self.value = value self.displayCancel = displayCancel @@ -402,6 +452,10 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, displayCancel: self.displayCancel, appearance: self.appearance, timestamp: timestamp) } + func maskView() -> UIView? { + return nil + } + func updateValue(value: CGFloat?) { if value != self.value { let previousValue = self.value @@ -501,6 +555,8 @@ private final class SemanticStatusNodeCheckContext: SemanticStatusNodeStateConte return true } + var requestUpdate: () -> Void = {} + init(value: CGFloat, appearance: SemanticStatusNodeState.CheckAppearance?) { self.value = value self.appearance = appearance @@ -524,6 +580,10 @@ private final class SemanticStatusNodeCheckContext: SemanticStatusNodeStateConte return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, appearance: self.appearance) } + func maskView() -> UIView? { + return nil + } + func animate() { guard self.value < 1.0 else { return @@ -553,8 +613,15 @@ private extension SemanticStatusNodeState { default: preconditionFailure() } - if let current = current as? SemanticStatusNodeIconContext, current.icon == icon { - return current + if let current = current as? SemanticStatusNodeIconContext { + if current.icon == icon { + return current + } else if (current.icon == .play && icon == .pause) || (current.icon == .pause && icon == .play) { + current.icon = icon + return current + } else { + return SemanticStatusNodeIconContext(icon: icon) + } } else { return SemanticStatusNodeIconContext(icon: icon) } @@ -874,6 +941,9 @@ public final class SemanticStatusNode: ASControlNode { self.state = state let previousStateContext = self.stateContext self.stateContext = self.state.context(current: self.stateContext) + self.stateContext.requestUpdate = { [weak self] in + self?.setNeedsDisplay() + } if animated && previousStateContext !== self.stateContext { self.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: previousStateContext, previousAppearanceContext: nil, completion: completion) @@ -947,3 +1017,53 @@ public final class SemanticStatusNode: ASControlNode { parameters.appearanceState.drawForeground(context: context, size: bounds.size) } } + +private enum PlayPauseIconNodeState: Equatable { + case play + case pause +} + +private final class PlayPauseIconNode: ManagedAnimationNode { + private let duration: Double = 0.35 + private var iconState: PlayPauseIconNodeState = .play + + init() { + super.init(size: CGSize(width: 36.0, height: 36.0)) + + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01)) + } + + func enqueueState(_ state: PlayPauseIconNodeState, animated: Bool) { + guard self.iconState != state else { + return + } + + let previousState = self.iconState + self.iconState = state + + switch previousState { + case .pause: + switch state { + case .play: + if animated { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 83), duration: self.duration)) + } else { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01)) + } + case .pause: + break + } + case .play: + switch state { + case .pause: + if animated { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 41), duration: self.duration)) + } else { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 41), duration: 0.01)) + } + case .play: + break + } + } + } +} diff --git a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift index 145568ebd9..2b63878ba5 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageController.swift @@ -394,7 +394,7 @@ func cacheUsageStats(context: AccountContext) -> Signal then(collectCacheUsageStats(account: context.account, additionalCachePaths: additionalPaths, logFilesPath: context.sharedContext.applicationBindings.containerPath + "/telegram-data/logs") + |> then(context.engine.resources.collectCacheUsageStats(additionalCachePaths: additionalPaths, logFilesPath: context.sharedContext.applicationBindings.containerPath + "/telegram-data/logs") |> map(Optional.init)) } @@ -581,7 +581,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P var updatedTempPaths = stats.tempPaths var updatedTempSize = stats.tempSize - var signal: Signal = clearCachedMediaResources(account: context.account, mediaResourceIds: clearResourceIds) + var signal: Signal = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds) if otherSize.0 { let removeTempFiles: Signal = Signal { subscriber in let fileManager = FileManager.default @@ -784,7 +784,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P } } - var signal = clearCachedMediaResources(account: context.account, mediaResourceIds: clearResourceIds) + var signal = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds) let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: stats.otherSize, otherPaths: stats.otherPaths, cacheSize: stats.cacheSize, tempPaths: stats.tempPaths, tempSize: stats.tempSize, immutableSize: stats.immutableSize) @@ -911,7 +911,7 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P } } - var signal = clearCachedMediaResources(account: context.account, mediaResourceIds: clearResourceIds) + var signal = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds) let resultStats = CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: stats.otherSize, otherPaths: stats.otherPaths, cacheSize: stats.cacheSize, tempPaths: stats.tempPaths, tempSize: stats.tempSize, immutableSize: stats.immutableSize) diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSounds.swift b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSounds.swift index b34263b70d..545d26e11e 100644 --- a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSounds.swift +++ b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSounds.swift @@ -1008,7 +1008,7 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) - let exceptionsSignal = Signal.single(exceptionsList) |> then(notificationExceptionsList(postbox: context.account.postbox, network: context.account.network) |> map(Optional.init)) + let exceptionsSignal = Signal.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init)) notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in var users:[PeerId : NotificationExceptionWrapper] = [:] diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 0c0d2fca8e..db77edcf7c 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -211,7 +211,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { var items: [ChatListItem] = [] - let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in + let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in gesture?.cancel() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 6b6d5370f0..70f69e86b2 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -779,7 +779,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { var items: [ChatListItem] = [] - let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in + let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in gesture?.cancel() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift index 1f7ef63018..0862763d18 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift @@ -520,7 +520,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { let geoPoint = collection.geoPoint.flatMap { geoPoint -> (Double, Double) in return (geoPoint.latitude, geoPoint.longitude) } - return requestChatContextResults(account: self.context.account, botId: collection.botId, peerId: collection.peerId, query: searchContext.result.query, location: .single(geoPoint), offset: nextOffset) + return self.context.engine.messages.requestChatContextResults(botId: collection.botId, peerId: collection.peerId, query: searchContext.result.query, location: .single(geoPoint), offset: nextOffset) |> map { results -> ChatContextResultCollection? in return results?.results } @@ -572,7 +572,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { return (.complete() |> delay(0.1, queue: Queue.concurrentDefaultQueue())) |> then( - requestContextResults(account: context.account, botId: user.id, query: wallpaperQuery, peerId: context.account.peerId, limit: 16) + requestContextResults(context: context, botId: user.id, query: wallpaperQuery, peerId: context.account.peerId, limit: 16) |> map { results -> ChatContextResultCollection? in return results?.results } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index c52a734653..13bb1e79eb 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -356,7 +356,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) { var items: [ChatListItem] = [] - let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in + let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in gesture?.cancel() diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index c5b8d24f32..16e3e6bd81 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -77,6 +77,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { public var tempVoicePlaylistEnded: (() -> Void)? public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? + public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem? public var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? @@ -150,6 +151,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { updatedVoiceItem = playlistStateAndType.1.item } + strongSelf.tempVoicePlaylistCurrentItem = updatedVoiceItem strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem) if let playlistStateAndType = playlistStateAndType { strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0) @@ -287,7 +289,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { let disposable = MetaDisposable() callContextCache.impl.syncWith { impl in - let callContext = impl.get(account: context.account, peerId: peerId, call: activeCall) + let callContext = impl.get(account: context.account, engine: context.engine, peerId: peerId, call: activeCall) disposable.set((callContext.context.panelData |> deliverOnMainQueue).start(next: { panelData in callContext.keep() @@ -861,7 +863,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { return transaction.getPeerCachedData(peerId: peerId) } - let _ = (combineLatest(currentAccountPeer, cachedGroupCallDisplayAsAvailablePeers(account: context.account, peerId: peerId), cachedData) + let _ = (combineLatest(currentAccountPeer, context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId), cachedData) |> map { currentAccountPeer, availablePeers, cachedData -> ([FoundPeer], CachedPeerData?) in var result = currentAccountPeer result.append(contentsOf: availablePeers) diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index 42e6e65611..57dca2b206 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -1,4 +1,44 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +load( + "@build_bazel_rules_apple//apple:resources.bzl", + "apple_resource_bundle", + "apple_resource_group", +) +load("//build-system/bazel-utils:plist_fragment.bzl", + "plist_fragment", +) + +filegroup( + name = "TelegramCallsUIMetalResources", + srcs = glob([ + "Resources/**/*.metal", + ]), + visibility = ["//visibility:public"], +) + +plist_fragment( + name = "TelegramCallsUIBundleInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + org.telegram.TelegramCallsUI + CFBundleDevelopmentRegion + en + CFBundleName + TelegramCallsUI + """ +) + +apple_resource_bundle( + name = "TelegramCallsUIBundle", + infoplists = [ + ":TelegramCallsUIBundleInfoPlist", + ], + resources = [ + ":TelegramCallsUIMetalResources", + ], +) swift_library( name = "TelegramCallsUI", @@ -6,6 +46,9 @@ swift_library( srcs = glob([ "Sources/**/*.swift", ]), + data = [ + ":TelegramCallsUIBundle", + ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Display:Display", diff --git a/submodules/TelegramCallsUI/Resources/I420VideoShaders.metal b/submodules/TelegramCallsUI/Resources/I420VideoShaders.metal new file mode 100644 index 0000000000..894d3e520c --- /dev/null +++ b/submodules/TelegramCallsUI/Resources/I420VideoShaders.metal @@ -0,0 +1,49 @@ +#include +using namespace metal; + +typedef struct { + packed_float2 position; + packed_float2 texcoord; +} Vertex; + +typedef struct { + float4 position[[position]]; + float2 texcoord; +} Varyings; + +vertex Varyings i420VertexPassthrough(constant Vertex *verticies[[buffer(0)]], + unsigned int vid[[vertex_id]]) { + Varyings out; + constant Vertex &v = verticies[vid]; + out.position = float4(float2(v.position), 0.0, 1.0); + out.texcoord = v.texcoord; + + return out; +} + +fragment half4 i420FragmentColorConversion( + Varyings in[[stage_in]], + texture2d textureY[[texture(0)]], + texture2d textureU[[texture(1)]], + texture2d textureV[[texture(2)]]) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + float y; + float u; + float v; + float r; + float g; + float b; + // Conversion for YUV to rgb from http://www.fourcc.org/fccyvrgb.php + y = textureY.sample(s, in.texcoord).r; + u = textureU.sample(s, in.texcoord).r; + v = textureV.sample(s, in.texcoord).r; + u = u - 0.5; + v = v - 0.5; + r = y + 1.403 * v; + g = y - 0.344 * u - 0.714 * v; + b = y + 1.770 * u; + + float4 out = float4(r, g, b, 1.0); + + return half4(out); +} \ No newline at end of file diff --git a/submodules/TelegramCallsUI/Resources/NV12VideoShaders.metal b/submodules/TelegramCallsUI/Resources/NV12VideoShaders.metal new file mode 100644 index 0000000000..3f0fca262d --- /dev/null +++ b/submodules/TelegramCallsUI/Resources/NV12VideoShaders.metal @@ -0,0 +1,57 @@ +#include +using namespace metal; + +typedef struct { + packed_float2 position; + packed_float2 texcoord; +} Vertex; + +typedef struct { + float4 position[[position]]; + float2 texcoord; +} Varyings; + +vertex Varyings nv12VertexPassthrough( + constant Vertex *verticies[[buffer(0)]], + unsigned int vid[[vertex_id]] +) { + Varyings out; + constant Vertex &v = verticies[vid]; + out.position = float4(float2(v.position), 0.0, 1.0); + out.texcoord = v.texcoord; + return out; +} + +float4 samplePoint(texture2d textureY, texture2d textureCbCr, sampler s, float2 texcoord) { + float y; + float2 uv; + y = textureY.sample(s, texcoord).r; + uv = textureCbCr.sample(s, texcoord).rg - float2(0.5, 0.5); + + // Conversion for YUV to rgb from http://www.fourcc.org/fccyvrgb.php + float4 out = float4(y + 1.403 * uv.y, y - 0.344 * uv.x - 0.714 * uv.y, y + 1.770 * uv.x, 1.0); + return out; +} + +fragment half4 nv12FragmentColorConversion( + Varyings in[[stage_in]], + texture2d textureY[[texture(0)]], + texture2d textureCbCr[[texture(1)]] +) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + + float4 out = samplePoint(textureY, textureCbCr, s, in.texcoord); + + return half4(out); +} + +fragment half4 blitFragmentColorConversion( + Varyings in[[stage_in]], + texture2d texture[[texture(0)]] +) { + constexpr sampler s(address::clamp_to_edge, filter::linear); + + float4 out = texture.sample(s, in.texcoord); + + return half4(out); +} diff --git a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift index 2afb05e066..3167b142ed 100644 --- a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift +++ b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift @@ -25,16 +25,17 @@ final class GroupVideoNode: ASDisplayNode { let sourceContainerNode: PinchSourceContainerNode private let containerNode: ASDisplayNode private let videoViewContainer: UIView - private let videoView: PresentationCallVideoView + private let videoView: VideoRenderingView private let backdropVideoViewContainer: UIView - private let backdropVideoView: PresentationCallVideoView? + private let backdropVideoView: VideoRenderingView? private var backdropEffectView: UIVisualEffectView? private var effectView: UIVisualEffectView? private var isBlurred: Bool = false private var isEnabled: Bool = false + private var isBlurEnabled: Bool = false private var validLayout: (CGSize, LayoutMode)? @@ -47,7 +48,7 @@ final class GroupVideoNode: ASDisplayNode { public var isMainstageExclusive = false - init(videoView: PresentationCallVideoView, backdropVideoView: PresentationCallVideoView?) { + init(videoView: VideoRenderingView, backdropVideoView: VideoRenderingView?) { self.sourceContainerNode = PinchSourceContainerNode() self.containerNode = ASDisplayNode() self.videoViewContainer = UIView() @@ -61,7 +62,7 @@ final class GroupVideoNode: ASDisplayNode { super.init() if let backdropVideoView = backdropVideoView { - self.backdropVideoViewContainer.addSubview(backdropVideoView.view) + self.backdropVideoViewContainer.addSubview(backdropVideoView) self.view.addSubview(self.backdropVideoViewContainer) let effect: UIVisualEffect @@ -70,12 +71,12 @@ final class GroupVideoNode: ASDisplayNode { } else { effect = UIBlurEffect(style: .dark) } - let backdropEffectView = UIVisualEffectView(effect: effect) - self.view.addSubview(backdropEffectView) - self.backdropEffectView = backdropEffectView + //let backdropEffectView = UIVisualEffectView(effect: effect) + //self.view.addSubview(backdropEffectView) + //self.backdropEffectView = backdropEffectView } - self.videoViewContainer.addSubview(self.videoView.view) + self.videoViewContainer.addSubview(self.videoView) self.addSubnode(self.sourceContainerNode) self.containerNode.view.addSubview(self.videoViewContainer) self.sourceContainerNode.contentNode.addSubnode(self.containerNode) @@ -112,7 +113,7 @@ final class GroupVideoNode: ASDisplayNode { self.isEnabled = isEnabled self.videoView.updateIsEnabled(isEnabled) - self.backdropVideoView?.updateIsEnabled(isEnabled) + self.backdropVideoView?.updateIsEnabled(isEnabled && self.isBlurEnabled) } func updateIsBlurred(isBlurred: Bool, light: Bool = false, animated: Bool = true) { @@ -150,11 +151,11 @@ final class GroupVideoNode: ASDisplayNode { self.backgroundColor = .black } var snapshotView: UIView? - if let snapshot = self.videoView.view.snapshotView(afterScreenUpdates: false) { + if let snapshot = self.videoView.snapshotView(afterScreenUpdates: false) { snapshotView = snapshot - snapshot.transform = self.videoView.view.transform - snapshot.frame = self.videoView.view.frame - self.videoView.view.superview?.insertSubview(snapshot, aboveSubview: self.videoView.view) + snapshot.transform = self.videoView.transform + snapshot.frame = self.videoView.frame + self.videoView.superview?.insertSubview(snapshot, aboveSubview: self.videoView) } UIView.transition(with: withBackground ? self.videoViewContainer : self.view, duration: 0.4, options: [.transitionFlipFromLeft, .curveEaseOut], animations: { UIView.performWithoutAnimation { @@ -282,17 +283,17 @@ final class GroupVideoNode: ASDisplayNode { rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width) rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height) - self.videoView.view.alpha = 0.995 + self.videoView.alpha = 0.995 let normalizedVideoSize = rotatedVideoFrame.size.aspectFilled(CGSize(width: 1080.0, height: 1080.0)) - transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center) - transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: normalizedVideoSize)) + transition.updatePosition(layer: self.videoView.layer, position: rotatedVideoFrame.center) + transition.updateBounds(layer: self.videoView.layer, bounds: CGRect(origin: CGPoint(), size: normalizedVideoSize)) let transformScale: CGFloat = rotatedVideoFrame.width / normalizedVideoSize.width transition.updateTransformScale(layer: self.videoViewContainer.layer, scale: transformScale) if let backdropVideoView = self.backdropVideoView { - backdropVideoView.view.alpha = 0.995 + backdropVideoView.alpha = 0.995 let topFrame = rotatedVideoFrame @@ -303,32 +304,34 @@ final class GroupVideoNode: ASDisplayNode { rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width) rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height) - let isBlurEnabled = !topFrame.contains(rotatedVideoFrame) + self.isBlurEnabled = !topFrame.contains(rotatedVideoFrame) let normalizedVideoSize = rotatedVideoFrame.size.aspectFilled(CGSize(width: 1080.0, height: 1080.0)) - if isBlurEnabled { - self.backdropVideoView?.updateIsEnabled(self.isEnabled) - self.backdropVideoView?.view.isHidden = false + + self.backdropVideoView?.updateIsEnabled(self.isEnabled && self.isBlurEnabled) + + if self.isBlurEnabled { + self.backdropVideoView?.isHidden = false self.backdropEffectView?.isHidden = false } - transition.updatePosition(layer: backdropVideoView.view.layer, position: rotatedVideoFrame.center, force: true, completion: { [weak self] value in + transition.updatePosition(layer: backdropVideoView.layer, position: rotatedVideoFrame.center, force: true, completion: { [weak self] value in guard let strongSelf = self, value else { return } - if !isBlurEnabled { + if !strongSelf.isBlurEnabled { strongSelf.backdropVideoView?.updateIsEnabled(false) - strongSelf.backdropVideoView?.view.isHidden = true + strongSelf.backdropVideoView?.isHidden = true strongSelf.backdropEffectView?.isHidden = false } }) - transition.updateBounds(layer: backdropVideoView.view.layer, bounds: CGRect(origin: CGPoint(), size: normalizedVideoSize)) + transition.updateBounds(layer: backdropVideoView.layer, bounds: CGRect(origin: CGPoint(), size: normalizedVideoSize)) let transformScale: CGFloat = rotatedVideoFrame.width / normalizedVideoSize.width transition.updateTransformScale(layer: self.backdropVideoViewContainer.layer, scale: transformScale) let transition: ContainedViewLayoutTransition = .immediate - transition.updateTransformRotation(view: backdropVideoView.view, angle: angle) + transition.updateTransformRotation(view: backdropVideoView, angle: angle) } if let backdropEffectView = self.backdropEffectView { @@ -359,7 +362,7 @@ final class GroupVideoNode: ASDisplayNode { } let transition: ContainedViewLayoutTransition = .immediate - transition.updateTransformRotation(view: self.videoView.view, angle: angle) + transition.updateTransformRotation(view: self.videoView, angle: angle) } var snapshotView: UIView? diff --git a/submodules/TelegramCallsUI/Sources/MetalVideoRenderingView.swift b/submodules/TelegramCallsUI/Sources/MetalVideoRenderingView.swift new file mode 100644 index 0000000000..54545769af --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/MetalVideoRenderingView.swift @@ -0,0 +1,657 @@ +#if targetEnvironment(simulator) +#else + +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import AccountContext +import TelegramVoip +import AVFoundation +import Metal +import MetalPerformanceShaders + +private func alignUp(size: Int, align: Int) -> Int { + precondition(((align - 1) & align) == 0, "Align must be a power of two") + + let alignmentMask = align - 1 + return (size + alignmentMask) & ~alignmentMask +} + +private func getCubeVertexData( + cropX: Int, + cropY: Int, + cropWidth: Int, + cropHeight: Int, + frameWidth: Int, + frameHeight: Int, + rotation: Int, + buffer: UnsafeMutablePointer +) { + let cropLeft = Float(cropX) / Float(frameWidth) + let cropRight = Float(cropX + cropWidth) / Float(frameWidth) + let cropTop = Float(cropY) / Float(frameHeight) + let cropBottom = Float(cropY + cropHeight) / Float(frameHeight) + + switch rotation { + default: + var values: [Float] = [ + -1.0, -1.0, cropLeft, cropBottom, + 1.0, -1.0, cropRight, cropBottom, + -1.0, 1.0, cropLeft, cropTop, + 1.0, 1.0, cropRight, cropTop + ] + memcpy(buffer, &values, values.count * MemoryLayout.size(ofValue: values[0])); + } +} + +@available(iOS 13.0, *) +private protocol FrameBufferRenderingState { + var frameSize: CGSize? { get } + + func encode(renderingContext: MetalVideoRenderingContext, vertexBuffer: MTLBuffer, renderEncoder: MTLRenderCommandEncoder) -> Bool +} + +@available(iOS 13.0, *) +private final class BlitRenderingState { + static func encode(renderingContext: MetalVideoRenderingContext, texture: MTLTexture, vertexBuffer: MTLBuffer, renderEncoder: MTLRenderCommandEncoder) -> Bool { + renderEncoder.setRenderPipelineState(renderingContext.blitPipelineState) + + renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) + + renderEncoder.setFragmentTexture(texture, index: 0) + + renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) + + return true + } +} + +@available(iOS 13.0, *) +private final class NV12FrameBufferRenderingState: FrameBufferRenderingState { + private var yTexture: MTLTexture? + private var uvTexture: MTLTexture? + + var frameSize: CGSize? { + if let yTexture = self.yTexture { + return CGSize(width: yTexture.width, height: yTexture.height) + } else { + return nil + } + } + + func updateTextureBuffers(renderingContext: MetalVideoRenderingContext, frameBuffer: OngoingGroupCallContext.VideoFrameData.NativeBuffer) { + let pixelBuffer = frameBuffer.pixelBuffer + + var lumaTexture: MTLTexture? + var chromaTexture: MTLTexture? + var outTexture: CVMetalTexture? + + let lumaWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0) + let lumaHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0) + + var indexPlane = 0 + var result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, renderingContext.textureCache, pixelBuffer, nil, .r8Unorm, lumaWidth, lumaHeight, indexPlane, &outTexture) + if result == kCVReturnSuccess, let outTexture = outTexture { + lumaTexture = CVMetalTextureGetTexture(outTexture) + } + outTexture = nil + + indexPlane = 1 + result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, renderingContext.textureCache, pixelBuffer, nil, .rg8Unorm, lumaWidth / 2, lumaHeight / 2, indexPlane, &outTexture) + if result == kCVReturnSuccess, let outTexture = outTexture { + chromaTexture = CVMetalTextureGetTexture(outTexture) + } + outTexture = nil + + if let lumaTexture = lumaTexture, let chromaTexture = chromaTexture { + self.yTexture = lumaTexture + self.uvTexture = chromaTexture + } else { + self.yTexture = nil + self.uvTexture = nil + } + } + + func encode(renderingContext: MetalVideoRenderingContext, vertexBuffer: MTLBuffer, renderEncoder: MTLRenderCommandEncoder) -> Bool { + guard let yTexture = self.yTexture, let uvTexture = self.uvTexture else { + return false + } + + renderEncoder.setRenderPipelineState(renderingContext.nv12PipelineState) + + renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) + + renderEncoder.setFragmentTexture(yTexture, index: 0) + renderEncoder.setFragmentTexture(uvTexture, index: 1) + + renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) + + return true + } +} + +@available(iOS 13.0, *) +private final class I420FrameBufferRenderingState: FrameBufferRenderingState { + private var yTexture: MTLTexture? + private var uTexture: MTLTexture? + private var vTexture: MTLTexture? + + private var lumaTextureDescriptorSize: CGSize? + private var lumaTextureDescriptor: MTLTextureDescriptor? + private var chromaTextureDescriptor: MTLTextureDescriptor? + + var frameSize: CGSize? { + if let yTexture = self.yTexture { + return CGSize(width: yTexture.width, height: yTexture.height) + } else { + return nil + } + } + + func updateTextureBuffers(renderingContext: MetalVideoRenderingContext, frameBuffer: OngoingGroupCallContext.VideoFrameData.I420Buffer) { + let lumaSize = CGSize(width: frameBuffer.width, height: frameBuffer.height) + + if lumaSize != lumaTextureDescriptorSize || lumaTextureDescriptor == nil || chromaTextureDescriptor == nil { + self.lumaTextureDescriptorSize = lumaSize + + let lumaTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .r8Unorm, width: frameBuffer.width, height: frameBuffer.height, mipmapped: false) + lumaTextureDescriptor.usage = .shaderRead + self.lumaTextureDescriptor = lumaTextureDescriptor + + self.yTexture = renderingContext.device.makeTexture(descriptor: lumaTextureDescriptor) + + let chromaTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .r8Unorm, width: frameBuffer.width / 2, height: frameBuffer.height / 2, mipmapped: false) + chromaTextureDescriptor.usage = .shaderRead + self.chromaTextureDescriptor = chromaTextureDescriptor + + self.uTexture = renderingContext.device.makeTexture(descriptor: chromaTextureDescriptor) + self.vTexture = renderingContext.device.makeTexture(descriptor: chromaTextureDescriptor) + } + + guard let yTexture = self.yTexture, let uTexture = self.uTexture, let vTexture = self.vTexture else { + return + } + + frameBuffer.y.withUnsafeBytes { bufferPointer in + if let baseAddress = bufferPointer.baseAddress { + yTexture.replace(region: MTLRegionMake2D(0, 0, yTexture.width, yTexture.height), mipmapLevel: 0, withBytes: baseAddress, bytesPerRow: frameBuffer.strideY) + } + } + + frameBuffer.u.withUnsafeBytes { bufferPointer in + if let baseAddress = bufferPointer.baseAddress { + uTexture.replace(region: MTLRegionMake2D(0, 0, uTexture.width, uTexture.height), mipmapLevel: 0, withBytes: baseAddress, bytesPerRow: frameBuffer.strideU) + } + } + + frameBuffer.v.withUnsafeBytes { bufferPointer in + if let baseAddress = bufferPointer.baseAddress { + vTexture.replace(region: MTLRegionMake2D(0, 0, vTexture.width, vTexture.height), mipmapLevel: 0, withBytes: baseAddress, bytesPerRow: frameBuffer.strideV) + } + } + } + + func encode(renderingContext: MetalVideoRenderingContext, vertexBuffer: MTLBuffer, renderEncoder: MTLRenderCommandEncoder) -> Bool { + guard let yTexture = self.yTexture, let uTexture = self.uTexture, let vTexture = self.vTexture else { + return false + } + + renderEncoder.setRenderPipelineState(renderingContext.i420PipelineState) + + renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) + + renderEncoder.setFragmentTexture(yTexture, index: 0) + renderEncoder.setFragmentTexture(uTexture, index: 1) + renderEncoder.setFragmentTexture(vTexture, index: 2) + + renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) + + return true + } +} + +@available(iOS 13.0, *) +final class MetalVideoRenderingView: UIView, VideoRenderingView { + static override var layerClass: AnyClass { + return CAMetalLayer.self + } + + private var metalLayer: CAMetalLayer { + return self.layer as! CAMetalLayer + } + + private weak var renderingContext: MetalVideoRenderingContext? + private var renderingContextIndex: Int? + + private let blur: Bool + + private let vertexBuffer: MTLBuffer + + private var frameBufferRenderingState: FrameBufferRenderingState? + private var blurInputTexture: MTLTexture? + private var blurOutputTexture: MTLTexture? + + fileprivate private(set) var isEnabled: Bool = false + fileprivate var needsRedraw: Bool = false + fileprivate let numberOfUsedDrawables = Atomic(value: 0) + + private var onFirstFrameReceived: ((Float) -> Void)? + private var onOrientationUpdated: ((PresentationCallVideoView.Orientation, CGFloat) -> Void)? + private var onIsMirroredUpdated: ((Bool) -> Void)? + + private var didReportFirstFrame: Bool = false + private var currentOrientation: PresentationCallVideoView.Orientation = .rotation0 + private var currentAspect: CGFloat = 1.0 + + private var disposable: Disposable? + + init?(renderingContext: MetalVideoRenderingContext, input: Signal, blur: Bool) { + self.renderingContext = renderingContext + self.blur = blur + + let vertexBufferArray = Array(repeating: 0, count: 16) + guard let vertexBuffer = renderingContext.device.makeBuffer(bytes: vertexBufferArray, length: vertexBufferArray.count * MemoryLayout.size(ofValue: vertexBufferArray[0]), options: [.cpuCacheModeWriteCombined]) else { + return nil + } + self.vertexBuffer = vertexBuffer + + super.init(frame: CGRect()) + + self.renderingContextIndex = renderingContext.add(view: self) + + self.metalLayer.device = renderingContext.device + self.metalLayer.pixelFormat = .bgra8Unorm + self.metalLayer.framebufferOnly = true + self.metalLayer.allowsNextDrawableTimeout = true + + self.disposable = input.start(next: { [weak self] videoFrameData in + Queue.mainQueue().async { + self?.addFrame(videoFrameData) + } + }) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposable?.dispose() + if let renderingContext = self.renderingContext, let renderingContextIndex = self.renderingContextIndex { + renderingContext.remove(index: renderingContextIndex) + } + } + + private func addFrame(_ videoFrameData: OngoingGroupCallContext.VideoFrameData) { + let aspect = CGFloat(videoFrameData.width) / CGFloat(videoFrameData.height) + var isAspectUpdated = false + if self.currentAspect != aspect { + self.currentAspect = aspect + isAspectUpdated = true + } + + let videoFrameOrientation = PresentationCallVideoView.Orientation(videoFrameData.orientation) + var isOrientationUpdated = false + if self.currentOrientation != videoFrameOrientation { + self.currentOrientation = videoFrameOrientation + isOrientationUpdated = true + } + + if isAspectUpdated || isOrientationUpdated { + self.onOrientationUpdated?(self.currentOrientation, self.currentAspect) + } + + if !self.didReportFirstFrame { + self.didReportFirstFrame = true + self.onFirstFrameReceived?(Float(self.currentAspect)) + } + + if self.isEnabled, let renderingContext = self.renderingContext { + switch videoFrameData.buffer { + case let .native(buffer): + let renderingState: NV12FrameBufferRenderingState + if let current = self.frameBufferRenderingState as? NV12FrameBufferRenderingState { + renderingState = current + } else { + renderingState = NV12FrameBufferRenderingState() + self.frameBufferRenderingState = renderingState + } + renderingState.updateTextureBuffers(renderingContext: renderingContext, frameBuffer: buffer) + self.needsRedraw = true + case let .i420(buffer): + let renderingState: I420FrameBufferRenderingState + if let current = self.frameBufferRenderingState as? I420FrameBufferRenderingState { + renderingState = current + } else { + renderingState = I420FrameBufferRenderingState() + self.frameBufferRenderingState = renderingState + } + renderingState.updateTextureBuffers(renderingContext: renderingContext, frameBuffer: buffer) + self.needsRedraw = true + default: + break + } + } + } + + fileprivate func encode(commandBuffer: MTLCommandBuffer) -> MTLDrawable? { + guard let renderingContext = self.renderingContext else { + return nil + } + if self.numberOfUsedDrawables.with({ $0 }) >= 2 { + return nil + } + guard let frameBufferRenderingState = self.frameBufferRenderingState else { + return nil + } + + guard let frameSize = frameBufferRenderingState.frameSize else { + return nil + } + + let drawableSize: CGSize + if self.blur { + drawableSize = frameSize.aspectFitted(CGSize(width: 64.0, height: 64.0)) + } else { + drawableSize = frameSize + } + + if self.blur { + if let current = self.blurInputTexture, current.width == Int(drawableSize.width) && current.height == Int(drawableSize.height) { + } else { + let blurTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm, width: Int(drawableSize.width), height: Int(drawableSize.height), mipmapped: false) + blurTextureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget] + + if let texture = renderingContext.device.makeTexture(descriptor: blurTextureDescriptor) { + self.blurInputTexture = texture + } + } + + if let current = self.blurOutputTexture, current.width == Int(drawableSize.width) && current.height == Int(drawableSize.height) { + } else { + let blurTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm, width: Int(drawableSize.width), height: Int(drawableSize.height), mipmapped: false) + blurTextureDescriptor.usage = [.shaderRead, .shaderWrite] + + if let texture = renderingContext.device.makeTexture(descriptor: blurTextureDescriptor) { + self.blurOutputTexture = texture + } + } + } + + if self.metalLayer.drawableSize != drawableSize { + self.metalLayer.drawableSize = drawableSize + + getCubeVertexData( + cropX: 0, + cropY: 0, + cropWidth: Int(drawableSize.width), + cropHeight: Int(drawableSize.height), + frameWidth: Int(drawableSize.width), + frameHeight: Int(drawableSize.height), + rotation: 0, + buffer: self.vertexBuffer.contents().assumingMemoryBound(to: Float.self) + ) + } + + + guard let drawable = self.metalLayer.nextDrawable() else { + return nil + } + + if let blurInputTexture = self.blurInputTexture, let blurOutputTexture = self.blurOutputTexture { + let renderPassDescriptor = MTLRenderPassDescriptor() + renderPassDescriptor.colorAttachments[0].texture = blurInputTexture + renderPassDescriptor.colorAttachments[0].loadAction = .clear + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor( + red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 1.0 + ) + + guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { + return nil + } + + let _ = frameBufferRenderingState.encode(renderingContext: renderingContext, vertexBuffer: self.vertexBuffer, renderEncoder: renderEncoder) + + renderEncoder.endEncoding() + + renderingContext.blurKernel.encode(commandBuffer: commandBuffer, sourceTexture: blurInputTexture, destinationTexture: blurOutputTexture) + + let blitPassDescriptor = MTLRenderPassDescriptor() + blitPassDescriptor.colorAttachments[0].texture = drawable.texture + blitPassDescriptor.colorAttachments[0].loadAction = .clear + blitPassDescriptor.colorAttachments[0].clearColor = MTLClearColor( + red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 1.0 + ) + + guard let blitEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: blitPassDescriptor) else { + return nil + } + + let _ = BlitRenderingState.encode(renderingContext: renderingContext, texture: blurOutputTexture, vertexBuffer: self.vertexBuffer, renderEncoder: blitEncoder) + + blitEncoder.endEncoding() + } else { + let renderPassDescriptor = MTLRenderPassDescriptor() + renderPassDescriptor.colorAttachments[0].texture = drawable.texture + renderPassDescriptor.colorAttachments[0].loadAction = .clear + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor( + red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 1.0 + ) + + guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { + return nil + } + + let _ = frameBufferRenderingState.encode(renderingContext: renderingContext, vertexBuffer: self.vertexBuffer, renderEncoder: renderEncoder) + + renderEncoder.endEncoding() + } + + return drawable + } + + func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) { + self.onFirstFrameReceived = f + self.didReportFirstFrame = false + } + + func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) { + self.onOrientationUpdated = f + } + + func getOrientation() -> PresentationCallVideoView.Orientation { + return self.currentOrientation + } + + func getAspect() -> CGFloat { + return self.currentAspect + } + + func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) { + self.onIsMirroredUpdated = f + } + + func updateIsEnabled(_ isEnabled: Bool) { + if self.isEnabled != isEnabled { + self.isEnabled = isEnabled + + if self.isEnabled { + self.needsRedraw = true + } + } + } +} + +@available(iOS 13.0, *) +class MetalVideoRenderingContext { + private final class ViewReference { + weak var view: MetalVideoRenderingView? + + init(view: MetalVideoRenderingView) { + self.view = view + } + } + + fileprivate let device: MTLDevice + fileprivate let textureCache: CVMetalTextureCache + fileprivate let blurKernel: MPSImageGaussianBlur + + fileprivate let blitPipelineState: MTLRenderPipelineState + fileprivate let nv12PipelineState: MTLRenderPipelineState + fileprivate let i420PipelineState: MTLRenderPipelineState + + private let commandQueue: MTLCommandQueue + + private var displayLink: ConstantDisplayLinkAnimator? + private var viewReferences = Bag() + + init?() { + guard let device = MTLCreateSystemDefaultDevice() else { + return nil + } + self.device = device + + var textureCache: CVMetalTextureCache? + let _ = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, self.device, nil, &textureCache) + if let textureCache = textureCache { + self.textureCache = textureCache + } else { + return nil + } + + let mainBundle = Bundle(for: MetalVideoRenderingView.self) + + guard let path = mainBundle.path(forResource: "TelegramCallsUIBundle", ofType: "bundle") else { + return nil + } + guard let bundle = Bundle(path: path) else { + return nil + } + guard let defaultLibrary = try? self.device.makeDefaultLibrary(bundle: bundle) else { + return nil + } + + self.blurKernel = MPSImageGaussianBlur(device: self.device, sigma: 3.0) + + func makePipelineState(vertexProgram: String, fragmentProgram: String) -> MTLRenderPipelineState? { + guard let loadedVertexProgram = defaultLibrary.makeFunction(name: vertexProgram) else { + return nil + } + guard let loadedFragmentProgram = defaultLibrary.makeFunction(name: fragmentProgram) else { + return nil + } + + let pipelineStateDescriptor = MTLRenderPipelineDescriptor() + pipelineStateDescriptor.vertexFunction = loadedVertexProgram + pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram + pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + guard let pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) else { + return nil + } + + return pipelineState + } + + guard let blitPipelineState = makePipelineState(vertexProgram: "nv12VertexPassthrough", fragmentProgram: "blitFragmentColorConversion") else { + return nil + } + self.blitPipelineState = blitPipelineState + + guard let nv12PipelineState = makePipelineState(vertexProgram: "nv12VertexPassthrough", fragmentProgram: "nv12FragmentColorConversion") else { + return nil + } + self.nv12PipelineState = nv12PipelineState + + guard let i420PipelineState = makePipelineState(vertexProgram: "i420VertexPassthrough", fragmentProgram: "i420FragmentColorConversion") else { + return nil + } + self.i420PipelineState = i420PipelineState + + guard let commandQueue = self.device.makeCommandQueue() else { + return nil + } + self.commandQueue = commandQueue + + self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in + self?.redraw() + }) + self.displayLink?.isPaused = false + } + + func updateVisibility(isVisible: Bool) { + self.displayLink?.isPaused = !isVisible + } + + fileprivate func add(view: MetalVideoRenderingView) -> Int { + return self.viewReferences.add(ViewReference(view: view)) + } + + fileprivate func remove(index: Int) { + self.viewReferences.remove(index) + } + + private func redraw() { + guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { + return + } + + var drawables: [MTLDrawable] = [] + var takenViewReferences: [ViewReference] = [] + + for viewReference in self.viewReferences.copyItems() { + guard let videoView = viewReference.view else { + continue + } + + if !videoView.needsRedraw { + continue + } + videoView.needsRedraw = false + + if let drawable = videoView.encode(commandBuffer: commandBuffer) { + let numberOfUsedDrawables = videoView.numberOfUsedDrawables + let _ = numberOfUsedDrawables.modify { + return $0 + 1 + } + takenViewReferences.append(viewReference) + + drawable.addPresentedHandler { _ in + let _ = numberOfUsedDrawables.modify { + return max(0, $0 - 1) + } + } + + drawables.append(drawable) + } + } + + if drawables.isEmpty { + return + } + + if drawables.count > 10 { + print("Schedule \(drawables.count) drawables") + } + + commandBuffer.addScheduledHandler { _ in + for drawable in drawables { + drawable.present() + } + } + + commandBuffer.commit() + } +} + +#endif diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 53515a37a4..36a3c47ba1 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -92,7 +92,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { return self.panelDataPromise.get() } - public init(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) { + public init(account: Account, engine: TelegramEngine, peerId: PeerId, call: CachedChannelData.ActiveCall) { self.panelDataPromise.set(.single(GroupCallPanelData( peerId: peerId, info: GroupCallInfo( @@ -115,7 +115,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { groupCall: nil ))) - self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: nil) + self.disposable = (engine.calls.getGroupCallParticipants(callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: nil) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -124,8 +124,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { guard let strongSelf = self, let state = state else { return } - let context = GroupCallParticipantsContext( - account: account, + let context = engine.calls.groupCall( peerId: peerId, myPeerId: account.peerId, id: call.id, @@ -185,12 +184,12 @@ public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCach self.queue = queue } - public func get(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) -> AccountGroupCallContextImpl.Proxy { + public func get(account: Account, engine: TelegramEngine, peerId: PeerId, call: CachedChannelData.ActiveCall) -> AccountGroupCallContextImpl.Proxy { let result: Record if let current = self.contexts[call.id] { result = current } else { - let context = AccountGroupCallContextImpl(account: account, peerId: peerId, call: call) + let context = AccountGroupCallContextImpl(account: account, engine: engine, peerId: peerId, call: call) result = Record(context: context) self.contexts[call.id] = result } @@ -216,8 +215,8 @@ public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCach }) } - public func leaveInBackground(account: Account, id: Int64, accessHash: Int64, source: UInt32) { - let disposable = leaveGroupCall(account: account, callId: id, accessHash: accessHash, source: source).start() + public func leaveInBackground(engine: TelegramEngine, id: Int64, accessHash: Int64, source: UInt32) { + let disposable = engine.calls.leaveGroupCall(callId: id, accessHash: accessHash, source: source).start() self.leaveDisposables.add(disposable) } } @@ -823,7 +822,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { }) if let initialCall = initialCall, let temporaryParticipantsContext = (self.accountContext.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl)?.impl.syncWith({ impl in - impl.get(account: accountContext.account, peerId: peerId, call: CachedChannelData.ActiveCall(id: initialCall.id, accessHash: initialCall.accessHash, title: initialCall.title, scheduleTimestamp: initialCall.scheduleTimestamp, subscribedToScheduled: initialCall.subscribedToScheduled)) + impl.get(account: accountContext.account, engine: accountContext.engine, peerId: peerId, call: CachedChannelData.ActiveCall(id: initialCall.id, accessHash: initialCall.accessHash, title: initialCall.title, scheduleTimestamp: initialCall.scheduleTimestamp, subscribedToScheduled: initialCall.subscribedToScheduled)) }) { self.switchToTemporaryParticipantsContext(sourceContext: temporaryParticipantsContext.context.participantsContext, oldMyPeerId: self.joinAsPeerId) } else { @@ -966,11 +965,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } |> beforeNext { view in if let view = view, view.1 == nil { - let _ = fetchAndUpdateCachedPeerData(accountPeerId: accountContext.account.peerId, peerId: myPeerId, network: accountContext.account.network, postbox: accountContext.account.postbox).start() + let _ = accountContext.engine.peers.fetchAndUpdateCachedPeerData(peerId: myPeerId).start() } } if let sourceContext = sourceContext, let initialState = sourceContext.immediateState { - let temporaryParticipantsContext = GroupCallParticipantsContext(account: self.account, peerId: self.peerId, myPeerId: myPeerId, id: sourceContext.id, accessHash: sourceContext.accessHash, state: initialState, previousServiceState: sourceContext.serviceState) + let temporaryParticipantsContext = self.accountContext.engine.calls.groupCall(peerId: self.peerId, myPeerId: myPeerId, id: sourceContext.id, accessHash: sourceContext.accessHash, state: initialState, previousServiceState: sourceContext.serviceState) self.temporaryParticipantsContext = temporaryParticipantsContext self.participantsContextStateDisposable.set((combineLatest(queue: .mainQueue(), myPeer, @@ -1192,8 +1191,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } |> distinctUntilChanged - let participantsContext = GroupCallParticipantsContext( - account: self.accountContext.account, + let participantsContext = self.accountContext.engine.calls.groupCall( peerId: self.peerId, myPeerId: self.joinAsPeerId, id: callInfo.id, @@ -1229,7 +1227,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } |> beforeNext { view in if let view = view, view.1 == nil { - let _ = fetchAndUpdateCachedPeerData(accountPeerId: accountContext.account.peerId, peerId: myPeerId, network: accountContext.account.network, postbox: accountContext.account.postbox).start() + let _ = accountContext.engine.peers.fetchAndUpdateCachedPeerData(peerId: myPeerId).start() } } self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(), @@ -1399,7 +1397,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { disposable.set(strongSelf.requestMediaChannelDescriptions(ssrcs: ssrcs, completion: completion)) } return disposable - }, audioStreamData: OngoingGroupCallContext.AudioStreamData(account: self.accountContext.account, callId: callInfo.id, accessHash: callInfo.accessHash), rejoinNeeded: { [weak self] in + }, audioStreamData: OngoingGroupCallContext.AudioStreamData(engine: self.accountContext.engine, callId: callInfo.id, accessHash: callInfo.accessHash), rejoinNeeded: { [weak self] in Queue.mainQueue().async { guard let strongSelf = self else { return @@ -1464,8 +1462,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } strongSelf.currentLocalSsrc = ssrc - strongSelf.requestDisposable.set((joinGroupCall( - account: strongSelf.account, + strongSelf.requestDisposable.set((strongSelf.accountContext.engine.calls.joinGroupCall( peerId: strongSelf.peerId, joinAs: strongSelf.joinAsPeerId, callId: callInfo.id, @@ -1525,7 +1522,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { ]), on: .root, blockInteraction: false, completion: {}) } else if case .invalidJoinAsPeer = error { let peerId = strongSelf.peerId - let _ = clearCachedGroupCallDisplayAsAvailablePeers(account: strongSelf.accountContext.account, peerId: peerId).start() + let _ = strongSelf.accountContext.engine.calls.clearCachedGroupCallDisplayAsAvailablePeers(peerId: peerId).start() let _ = (strongSelf.accountContext.account.postbox.transaction { transaction -> Void in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in if let current = current as? CachedChannelData { @@ -1749,8 +1746,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { serviceState = participantsContext.serviceState } - let participantsContext = GroupCallParticipantsContext( - account: self.accountContext.account, + let participantsContext = self.accountContext.engine.calls.groupCall( peerId: self.peerId, myPeerId: self.joinAsPeerId, id: callInfo.id, @@ -1770,7 +1766,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } |> beforeNext { view in if let view = view, view.1 == nil { - let _ = fetchAndUpdateCachedPeerData(accountPeerId: accountContext.account.peerId, peerId: myPeerId, network: accountContext.account.network, postbox: accountContext.account.postbox).start() + let _ = accountContext.engine.peers.fetchAndUpdateCachedPeerData(peerId: myPeerId).start() } } @@ -2105,7 +2101,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } if !remainingSsrcs.isEmpty, let callInfo = self.internalState.callInfo { - return (getGroupCallParticipants(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, offset: "", ssrcs: Array(remainingSsrcs), limit: 100, sortAscending: callInfo.sortAscending) + return (self.accountContext.engine.calls.getGroupCallParticipants(callId: callInfo.id, accessHash: callInfo.accessHash, offset: "", ssrcs: Array(remainingSsrcs), limit: 100, sortAscending: callInfo.sortAscending) |> deliverOnMainQueue).start(next: { state in extractMediaChannelDescriptions(remainingSsrcs: &remainingSsrcs, participants: state.participants, into: &result) @@ -2122,7 +2118,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return } if case let .established(callInfo, connectionMode, _, ssrc, _) = self.internalState, case .rtc = connectionMode { - let checkSignal = checkGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, ssrcs: [ssrc]) + let checkSignal = self.accountContext.engine.calls.checkGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash, ssrcs: [ssrc]) self.checkCallDisposable = (( checkSignal @@ -2288,7 +2284,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.leaving = true if let callInfo = self.internalState.callInfo { if terminateIfPossible { - self.leaveDisposable.set((stopGroupCall(account: self.account, peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash) + self.leaveDisposable.set((self.accountContext.engine.calls.stopGroupCall(peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash) |> deliverOnMainQueue).start(completed: { [weak self] in guard let strongSelf = self else { return @@ -2297,12 +2293,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { })) } else if let localSsrc = self.currentLocalSsrc { if let contexts = self.accountContext.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl { - let account = self.account + let engine = self.accountContext.engine let id = callInfo.id let accessHash = callInfo.accessHash let source = localSsrc contexts.impl.with { impl in - impl.leaveInBackground(account: account, id: id, accessHash: accessHash, source: source) + impl.leaveInBackground(engine: engine, id: id, accessHash: accessHash, source: source) } } self.markAsCanBeRemoved() @@ -2365,7 +2361,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.stateValue.subscribedToScheduled = subscribe - self.subscribeDisposable.set((toggleScheduledGroupCallSubscription(account: self.account, peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash, subscribe: subscribe) + self.subscribeDisposable.set((self.accountContext.engine.calls.toggleScheduledGroupCallSubscription(peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash, subscribe: subscribe) |> deliverOnMainQueue).start()) } @@ -2383,7 +2379,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { activeSpeakers: Set() ))) - self.startDisposable.set((createGroupCall(account: self.account, peerId: self.peerId, title: nil, scheduleDate: timestamp) + self.startDisposable.set((self.accountContext.engine.calls.createGroupCall(peerId: self.peerId, title: nil, scheduleDate: timestamp) |> deliverOnMainQueue).start(next: { [weak self] callInfo in guard let strongSelf = self else { return @@ -2405,7 +2401,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.isScheduledStarted = true self.stateValue.scheduleTimestamp = nil - self.startDisposable.set((startScheduledGroupCall(account: self.account, peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash) + self.startDisposable.set((self.accountContext.engine.calls.startScheduledGroupCall(peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash) |> deliverOnMainQueue).start(next: { [weak self] callInfo in guard let strongSelf = self else { return @@ -2595,6 +2591,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { let videoCapturer = OngoingCallVideoCapturer() self.videoCapturer = videoCapturer } + + if let videoCapturer = self.videoCapturer { + self.requestVideo(capturer: videoCapturer) + } + } + + func requestVideo(capturer: OngoingCallVideoCapturer) { + self.videoCapturer = capturer + self.hasVideo = true if let videoCapturer = self.videoCapturer { self.genericCallContext?.requestVideo(videoCapturer) @@ -2652,8 +2657,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return } - strongSelf.requestDisposable.set((joinGroupCallAsScreencast( - account: strongSelf.account, + strongSelf.requestDisposable.set((strongSelf.accountContext.engine.calls.joinGroupCallAsScreencast( peerId: strongSelf.peerId, callId: callInfo.id, accessHash: callInfo.accessHash, @@ -2684,8 +2688,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { let maybeCallInfo: GroupCallInfo? = self.internalState.callInfo if let callInfo = maybeCallInfo { - self.screencastJoinDisposable.set(leaveGroupCallAsScreencast( - account: self.account, + self.screencastJoinDisposable.set(self.accountContext.engine.calls.leaveGroupCallAsScreencast( callId: callInfo.id, accessHash: callInfo.accessHash ).start()) @@ -2693,19 +2696,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.screencastBufferServerContext?.stopScreencast() } - /*if let _ = self.screencastIpcContext { - self.screencastIpcContext = nil - - let maybeCallInfo: GroupCallInfo? = self.internalState.callInfo - - if let callInfo = maybeCallInfo { - self.screencastJoinDisposable.set(leaveGroupCallAsScreencast( - account: self.account, - callId: callInfo.id, - accessHash: callInfo.accessHash - ).start()) - } - }*/ } public func setVolume(peerId: PeerId, volume: Int32, sync: Bool) { @@ -2885,9 +2875,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } let account = self.account + let context = self.accountContext let currentCall: Signal if let initialCall = self.initialCall { - currentCall = getCurrentGroupCall(account: account, callId: initialCall.id, accessHash: initialCall.accessHash) + currentCall = context.engine.calls.getCurrentGroupCall(callId: initialCall.id, accessHash: initialCall.accessHash) |> mapError { _ -> CallError in return .generic } @@ -2895,7 +2886,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return summary?.info } } else if case let .active(callInfo) = self.internalState { - currentCall = getCurrentGroupCall(account: account, callId: callInfo.id, accessHash: callInfo.accessHash) + currentCall = context.engine.calls.getCurrentGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash) |> mapError { _ -> CallError in return .generic } @@ -2952,7 +2943,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { updatedInvitedPeers.insert(peerId, at: 0) self.invitedPeersValue = updatedInvitedPeers - let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start() + let _ = self.accountContext.engine.calls.inviteToGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start() return true } @@ -2968,10 +2959,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return } self.stateValue.title = title.isEmpty ? nil : title - let _ = editGroupCallTitle(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, title: title).start() + let _ = self.accountContext.engine.calls.editGroupCallTitle(callId: callInfo.id, accessHash: callInfo.accessHash, title: title).start() } public var inviteLinks: Signal { + let engine = self.accountContext.engine + return self.state |> map { state -> PeerId in return state.myPeerId @@ -2988,7 +2981,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } |> mapToSignal { state in if let callInfo = state.callInfo { - return groupCallInviteLinks(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash) + return engine.calls.groupCallInviteLinks(callId: callInfo.id, accessHash: callInfo.accessHash) } else { return .complete() } @@ -3189,6 +3182,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } }) } + + func video(endpointId: String) -> Signal? { + return self.genericCallContext?.video(endpointId: endpointId) + } public func loadMoreMembers(token: String) { self.participantsContext?.loadMore(token: token) diff --git a/submodules/TelegramCallsUI/Sources/SampleBufferVideoRenderingView.swift b/submodules/TelegramCallsUI/Sources/SampleBufferVideoRenderingView.swift new file mode 100644 index 0000000000..a6dd976c83 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/SampleBufferVideoRenderingView.swift @@ -0,0 +1,144 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import AccountContext +import TelegramVoip +import AVFoundation + +private func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? { + var maybeFormat: CMVideoFormatDescription? + let status = CMVideoFormatDescriptionCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, formatDescriptionOut: &maybeFormat) + if status != noErr { + return nil + } + guard let format = maybeFormat else { + return nil + } + + var timingInfo = CMSampleTimingInfo( + duration: CMTimeMake(value: 1, timescale: 30), + presentationTimeStamp: CMTimeMake(value: 0, timescale: 30), + decodeTimeStamp: CMTimeMake(value: 0, timescale: 30) + ) + + var maybeSampleBuffer: CMSampleBuffer? + let bufferStatus = CMSampleBufferCreateReadyWithImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, formatDescription: format, sampleTiming: &timingInfo, sampleBufferOut: &maybeSampleBuffer) + + if (bufferStatus != noErr) { + return nil + } + guard let sampleBuffer = maybeSampleBuffer else { + return nil + } + + let attachments: NSArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: true)! as NSArray + let dict: NSMutableDictionary = attachments[0] as! NSMutableDictionary + dict[kCMSampleAttachmentKey_DisplayImmediately as NSString] = true as NSNumber + + return sampleBuffer +} + +final class SampleBufferVideoRenderingView: UIView, VideoRenderingView { + static override var layerClass: AnyClass { + return AVSampleBufferDisplayLayer.self + } + + private var sampleBufferLayer: AVSampleBufferDisplayLayer { + return self.layer as! AVSampleBufferDisplayLayer + } + + private var isEnabled: Bool = false + + private var onFirstFrameReceived: ((Float) -> Void)? + private var onOrientationUpdated: ((PresentationCallVideoView.Orientation, CGFloat) -> Void)? + private var onIsMirroredUpdated: ((Bool) -> Void)? + + private var didReportFirstFrame: Bool = false + private var currentOrientation: PresentationCallVideoView.Orientation = .rotation0 + private var currentAspect: CGFloat = 1.0 + + private var disposable: Disposable? + + init(input: Signal) { + super.init(frame: CGRect()) + + self.disposable = input.start(next: { [weak self] videoFrameData in + Queue.mainQueue().async { + self?.addFrame(videoFrameData) + } + }) + + self.sampleBufferLayer.videoGravity = .resize + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposable?.dispose() + } + + private func addFrame(_ videoFrameData: OngoingGroupCallContext.VideoFrameData) { + let aspect = CGFloat(videoFrameData.width) / CGFloat(videoFrameData.height) + var isAspectUpdated = false + if self.currentAspect != aspect { + self.currentAspect = aspect + isAspectUpdated = true + } + + let videoFrameOrientation = PresentationCallVideoView.Orientation(videoFrameData.orientation) + var isOrientationUpdated = false + if self.currentOrientation != videoFrameOrientation { + self.currentOrientation = videoFrameOrientation + isOrientationUpdated = true + } + + if isAspectUpdated || isOrientationUpdated { + self.onOrientationUpdated?(self.currentOrientation, self.currentAspect) + } + + if !self.didReportFirstFrame { + self.didReportFirstFrame = true + self.onFirstFrameReceived?(Float(self.currentAspect)) + } + + if self.isEnabled { + switch videoFrameData.buffer { + case let .native(buffer): + if let sampleBuffer = sampleBufferFromPixelBuffer(pixelBuffer: buffer.pixelBuffer) { + self.sampleBufferLayer.enqueue(sampleBuffer) + } + default: + break + } + } + } + + func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) { + self.onFirstFrameReceived = f + self.didReportFirstFrame = false + } + + func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) { + self.onOrientationUpdated = f + } + + func getOrientation() -> PresentationCallVideoView.Orientation { + return self.currentOrientation + } + + func getAspect() -> CGFloat { + return self.currentAspect + } + + func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) { + self.onIsMirroredUpdated = f + } + + func updateIsEnabled(_ isEnabled: Bool) { + self.isEnabled = isEnabled + } +} diff --git a/submodules/TelegramCallsUI/Sources/VideoRenderingContext.swift b/submodules/TelegramCallsUI/Sources/VideoRenderingContext.swift new file mode 100644 index 0000000000..e9bb3e13cd --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/VideoRenderingContext.swift @@ -0,0 +1,71 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import AccountContext +import TelegramVoip +import AVFoundation + +protocol VideoRenderingView: UIView { + func setOnFirstFrameReceived(_ f: @escaping (Float) -> Void) + func setOnOrientationUpdated(_ f: @escaping (PresentationCallVideoView.Orientation, CGFloat) -> Void) + func getOrientation() -> PresentationCallVideoView.Orientation + func getAspect() -> CGFloat + func setOnIsMirroredUpdated(_ f: @escaping (Bool) -> Void) + func updateIsEnabled(_ isEnabled: Bool) +} + +class VideoRenderingContext { + private var metalContextImpl: Any? + + #if targetEnvironment(simulator) + #else + @available(iOS 13.0, *) + var metalContext: MetalVideoRenderingContext { + if let value = self.metalContextImpl as? MetalVideoRenderingContext { + return value + } else { + let value = MetalVideoRenderingContext()! + self.metalContextImpl = value + return value + } + } + #endif + + func makeView(input: Signal, blur: Bool) -> VideoRenderingView? { + #if targetEnvironment(simulator) + return SampleBufferVideoRenderingView(input: input) + #else + if #available(iOS 13.0, *) { + return MetalVideoRenderingView(renderingContext: self.metalContext, input: input, blur: blur) + } else { + return SampleBufferVideoRenderingView(input: input) + } + #endif + } + + func updateVisibility(isVisible: Bool) { + #if targetEnvironment(simulator) + #else + if #available(iOS 13.0, *) { + self.metalContext.updateVisibility(isVisible: isVisible) + } + #endif + } +} + +extension PresentationCallVideoView.Orientation { + init(_ orientation: OngoingCallVideoOrientation) { + switch orientation { + case .rotation0: + self = .rotation0 + case .rotation90: + self = .rotation90 + case .rotation180: + self = .rotation180 + case .rotation270: + self = .rotation270 + } + } +} diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index fd4fda1b74..42249b143f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -918,6 +918,7 @@ public final class VoiceChatController: ViewController { private var requestedVideoSources = Set() private var requestedVideoChannels: [PresentationGroupCallRequestedVideo] = [] + private var videoRenderingContext: VideoRenderingContext private var videoNodes: [String: GroupVideoNode] = [:] private var wideVideoNodes = Set() private var videoOrder: [String] = [] @@ -971,6 +972,8 @@ public final class VoiceChatController: ViewController { self.sharedContext = sharedContext self.context = call.accountContext self.call = call + + self.videoRenderingContext = VideoRenderingContext() self.isScheduling = call.schedulePending @@ -1164,7 +1167,7 @@ public final class VoiceChatController: ViewController { let displayAsPeers: Signal<[FoundPeer], NoError> = currentAccountPeer |> then( - combineLatest(currentAccountPeer, cachedGroupCallDisplayAsAvailablePeers(account: context.account, peerId: call.peerId)) + combineLatest(currentAccountPeer, context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: call.peerId)) |> map { currentAccountPeer, availablePeers -> [FoundPeer] in var result = currentAccountPeer result.append(contentsOf: availablePeers) @@ -1550,7 +1553,7 @@ public final class VoiceChatController: ViewController { return .complete() }).start() } else { - let _ = (updatePeerDescription(account: strongSelf.context.account, peerId: peer.id, description: bio) + let _ = (strongSelf.context.engine.peers.updatePeerDescription(peerId: peer.id, description: bio) |> `catch` { _ -> Signal in return .complete() }).start() @@ -2332,13 +2335,19 @@ public final class VoiceChatController: ViewController { })) } } else { - strongSelf.call.makeIncomingVideoView(endpointId: endpointId, requestClone: GroupVideoNode.useBlurTransparency, completion: { videoView, backdropVideoView in + if let input = (strongSelf.call as! PresentationGroupCallImpl).video(endpointId: endpointId) { + if let videoView = strongSelf.videoRenderingContext.makeView(input: input, blur: false) { + completion(GroupVideoNode(videoView: videoView, backdropVideoView: strongSelf.videoRenderingContext.makeView(input: input, blur: true))) + } + } + + /*strongSelf.call.makeIncomingVideoView(endpointId: endpointId, requestClone: GroupVideoNode.useBlurTransparency, completion: { videoView, backdropVideoView in if let videoView = videoView { completion(GroupVideoNode(videoView: videoView, backdropVideoView: backdropVideoView)) } else { completion(nil) } - }) + })*/ } } } @@ -3482,7 +3491,29 @@ public final class VoiceChatController: ViewController { guard let strongSelf = self, ready else { return } - strongSelf.call.makeOutgoingVideoView(requestClone: false, completion: { [weak self] view, _ in + let videoCapturer = OngoingCallVideoCapturer() + let input = videoCapturer.video() + if let videoView = strongSelf.videoRenderingContext.makeView(input: input, blur: false) { + let cameraNode = GroupVideoNode(videoView: videoView, backdropVideoView: nil) + let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] _, unmuted in + if let strongSelf = self { + strongSelf.call.setIsMuted(action: unmuted ? .unmuted : .muted(isPushToTalkActive: false)) + (strongSelf.call as! PresentationGroupCallImpl).requestVideo(capturer: videoCapturer) + + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.animatingButtonsSwap = true + strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + } + }, switchCamera: { [weak self] in + Queue.mainQueue().after(0.1) { + self?.call.switchVideoCamera() + } + }) + strongSelf.controller?.present(controller, in: .window(.root)) + } + + /*strongSelf.call.makeOutgoingVideoView(requestClone: false, completion: { [weak self] view, _ in guard let strongSelf = self, let view = view else { return } @@ -3503,7 +3534,7 @@ public final class VoiceChatController: ViewController { } }) strongSelf.controller?.present(controller, in: .window(.root)) - }) + })*/ }) } } @@ -4547,12 +4578,18 @@ public final class VoiceChatController: ViewController { private var appIsActive = true { didSet { - self.updateVisibility() + if self.appIsActive != oldValue { + self.updateVisibility() + self.updateRequestedVideoChannels() + } } } private var visibility = false { didSet { - self.updateVisibility() + if self.visibility != oldValue { + self.updateVisibility() + self.updateRequestedVideoChannels() + } } } @@ -4574,6 +4611,8 @@ public final class VoiceChatController: ViewController { itemNode.gridVisibility = visible } } + + self.videoRenderingContext.updateVisibility(isVisible: visible) } func animateIn() { @@ -5201,7 +5240,63 @@ public final class VoiceChatController: ViewController { if !self.requestedVideoSources.contains(channel.endpointId) { self.requestedVideoSources.insert(channel.endpointId) - self.call.makeIncomingVideoView(endpointId: channel.endpointId, requestClone: GroupVideoNode.useBlurTransparency, completion: { [weak self] videoView, backdropVideoView in + + let input = (self.call as! PresentationGroupCallImpl).video(endpointId: channel.endpointId) + if let input = input, let videoView = self.videoRenderingContext.makeView(input: input, blur: false) { + let videoNode = GroupVideoNode(videoView: videoView, backdropVideoView: self.videoRenderingContext.makeView(input: input, blur: true)) + + self.readyVideoDisposables.set((combineLatest(videoNode.ready, .single(false) |> then(.single(true) |> delay(10.0, queue: Queue.mainQueue()))) + |> deliverOnMainQueue + ).start(next: { [weak self, weak videoNode] ready, timeouted in + if let strongSelf = self, let videoNode = videoNode { + Queue.mainQueue().after(0.1) { + if timeouted && !ready { + strongSelf.timeoutedEndpointIds.insert(channel.endpointId) + strongSelf.readyVideoEndpointIds.remove(channel.endpointId) + strongSelf.readyVideoEndpointIdsPromise.set(strongSelf.readyVideoEndpointIds) + strongSelf.wideVideoNodes.remove(channel.endpointId) + + strongSelf.updateMembers() + } else if ready { + strongSelf.readyVideoEndpointIds.insert(channel.endpointId) + strongSelf.readyVideoEndpointIdsPromise.set(strongSelf.readyVideoEndpointIds) + strongSelf.timeoutedEndpointIds.remove(channel.endpointId) + if videoNode.aspectRatio <= 0.77 { + strongSelf.wideVideoNodes.insert(channel.endpointId) + } else { + strongSelf.wideVideoNodes.remove(channel.endpointId) + } + strongSelf.updateMembers() + + if let (layout, _) = strongSelf.validLayout, case .compact = layout.metrics.widthClass { + if let interaction = strongSelf.itemInteraction { + loop: for i in 0 ..< strongSelf.currentFullscreenEntries.count { + let entry = strongSelf.currentFullscreenEntries[i] + switch entry { + case let .peer(peerEntry, _): + if peerEntry.effectiveVideoEndpointId == channel.endpointId { + let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme) + strongSelf.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.fullscreenItem(context: strongSelf.context, presentationData: presentationData, interaction: interaction), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil) + break loop + } + default: + break + } + } + } + } + } + } + } + }), forKey: channel.endpointId) + self.videoNodes[channel.endpointId] = videoNode + + if let _ = self.validLayout { + self.updateMembers() + } + } + + /*self.call.makeIncomingVideoView(endpointId: channel.endpointId, requestClone: GroupVideoNode.useBlurTransparency, completion: { [weak self] videoView, backdropVideoView in Queue.mainQueue().async { guard let strongSelf = self, let videoView = videoView else { return @@ -5258,7 +5353,7 @@ public final class VoiceChatController: ViewController { strongSelf.updateMembers() } } - }) + })*/ } } @@ -5366,7 +5461,9 @@ public final class VoiceChatController: ViewController { private func updateRequestedVideoChannels() { Queue.mainQueue().after(0.3) { - self.call.setRequestedVideoList(items: self.requestedVideoChannels) + let enableVideo = self.appIsActive && self.visibility + + self.call.setRequestedVideoList(items: enableVideo ? self.requestedVideoChannels : []) self.filterRequestedVideoChannels(channels: self.requestedVideoChannels) } } @@ -5839,7 +5936,7 @@ public final class VoiceChatController: ViewController { let proceed = { let _ = strongSelf.currentAvatarMixin.swap(nil) let postbox = strongSelf.context.account.postbox - strongSelf.updateAvatarDisposable.set((updatePeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, accountPeerId: strongSelf.context.account.peerId, peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in + strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.peers.updatePeerPhoto(peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) }) |> deliverOnMainQueue).start()) @@ -5892,9 +5989,9 @@ public final class VoiceChatController: ViewController { self.updateAvatarPromise.set(.single((representation, 0.0))) let postbox = self.call.account.postbox - let signal = peerId.namespace == Namespaces.Peer.CloudUser ? updateAccountPhoto(account: self.call.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in + let signal = peerId.namespace == Namespaces.Peer.CloudUser ? self.call.accountContext.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) - }) : updatePeerPhoto(postbox: postbox, network: self.call.account.network, stateManager: self.call.account.stateManager, accountPeerId: self.context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: postbox, network: self.call.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + }) : self.call.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: self.call.accountContext.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) }) @@ -5930,7 +6027,8 @@ public final class VoiceChatController: ViewController { if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue } - + + let context = self.context let account = self.context.account let signal = Signal { [weak self] subscriber in let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in @@ -5940,7 +6038,7 @@ public final class VoiceChatController: ViewController { return nil } } - let uploadInterface = LegacyLiveUploadInterface(account: account) + let uploadInterface = LegacyLiveUploadInterface(context: context) let signal: SSignal if let asset = asset as? AVAsset { signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)! @@ -6011,11 +6109,11 @@ public final class VoiceChatController: ViewController { self.updateAvatarDisposable.set((signal |> mapToSignal { videoResource -> Signal in if peerId.namespace == Namespaces.Peer.CloudUser { - return updateAccountPhoto(account: account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in + return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) }) } else { - return updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: photoResource), video: uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: context.engine.peers.uploadedPeerVideo(resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) }) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift index 2d5655924d..2c1ecb5060 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift @@ -72,7 +72,7 @@ public final class VoiceChatJoinScreen: ViewController { let context = self.context let peerId = self.peerId let invite = self.invite - let signal = updatedCurrentPeerGroupCall(account: context.account, peerId: peerId) + let signal = context.engine.calls.updatedCurrentPeerGroupCall(peerId: peerId) |> castError(GetCurrentGroupCallError.self) |> mapToSignal { call -> Signal<(Peer, GroupCallSummary)?, GetCurrentGroupCallError> in if let call = call { @@ -80,7 +80,7 @@ public final class VoiceChatJoinScreen: ViewController { return transaction.getPeer(peerId) } |> castError(GetCurrentGroupCallError.self) - return combineLatest(peer, getCurrentGroupCall(account: context.account, callId: call.id, accessHash: call.accessHash)) + return combineLatest(peer, context.engine.calls.getCurrentGroupCall(callId: call.id, accessHash: call.accessHash)) |> map { peer, call -> (Peer, GroupCallSummary)? in if let peer = peer, let call = call { return (peer, call) @@ -125,7 +125,7 @@ public final class VoiceChatJoinScreen: ViewController { currentGroupCall = .single(nil) } - self.disposable.set(combineLatest(queue: Queue.mainQueue(), signal, cachedGroupCallDisplayAsAvailablePeers(account: context.account, peerId: peerId) |> castError(GetCurrentGroupCallError.self), cachedData, currentGroupCall).start(next: { [weak self] peerAndCall, availablePeers, cachedData, currentGroupCallIdAndCanUnmute in + self.disposable.set(combineLatest(queue: Queue.mainQueue(), signal, context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId) |> castError(GetCurrentGroupCallError.self), cachedData, currentGroupCall).start(next: { [weak self] peerAndCall, availablePeers, cachedData, currentGroupCallIdAndCanUnmute in if let strongSelf = self { if let (peer, call) = peerAndCall { if let (currentGroupCall, currentGroupCallId, canUnmute) = currentGroupCallIdAndCanUnmute, call.info.id == currentGroupCallId { diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 2bb69f2c6c..32253ab0f9 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -854,7 +854,7 @@ public class Account { public private(set) var viewTracker: AccountViewTracker! public private(set) var pendingMessageManager: PendingMessageManager! public private(set) var pendingUpdateMessageManager: PendingUpdateMessageManager! - public private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager! + private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager! private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext! private var peerInputActivityManager: PeerInputActivityManager! private var localInputActivityManager: PeerInputActivityManager! diff --git a/submodules/TelegramCore/Sources/ChatContextResult.swift b/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift similarity index 100% rename from submodules/TelegramCore/Sources/ChatContextResult.swift rename to submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 6a402cc7d0..ce58de6028 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -533,7 +533,7 @@ public func signUpWithName(accountManager: AccountManager, account: Unauthorized let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) account.postbox.mediaBox.storeResourceData(resource.id, data: avatarData) - return updatePeerPhotoInternal(postbox: account.postbox, network: account.network, stateManager: nil, accountPeerId: user.id, peer: .single(user), photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource), video: avatarVideo, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { _, _ in .single([:]) }) + return _internal_updatePeerPhotoInternal(postbox: account.postbox, network: account.network, stateManager: nil, accountPeerId: user.id, peer: .single(user), photo: _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource), video: avatarVideo, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { _, _ in .single([:]) }) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 93b2960fcf..ec2be9c268 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1236,7 +1236,7 @@ public final class AccountViewTracker { return } let queue = self.queue - context.disposable.set(combineLatest(fetchAndUpdateSupplementalCachedPeerData(peerId: peerId, network: account.network, postbox: account.postbox), fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox)).start(next: { [weak self] supplementalStatus, cachedStatus in + context.disposable.set(combineLatest(fetchAndUpdateSupplementalCachedPeerData(peerId: peerId, network: account.network, postbox: account.postbox), _internal_fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox)).start(next: { [weak self] supplementalStatus, cachedStatus in queue.async { guard let strongSelf = self else { return @@ -1278,7 +1278,7 @@ public final class AccountViewTracker { return } let queue = self.queue - context.disposable.set(combineLatest(fetchAndUpdateSupplementalCachedPeerData(peerId: peerId, network: account.network, postbox: account.postbox), fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox)).start(next: { [weak self] supplementalStatus, cachedStatus in + context.disposable.set(combineLatest(fetchAndUpdateSupplementalCachedPeerData(peerId: peerId, network: account.network, postbox: account.postbox), _internal_fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox)).start(next: { [weak self] supplementalStatus, cachedStatus in queue.async { guard let strongSelf = self else { return diff --git a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift similarity index 100% rename from submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift rename to submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift diff --git a/submodules/TelegramCore/Sources/MessageMediaPreuploadManager.swift b/submodules/TelegramCore/Sources/State/MessageMediaPreuploadManager.swift similarity index 95% rename from submodules/TelegramCore/Sources/MessageMediaPreuploadManager.swift rename to submodules/TelegramCore/Sources/State/MessageMediaPreuploadManager.swift index e53e9b0414..dedd571f41 100644 --- a/submodules/TelegramCore/Sources/MessageMediaPreuploadManager.swift +++ b/submodules/TelegramCore/Sources/State/MessageMediaPreuploadManager.swift @@ -33,7 +33,7 @@ private final class MessageMediaPreuploadManagerContext { assert(self.queue.isCurrent()) } - func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete:(()->Void)? = nil) { + func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete: (()->Void)? = nil) { let context = MessageMediaPreuploadManagerUploadContext() self.uploadContexts[id] = context let queue = self.queue @@ -103,7 +103,7 @@ private final class MessageMediaPreuploadManagerContext { } } -public final class MessageMediaPreuploadManager { +final class MessageMediaPreuploadManager { private let impl: QueueLocalObject init() { @@ -113,7 +113,7 @@ public final class MessageMediaPreuploadManager { }) } - public func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete:(()->Void)? = nil) { + func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete:(()->Void)? = nil) { self.impl.with { context in context.add(network: network, postbox: postbox, id: id, encrypt: encrypt, tag: tag, source: source, onComplete: onComplete) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index 8ea9a469ae..5fbab699aa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -46,5 +46,17 @@ public extension TelegramEngine { public func registerNotificationToken(token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [PeerId.Id], excludeMutedChats: Bool) -> Signal { return _internal_registerNotificationToken(account: self.account, token: token, type: type, sandbox: sandbox, otherAccountUserIds: otherAccountUserIds, excludeMutedChats: excludeMutedChats) } + + public func updateAccountPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) + } + + public func updatePeerPhotoExisting(reference: TelegramMediaImageReference) -> Signal { + return _internal_updatePeerPhotoExisting(network: self.account.network, reference: reference) + } + + public func removeAccountPhoto(reference: TelegramMediaImageReference?) -> Signal { + return _internal_removeAccountPhoto(network: self.account.network, reference: reference) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift index 2b1e5a2ae2..c0f69f9d95 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift @@ -39,6 +39,10 @@ public extension TelegramEngineUnauthorized { public func resendTwoStepRecoveryEmail() -> Signal { return _internal_resendTwoStepRecoveryEmail(network: self.account.network) } + + public func uploadedPeerVideo(resource: MediaResource) -> Signal { + return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: nil, resource: resource) + } } } diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift similarity index 95% rename from submodules/TelegramCore/Sources/GroupCalls.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index d60bcedf95..61b4a050d1 100644 --- a/submodules/TelegramCore/Sources/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -80,7 +80,7 @@ public enum GetCurrentGroupCallError { case generic } -public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal { +func _internal_getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal { return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 3)) |> mapError { _ -> GetCurrentGroupCallError in return .generic @@ -144,7 +144,7 @@ public enum CreateGroupCallError { case scheduledTooLate } -public func createGroupCall(account: Account, peerId: PeerId, title: String?, scheduleDate: Int32?) -> Signal { +func _internal_createGroupCall(account: Account, peerId: PeerId, title: String?, scheduleDate: Int32?) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in let callPeer = transaction.getPeer(peerId).flatMap(apiInputPeer) return callPeer @@ -210,7 +210,7 @@ public enum StartScheduledGroupCallError { case generic } -public func startScheduledGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal { +func _internal_startScheduledGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal { return account.network.request(Api.functions.phone.startScheduledGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) |> mapError { error -> StartScheduledGroupCallError in return .generic @@ -254,7 +254,7 @@ public enum ToggleScheduledGroupCallSubscriptionError { case generic } -public func toggleScheduledGroupCallSubscription(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal { +func _internal_toggleScheduledGroupCallSubscription(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal { return account.network.request(Api.functions.phone.toggleGroupCallStartSubscription(call: .inputGroupCall(id: callId, accessHash: accessHash), subscribed: subscribe ? .boolTrue : .boolFalse)) |> mapError { error -> ToggleScheduledGroupCallSubscriptionError in return .generic @@ -296,7 +296,7 @@ public enum UpdateGroupCallJoinAsPeerError { case generic } -public func updateGroupCallJoinAsPeer(account: Account, peerId: PeerId, joinAs: PeerId) -> Signal { +func _internal_updateGroupCallJoinAsPeer(account: Account, peerId: PeerId, joinAs: PeerId) -> Signal { return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputPeer)? in if let peer = transaction.getPeer(peerId), let joinAsPeer = transaction.getPeer(joinAs), let inputPeer = apiInputPeer(peer), let joinInputPeer = apiInputPeer(joinAsPeer) { return (inputPeer, joinInputPeer) @@ -335,10 +335,10 @@ public enum GetGroupCallParticipantsError { case generic } -public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal { +func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal { let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int), GetGroupCallParticipantsError> - sortAscendingValue = getCurrentGroupCall(account: account, callId: callId, accessHash: accessHash) + sortAscendingValue = _internal_getCurrentGroupCall(account: account, callId: callId, accessHash: accessHash) |> mapError { _ -> GetGroupCallParticipantsError in return .generic } @@ -443,7 +443,7 @@ public struct JoinGroupCallResult { public var jsonParams: String } -public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil) -> Signal { +func _internal_joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in if let joinAs = joinAs { return transaction.getPeer(joinAs).flatMap(apiInputPeer) @@ -498,7 +498,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, cal } } - let getParticipantsRequest = getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: true) + let getParticipantsRequest = _internal_getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: true) |> mapError { _ -> JoinGroupCallError in return .generic } @@ -687,7 +687,7 @@ public struct JoinGroupCallAsScreencastResult { public var endpointId: String } -public func joinGroupCallAsScreencast(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { +func _internal_joinGroupCallAsScreencast(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { return account.network.request(Api.functions.phone.joinGroupCallPresentation(call: .inputGroupCall(id: callId, accessHash: accessHash), params: .dataJSON(data: joinPayload))) |> mapError { _ -> JoinGroupCallError in return .generic @@ -735,7 +735,7 @@ public enum LeaveGroupCallAsScreencastError { case generic } -public func leaveGroupCallAsScreencast(account: Account, callId: Int64, accessHash: Int64) -> Signal { +func _internal_leaveGroupCallAsScreencast(account: Account, callId: Int64, accessHash: Int64) -> Signal { return account.network.request(Api.functions.phone.leaveGroupCallPresentation(call: .inputGroupCall(id: callId, accessHash: accessHash))) |> mapError { _ -> LeaveGroupCallAsScreencastError in return .generic @@ -751,7 +751,7 @@ public enum LeaveGroupCallError { case generic } -public func leaveGroupCall(account: Account, callId: Int64, accessHash: Int64, source: UInt32) -> Signal { +func _internal_leaveGroupCall(account: Account, callId: Int64, accessHash: Int64, source: UInt32) -> Signal { return account.network.request(Api.functions.phone.leaveGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), source: Int32(bitPattern: source))) |> mapError { _ -> LeaveGroupCallError in return .generic @@ -767,7 +767,7 @@ public enum StopGroupCallError { case generic } -public func stopGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal { +func _internal_stopGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal { return account.network.request(Api.functions.phone.discardGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) |> mapError { _ -> StopGroupCallError in return .generic @@ -809,7 +809,7 @@ public func stopGroupCall(account: Account, peerId: PeerId, callId: Int64, acces } } -public func checkGroupCall(account: Account, callId: Int64, accessHash: Int64, ssrcs: [UInt32]) -> Signal<[UInt32], NoError> { +func _internal_checkGroupCall(account: Account, callId: Int64, accessHash: Int64, ssrcs: [UInt32]) -> Signal<[UInt32], NoError> { return account.network.request(Api.functions.phone.checkGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), sources: ssrcs.map(Int32.init(bitPattern:)))) |> `catch` { _ -> Signal<[Int32], NoError> in return .single([]) @@ -1297,7 +1297,7 @@ public final class GroupCallParticipantsContext { public private(set) var serviceState: ServiceState - public init(account: Account, peerId: PeerId, myPeerId: PeerId, id: Int64, accessHash: Int64, state: State, previousServiceState: ServiceState?) { + init(account: Account, peerId: PeerId, myPeerId: PeerId, id: Int64, accessHash: Int64, state: State, previousServiceState: ServiceState?) { self.account = account self.myPeerId = myPeerId self.id = id @@ -1563,7 +1563,7 @@ public final class GroupCallParticipantsContext { Logger.shared.log("GroupCallParticipantsContext", "will request ssrcs=\(ssrcs)") - self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: Array(ssrcs), limit: 100, sortAscending: true) + self.disposable.set((_internal_getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: Array(ssrcs), limit: 100, sortAscending: true) |> deliverOnMainQueue).start(next: { [weak self] state in guard let strongSelf = self else { return @@ -1775,7 +1775,7 @@ public final class GroupCallParticipantsContext { self.updateQueue.removeAll() - self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending) + self.disposable.set((_internal_getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending) |> deliverOnMainQueue).start(next: { [weak self] state in guard let strongSelf = self else { return @@ -2034,7 +2034,7 @@ public final class GroupCallParticipantsContext { } self.isLoadingMore = true - self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: token, ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending) + self.disposable.set((_internal_getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: token, ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending) |> deliverOnMainQueue).start(next: { [weak self] state in guard let strongSelf = self else { return @@ -2126,7 +2126,7 @@ public enum InviteToGroupCallError { case generic } -public func inviteToGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId) -> Signal { +func _internal_inviteToGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(peerId) } @@ -2161,7 +2161,7 @@ public struct GroupCallInviteLinks { } } -public func groupCallInviteLinks(account: Account, callId: Int64, accessHash: Int64) -> Signal { +func _internal_groupCallInviteLinks(account: Account, callId: Int64, accessHash: Int64) -> Signal { let call = Api.InputGroupCall.inputGroupCall(id: callId, accessHash: accessHash) let listenerInvite: Signal = account.network.request(Api.functions.phone.exportGroupCallInvite(flags: 0, call: call)) |> map(Optional.init) @@ -2201,7 +2201,7 @@ public enum EditGroupCallTitleError { case generic } -public func editGroupCallTitle(account: Account, callId: Int64, accessHash: Int64, title: String) -> Signal { +func _internal_editGroupCallTitle(account: Account, callId: Int64, accessHash: Int64, title: String) -> Signal { return account.network.request(Api.functions.phone.editGroupCallTitle(call: .inputGroupCall(id: callId, accessHash: accessHash), title: title)) |> mapError { _ -> EditGroupCallTitleError in return .generic } @@ -2211,7 +2211,7 @@ public func editGroupCallTitle(account: Account, callId: Int64, accessHash: Int6 } } -public func groupCallDisplayAsAvailablePeers(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<[FoundPeer], NoError> { +func _internal_groupCallDisplayAsAvailablePeers(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<[FoundPeer], NoError> { return postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer) } |> mapToSignal { inputPeer in @@ -2279,7 +2279,7 @@ public final class CachedDisplayAsPeers: PostboxCoding { } } -public func clearCachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: PeerId) -> Signal { +func _internal_clearCachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Void in let key = ValueBoxKey(length: 8) key.setInt64(0, value: peerId.toInt64()) @@ -2288,7 +2288,7 @@ public func clearCachedGroupCallDisplayAsAvailablePeers(account: Account, peerId |> ignoreValues } -public func cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<[FoundPeer], NoError> { +func _internal_cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<[FoundPeer], NoError> { let key = ValueBoxKey(length: 8) key.setInt64(0, value: peerId.toInt64()) return account.postbox.transaction { transaction -> ([FoundPeer], Int32)? in @@ -2314,7 +2314,7 @@ public func cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: Pee if let (cachedPeers, timestamp) = cachedPeersAndTimestamp, currentTimestamp - timestamp < 60 * 3 && !cachedPeers.isEmpty { return .single(cachedPeers) } else { - return groupCallDisplayAsAvailablePeers(network: account.network, postbox: account.postbox, peerId: peerId) + return _internal_groupCallDisplayAsAvailablePeers(network: account.network, postbox: account.postbox, peerId: peerId) |> mapToSignal { peers -> Signal<[FoundPeer], NoError> in return account.postbox.transaction { transaction -> [FoundPeer] in let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) @@ -2326,8 +2326,8 @@ public func cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: Pee } } -public func updatedCurrentPeerGroupCall(account: Account, peerId: PeerId) -> Signal { - return fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox) +func _internal_updatedCurrentPeerGroupCall(account: Account, peerId: PeerId) -> Signal { + return _internal_fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox) |> mapToSignal { _ -> Signal in return account.postbox.transaction { transaction -> CachedChannelData.ActiveCall? in return (transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData)?.activeCall @@ -2363,7 +2363,7 @@ public final class AudioBroadcastDataSource { } } -public func getAudioBroadcastDataSource(account: Account, callId: Int64, accessHash: Int64) -> Signal { +func _internal_getAudioBroadcastDataSource(account: Account, callId: Int64, accessHash: Int64) -> Signal { return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 3)) |> map(Optional.init) |> `catch` { _ -> Signal in @@ -2399,7 +2399,7 @@ public struct GetAudioBroadcastPartResult { public var responseTimestamp: Double } -public func getAudioBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64) -> Signal { +func _internal_getAudioBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64) -> Signal { let scale: Int32 switch durationMilliseconds { case 1000: diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift index 8f2dd214df..3e0f02a662 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift @@ -1,4 +1,6 @@ import SwiftSignalKit +import Postbox +import SyncCore public extension TelegramEngine { final class Calls { @@ -15,5 +17,93 @@ public extension TelegramEngine { public func saveCallDebugLog(callId: CallId, log: String) -> Signal { return _internal_saveCallDebugLog(network: self.account.network, callId: callId, log: log) } + + public func getCurrentGroupCall(callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal { + return _internal_getCurrentGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId) + } + + public func createGroupCall(peerId: PeerId, title: String?, scheduleDate: Int32?) -> Signal { + return _internal_createGroupCall(account: self.account, peerId: peerId, title: title, scheduleDate: scheduleDate) + } + + public func startScheduledGroupCall(peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal { + return _internal_startScheduledGroupCall(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash) + } + + public func toggleScheduledGroupCallSubscription(peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal { + return _internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash, subscribe: subscribe) + } + + public func updateGroupCallJoinAsPeer(peerId: PeerId, joinAs: PeerId) -> Signal { + return _internal_updateGroupCallJoinAsPeer(account: self.account, peerId: peerId, joinAs: joinAs) + } + + public func getGroupCallParticipants(callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal { + return _internal_getGroupCallParticipants(account: self.account, callId: callId, accessHash: accessHash, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending) + } + + public func joinGroupCall(peerId: PeerId, joinAs: PeerId?, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil) -> Signal { + return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, accessHash: accessHash, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash) + } + + public func joinGroupCallAsScreencast(peerId: PeerId, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { + return _internal_joinGroupCallAsScreencast(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash, joinPayload: joinPayload) + } + + public func leaveGroupCallAsScreencast(callId: Int64, accessHash: Int64) -> Signal { + return _internal_leaveGroupCallAsScreencast(account: self.account, callId: callId, accessHash: accessHash) + } + + public func leaveGroupCall(callId: Int64, accessHash: Int64, source: UInt32) -> Signal { + return _internal_leaveGroupCall(account: self.account, callId: callId, accessHash: accessHash, source: source) + } + + public func stopGroupCall(peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal { + return _internal_stopGroupCall(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash) + } + + public func checkGroupCall(callId: Int64, accessHash: Int64, ssrcs: [UInt32]) -> Signal<[UInt32], NoError> { + return _internal_checkGroupCall(account: account, callId: callId, accessHash: accessHash, ssrcs: ssrcs) + } + + public func inviteToGroupCall(callId: Int64, accessHash: Int64, peerId: PeerId) -> Signal { + return _internal_inviteToGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId) + } + + public func groupCallInviteLinks(callId: Int64, accessHash: Int64) -> Signal { + return _internal_groupCallInviteLinks(account: self.account, callId: callId, accessHash: accessHash) + } + + public func editGroupCallTitle(callId: Int64, accessHash: Int64, title: String) -> Signal { + return _internal_editGroupCallTitle(account: self.account, callId: callId, accessHash: accessHash, title: title) + } + + /*public func groupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<[FoundPeer], NoError> { + return _internal_groupCallDisplayAsAvailablePeers(network: self.account.network, postbox: self.account.postbox, peerId: peerId) + }*/ + + public func clearCachedGroupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal { + return _internal_clearCachedGroupCallDisplayAsAvailablePeers(account: self.account, peerId: peerId) + } + + public func cachedGroupCallDisplayAsAvailablePeers(peerId: PeerId) -> Signal<[FoundPeer], NoError> { + return _internal_cachedGroupCallDisplayAsAvailablePeers(account: self.account, peerId: peerId) + } + + public func updatedCurrentPeerGroupCall(peerId: PeerId) -> Signal { + return _internal_updatedCurrentPeerGroupCall(account: self.account, peerId: peerId) + } + + public func getAudioBroadcastDataSource(callId: Int64, accessHash: Int64) -> Signal { + return _internal_getAudioBroadcastDataSource(account: self.account, callId: callId, accessHash: accessHash) + } + + public func getAudioBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64) -> Signal { + return _internal_getAudioBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds) + } + + public func groupCall(peerId: PeerId, myPeerId: PeerId, id: Int64, accessHash: Int64, state: GroupCallParticipantsContext.State, previousServiceState: GroupCallParticipantsContext.ServiceState?) -> GroupCallParticipantsContext { + return GroupCallParticipantsContext(account: self.account, peerId: peerId, myPeerId: myPeerId, id: id, accessHash: accessHash, state: state, previousServiceState: previousServiceState) + } } } diff --git a/submodules/TelegramCore/Sources/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift similarity index 79% rename from submodules/TelegramCore/Sources/OutgoingMessageWithChatContextResult.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index 9b186b1fd0..51b5b930f5 100644 --- a/submodules/TelegramCore/Sources/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -3,8 +3,15 @@ import Postbox import SwiftSignalKit import SyncCore +func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, results: ChatContextResultCollection, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> Bool { + guard let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else { + return false + } + let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start() + return true +} -public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: ChatContextResultCollection, result: ChatContextResult, hideVia: Bool = false, scheduleTime: Int32? = nil, correlationId: Int64? = nil) -> EnqueueMessage? { +private func outgoingMessageWithChatContextResult(to peerId: PeerId, results: ChatContextResultCollection, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { var attributes: [MessageAttribute] = [] attributes.append(OutgoingChatContextResultMessageAttribute(queryId: result.queryId, id: result.id, hideVia: hideVia)) if !hideVia { @@ -13,6 +20,9 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha if let scheduleTime = scheduleTime { attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) } + if silentPosting { + attributes.append(NotificationInfoMessageAttribute(flags: .muted)) + } switch result.message { case let .auto(caption, entities, replyMarkup): if let entities = entities { @@ -32,19 +42,19 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha return true } if let media: Media = internalReference.file ?? internalReference.image { - return .message(text: caption, attributes: filteredAttributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: filteredAttributes, mediaReference: .standalone(media: media), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } else { - return .message(text: caption, attributes: filteredAttributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: filteredAttributes, mediaReference: nil, replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } } else { - return .message(text: "", attributes: attributes, mediaReference: .standalone(media: TelegramMediaGame(gameId: 0, accessHash: 0, name: "", title: internalReference.title ?? "", description: internalReference.description ?? "", image: internalReference.image, file: internalReference.file)), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: "", attributes: attributes, mediaReference: .standalone(media: TelegramMediaGame(gameId: 0, accessHash: 0, name: "", title: internalReference.title ?? "", description: internalReference.description ?? "", image: internalReference.image, file: internalReference.file)), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } } else if let file = internalReference.file, internalReference.type == "gif" { - return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: file), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } else if let image = internalReference.image { - return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: image), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: image), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } else if let file = internalReference.file { - return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: file), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } else { return nil } @@ -56,9 +66,9 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha let thumbnailResource = thumbnail.resource let imageDimensions = thumbnail.dimensions ?? PixelDimensions(width: 128, height: 128) let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: tmpImage), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: tmpImage), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } else { - return .message(text: caption, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: attributes, mediaReference: nil, replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } } else if externalReference.type == "document" || externalReference.type == "gif" || externalReference.type == "audio" || externalReference.type == "voice" { var videoThumbnails: [TelegramMediaFile.VideoThumbnail] = [] @@ -118,9 +128,9 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha } let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: externalReference.content?.mimeType ?? "application/binary", size: nil, attributes: fileAttributes) - return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: file), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } else { - return .message(text: caption, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: caption, attributes: attributes, mediaReference: nil, replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } } case let .text(text, entities, disableUrlPreview, replyMarkup): @@ -130,21 +140,21 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha if let replyMarkup = replyMarkup { attributes.append(replyMarkup) } - return .message(text: text, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: text, attributes: attributes, mediaReference: nil, replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) case let .mapLocation(media, replyMarkup): if let replyMarkup = replyMarkup { attributes.append(replyMarkup) } - return .message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) case let .contact(media, replyMarkup): if let replyMarkup = replyMarkup { attributes.append(replyMarkup) } - return .message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) case let .invoice(media, replyMarkup): if let replyMarkup = replyMarkup { attributes.append(replyMarkup) } - return .message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil, correlationId: correlationId) + return .message(text: "", attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: replyToMessageId, localGroupingKey: nil, correlationId: correlationId) } } diff --git a/submodules/TelegramCore/Sources/PeerLiveLocationsContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PeerLiveLocationsContext.swift similarity index 88% rename from submodules/TelegramCore/Sources/PeerLiveLocationsContext.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/PeerLiveLocationsContext.swift index 3609e85ced..04be82ce54 100644 --- a/submodules/TelegramCore/Sources/PeerLiveLocationsContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PeerLiveLocationsContext.swift @@ -2,10 +2,9 @@ import Foundation import Postbox import SwiftSignalKit import TelegramApi - import SyncCore -public func topPeerActiveLiveLocationMessages(viewTracker: AccountViewTracker, accountPeerId: PeerId, peerId: PeerId) -> Signal<(Peer?, [Message]), NoError> { +func _internal_topPeerActiveLiveLocationMessages(viewTracker: AccountViewTracker, accountPeerId: PeerId, peerId: PeerId) -> Signal<(Peer?, [Message]), NoError> { return viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 50, fixedCombinedReadStates: nil, tagMask: .liveLocation, orderStatistics: [], additionalData: [.peer(accountPeerId)]) |> map { (view, _, _) -> (Peer?, [Message]) in var accountPeer: Peer? diff --git a/submodules/TelegramCore/Sources/RecentlyUsedHashtags.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RecentlyUsedHashtags.swift similarity index 90% rename from submodules/TelegramCore/Sources/RecentlyUsedHashtags.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/RecentlyUsedHashtags.swift index 0bd04d68a4..9c313facbe 100644 --- a/submodules/TelegramCore/Sources/RecentlyUsedHashtags.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RecentlyUsedHashtags.swift @@ -30,7 +30,7 @@ func addRecentlyUsedHashtag(transaction: Transaction, string: String) { } } -public func removeRecentlyUsedHashtag(postbox: Postbox, string: String) -> Signal { +func _internal_removeRecentlyUsedHashtag(postbox: Postbox, string: String) -> Signal { return postbox.transaction { transaction -> Void in if let itemId = RecentHashtagItemId(string) { transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlyUsedHashtags, itemId: itemId.rawValue) @@ -38,7 +38,7 @@ public func removeRecentlyUsedHashtag(postbox: Postbox, string: String) -> Signa } } -public func recentlyUsedHashtags(postbox: Postbox) -> Signal<[String], NoError> { +func _internal_recentlyUsedHashtags(postbox: Postbox) -> Signal<[String], NoError> { return postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.RecentlyUsedHashtags)]) |> mapToSignal { view -> Signal<[String], NoError> in return postbox.transaction { transaction -> [String] in diff --git a/submodules/TelegramCore/Sources/RequestChatContextResults.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestChatContextResults.swift similarity index 95% rename from submodules/TelegramCore/Sources/RequestChatContextResults.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestChatContextResults.swift index 20ef3a4b63..39f0488710 100644 --- a/submodules/TelegramCore/Sources/RequestChatContextResults.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestChatContextResults.swift @@ -51,7 +51,7 @@ public struct RequestChatContextResultsResult { } } -public func requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal { +func _internal_requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal { return account.postbox.transaction { transaction -> (bot: Peer, peer: Peer)? in if let bot = transaction.getPeer(botId), let peer = transaction.getPeer(peerId) { return (bot, peer) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index c11b8dca37..6dc415b280 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -151,5 +151,25 @@ public extension TelegramEngine { public func exportMessageLink(peerId: PeerId, messageId: MessageId, isThread: Bool = false) -> Signal { return _internal_exportMessageLink(account: self.account, peerId: peerId, messageId: messageId, isThread: isThread) } + + public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, results: ChatContextResultCollection, result: ChatContextResult, replyToMessageId: MessageId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, correlationId: Int64? = nil) -> Bool { + return _internal_enqueueOutgoingMessageWithChatContextResult(account: self.account, to: peerId, results: results, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) + } + + public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal { + return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) + } + + public func removeRecentlyUsedHashtag(string: String) -> Signal { + return _internal_removeRecentlyUsedHashtag(postbox: self.account.postbox, string: string) + } + + public func recentlyUsedHashtags() -> Signal<[String], NoError> { + return _internal_recentlyUsedHashtags(postbox: self.account.postbox) + } + + public func topPeerActiveLiveLocationMessages(peerId: PeerId) -> Signal<(Peer?, [Message]), NoError> { + return _internal_topPeerActiveLiveLocationMessages(viewTracker: self.account.viewTracker, accountPeerId: self.account.peerId, peerId: peerId) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/PeerManagement/TelegramEnginePeerManagement.swift b/submodules/TelegramCore/Sources/TelegramEngine/PeerManagement/TelegramEnginePeerManagement.swift deleted file mode 100644 index 112d788c1d..0000000000 --- a/submodules/TelegramCore/Sources/TelegramEngine/PeerManagement/TelegramEnginePeerManagement.swift +++ /dev/null @@ -1,13 +0,0 @@ -import SwiftSignalKit - -public extension TelegramEngine { - final class PeerManagement { - private let account: Account - - init(account: Account) { - self.account = account - } - - - } -} diff --git a/submodules/TelegramCore/Sources/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift similarity index 97% rename from submodules/TelegramCore/Sources/ChatListFiltering.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 05a4c0cf1d..fb7ca86e26 100644 --- a/submodules/TelegramCore/Sources/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -367,7 +367,7 @@ public enum RequestUpdateChatListFilterError { case generic } -public func requestUpdateChatListFilter(postbox: Postbox, network: Network, id: Int32, filter: ChatListFilter?) -> Signal { +func _internal_requestUpdateChatListFilter(postbox: Postbox, network: Network, id: Int32, filter: ChatListFilter?) -> Signal { return postbox.transaction { transaction -> Api.DialogFilter? in return filter?.apiFilter(transaction: transaction) } @@ -391,7 +391,7 @@ public enum RequestUpdateChatListFilterOrderError { case generic } -public func requestUpdateChatListFilterOrder(account: Account, ids: [Int32]) -> Signal { +func _internal_requestUpdateChatListFilterOrder(account: Account, ids: [Int32]) -> Signal { return account.network.request(Api.functions.messages.updateDialogFiltersOrder(order: ids)) |> mapError { _ -> RequestUpdateChatListFilterOrderError in return .generic @@ -781,7 +781,7 @@ struct ChatListFiltersState: PreferencesEntry, Equatable { } } -public func generateNewChatListFilterId(filters: [ChatListFilter]) -> Int32 { +func _internal_generateNewChatListFilterId(filters: [ChatListFilter]) -> Int32 { while true { let id = Int32(2 + arc4random_uniform(255 - 2)) if !filters.contains(where: { $0.id == id }) { @@ -790,7 +790,7 @@ public func generateNewChatListFilterId(filters: [ChatListFilter]) -> Int32 { } } -public func updateChatListFiltersInteractively(postbox: Postbox, _ f: @escaping ([ChatListFilter]) -> [ChatListFilter]) -> Signal<[ChatListFilter], NoError> { +func _internal_updateChatListFiltersInteractively(postbox: Postbox, _ f: @escaping ([ChatListFilter]) -> [ChatListFilter]) -> Signal<[ChatListFilter], NoError> { return postbox.transaction { transaction -> [ChatListFilter] in var updated: [ChatListFilter] = [] var hasUpdates = false @@ -811,7 +811,7 @@ public func updateChatListFiltersInteractively(postbox: Postbox, _ f: @escaping } } -public func updateChatListFiltersInteractively(transaction: Transaction, _ f: ([ChatListFilter]) -> [ChatListFilter]) { +func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f: ([ChatListFilter]) -> [ChatListFilter]) { var hasUpdates = false transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in var state = entry as? ChatListFiltersState ?? ChatListFiltersState.default @@ -828,7 +828,7 @@ public func updateChatListFiltersInteractively(transaction: Transaction, _ f: ([ } -public func updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> { +func _internal_updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> { return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters]) |> map { preferences -> [ChatListFilter] in let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default @@ -837,7 +837,7 @@ public func updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], |> distinctUntilChanged } -public func updatedChatListFiltersInfo(postbox: Postbox) -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> { +func _internal_updatedChatListFiltersInfo(postbox: Postbox) -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> { return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters]) |> map { preferences -> (filters: [ChatListFilter], synchronized: Bool) in let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default @@ -854,7 +854,7 @@ public func updatedChatListFiltersInfo(postbox: Postbox) -> Signal<(filters: [Ch }) } -public func currentChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> { +func _internal_currentChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> { return postbox.transaction { transaction -> [ChatListFilter] in let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default return settings.filters @@ -941,7 +941,7 @@ public struct ChatListFiltersFeaturedState: PreferencesEntry, Equatable { } } -public func markChatListFeaturedFiltersAsSeen(postbox: Postbox) -> Signal { +func _internal_markChatListFeaturedFiltersAsSeen(postbox: Postbox) -> Signal { return postbox.transaction { transaction -> Void in transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState, { entry in guard var state = entry as? ChatListFiltersFeaturedState else { @@ -954,7 +954,7 @@ public func markChatListFeaturedFiltersAsSeen(postbox: Postbox) -> Signal ignoreValues } -public func unmarkChatListFeaturedFiltersAsSeen(transaction: Transaction) { +func _internal_unmarkChatListFeaturedFiltersAsSeen(transaction: Transaction) { transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState, { entry in guard var state = entry as? ChatListFiltersFeaturedState else { return entry @@ -964,7 +964,7 @@ public func unmarkChatListFeaturedFiltersAsSeen(transaction: Transaction) { }) } -public func updateChatListFeaturedFilters(postbox: Postbox, network: Network) -> Signal { +func _internal_updateChatListFeaturedFilters(postbox: Postbox, network: Network) -> Signal { return network.request(Api.functions.messages.getSuggestedDialogFilters()) |> `catch` { _ -> Signal<[Api.DialogFilterSuggested], NoError> in return .single([]) @@ -1111,7 +1111,7 @@ func requestChatListFiltersSync(transaction: Transaction) { func managedChatListFilters(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal { return Signal { _ in - let updateFeaturedDisposable = updateChatListFeaturedFilters(postbox: postbox, network: network).start() + let updateFeaturedDisposable = _internal_updateChatListFeaturedFilters(postbox: postbox, network: network).start() let _ = postbox.transaction({ transaction in requestChatListFiltersSync(transaction: transaction) }).start() @@ -1218,7 +1218,7 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId: if !mergedFilterIds.contains(where: { $0 == filter.id }) { deleteSignals = deleteSignals |> then( - requestUpdateChatListFilter(postbox: postbox, network: network, id: filter.id, filter: nil) + _internal_requestUpdateChatListFilter(postbox: postbox, network: network, id: filter.id, filter: nil) |> `catch` { _ -> Signal in return .complete() } @@ -1238,7 +1238,7 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId: if updated { addSignals = addSignals |> then( - requestUpdateChatListFilter(postbox: postbox, network: network, id: filter.id, filter: filter) + _internal_requestUpdateChatListFilter(postbox: postbox, network: network, id: filter.id, filter: filter) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/CheckPeerChatServiceActions.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/CheckPeerChatServiceActions.swift similarity index 87% rename from submodules/TelegramCore/Sources/CheckPeerChatServiceActions.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/CheckPeerChatServiceActions.swift index d72f1b809a..01b5acd4ce 100644 --- a/submodules/TelegramCore/Sources/CheckPeerChatServiceActions.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/CheckPeerChatServiceActions.swift @@ -4,7 +4,7 @@ import SwiftSignalKit import SyncCore -public func checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal { +func _internal_checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in transaction.applyMarkUnread(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, value: false, interactive: true) diff --git a/submodules/TelegramCore/Sources/InvitationLinks.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift similarity index 95% rename from submodules/TelegramCore/Sources/InvitationLinks.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift index 1bb8d95165..2eddd1043c 100644 --- a/submodules/TelegramCore/Sources/InvitationLinks.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift @@ -3,11 +3,9 @@ import Postbox import SwiftSignalKit import TelegramApi import MtProtoKit - import SyncCore - -public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal { +func _internal_revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { let flags: Int32 = (1 << 2) @@ -58,7 +56,7 @@ public enum CreatePeerExportedInvitationError { case generic } -public func createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal { +func _internal_createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var flags: Int32 = 0 @@ -85,7 +83,7 @@ public enum EditPeerExportedInvitationError { case generic } -public func editPeerExportedInvitation(account: Account, peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal { +func _internal_editPeerExportedInvitation(account: Account, peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var flags: Int32 = 0 @@ -131,7 +129,7 @@ public enum RevokeExportedInvitationResult { case replace(ExportedInvitation, ExportedInvitation) } -public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal { +func _internal_revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { let flags: Int32 = (1 << 2) @@ -197,7 +195,7 @@ public struct ExportedInvitations : Equatable { public let totalCount: Int32 } -public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, adminId: PeerId? = nil, offsetLink: ExportedInvitation? = nil) -> Signal { +func _internal_peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, adminId: PeerId? = nil, offsetLink: ExportedInvitation? = nil) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let adminPeer = transaction.getPeer(adminId ?? account.peerId), let adminId = apiInputUser(adminPeer) { var flags: Int32 = 0 @@ -244,7 +242,7 @@ public enum DeletePeerExportedInvitationError { case generic } -public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal { +func _internal_deletePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { return account.network.request(Api.functions.messages.deleteExportedChatInvite(peer: inputPeer, link: link)) @@ -258,7 +256,7 @@ public func deletePeerExportedInvitation(account: Account, peerId: PeerId, link: |> switchToLatest } -public func deleteAllRevokedPeerExportedInvitations(account: Account, peerId: PeerId, adminId: PeerId) -> Signal { +func _internal_deleteAllRevokedPeerExportedInvitations(account: Account, peerId: PeerId, adminId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let adminPeer = transaction.getPeer(adminId), let inputAdminId = apiInputUser(adminPeer) { return account.network.request(Api.functions.messages.deleteRevokedExportedChatInvites(peer: inputPeer, adminId: inputAdminId)) @@ -304,7 +302,7 @@ final class CachedPeerExportedInvitations: PostboxCoding { let canLoadMore: Bool let count: Int32 - public static func key(peerId: PeerId, revoked: Bool) -> ValueBoxKey { + static func key(peerId: PeerId, revoked: Bool) -> ValueBoxKey { let key = ValueBoxKey(length: 8 + 4) key.setInt64(0, value: peerId.toInt64()) key.setInt32(8, value: revoked ? 1 : 0) @@ -317,13 +315,13 @@ final class CachedPeerExportedInvitations: PostboxCoding { self.count = count } - public init(decoder: PostboxDecoder) { + init(decoder: PostboxDecoder) { self.invitations = decoder.decodeObjectArrayForKey("invitations") self.canLoadMore = decoder.decodeBoolForKey("canLoadMore", orElse: false) self.count = decoder.decodeInt32ForKey("count", orElse: 0) } - public func encode(_ encoder: PostboxEncoder) { + func encode(_ encoder: PostboxEncoder) { encoder.encodeObjectArray(self.invitations, forKey: "invitations") encoder.encodeBool(self.canLoadMore, forKey: "canLoadMore") encoder.encodeInt32(self.count, forKey: "count") @@ -494,7 +492,7 @@ private final class PeerExportedInvitationsContextImpl { self.updateState() } - public func add(_ invite: ExportedInvitation) { + func add(_ invite: ExportedInvitation) { var results = self.results results.removeAll(where: { $0.link == invite.link}) results.insert(invite, at: 0) @@ -503,7 +501,7 @@ private final class PeerExportedInvitationsContextImpl { self.updateCache() } - public func update(_ invite: ExportedInvitation) { + func update(_ invite: ExportedInvitation) { var results = self.results if let index = self.results.firstIndex(where: { $0.link == invite.link }) { results[index] = invite @@ -513,7 +511,7 @@ private final class PeerExportedInvitationsContextImpl { self.updateCache() } - public func remove(_ invite: ExportedInvitation) { + func remove(_ invite: ExportedInvitation) { var results = self.results results.removeAll(where: { $0.link == invite.link}) self.results = results @@ -521,7 +519,7 @@ private final class PeerExportedInvitationsContextImpl { self.updateCache() } - public func clear() { + func clear() { self.results = [] self.count = 0 self.updateState() @@ -564,7 +562,7 @@ public final class PeerExportedInvitationsContext { } } - public init(account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) { + init(account: Account, peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { return PeerExportedInvitationsContextImpl(queue: queue, account: account, peerId: peerId, adminId: adminId, revoked: revoked, forceUpdate: forceUpdate) @@ -629,7 +627,7 @@ final class CachedPeerInvitationImporters: PostboxCoding { let dates: [PeerId: Int32] let count: Int32 - public static func key(peerId: PeerId, link: String) -> ValueBoxKey { + static func key(peerId: PeerId, link: String) -> ValueBoxKey { let key = ValueBoxKey(length: 8 + 4) key.setInt64(0, value: peerId.toInt64()) key.setInt32(8, value: Int32(HashFunctions.murMurHash32(link))) @@ -644,13 +642,13 @@ final class CachedPeerInvitationImporters: PostboxCoding { self.count = count } - public init(peerIds: [PeerId], dates: [PeerId: Int32], count: Int32) { + init(peerIds: [PeerId], dates: [PeerId: Int32], count: Int32) { self.peerIds = peerIds self.dates = dates self.count = count } - public init(decoder: PostboxDecoder) { + init(decoder: PostboxDecoder) { self.peerIds = decoder.decodeInt64ArrayForKey("peerIds").map(PeerId.init) var dates: [PeerId: Int32] = [:] @@ -666,7 +664,7 @@ final class CachedPeerInvitationImporters: PostboxCoding { self.count = decoder.decodeInt32ForKey("count", orElse: 0) } - public func encode(_ encoder: PostboxEncoder) { + func encode(_ encoder: PostboxEncoder) { encoder.encodeInt64Array(self.peerIds.map { $0.toInt64() }, forKey: "peerIds") var dates: [Int64] = [] @@ -859,7 +857,7 @@ public final class PeerInvitationImportersContext { } } - public init(account: Account, peerId: PeerId, invite: ExportedInvitation) { + init(account: Account, peerId: PeerId, invite: ExportedInvitation) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { return PeerInvitationImportersContextImpl(queue: queue, account: account, peerId: peerId, invite: invite) @@ -879,7 +877,7 @@ public struct ExportedInvitationCreator : Equatable { public let revokedCount: Int32 } -public func peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> { +func _internal_peerExportedInvitationsCreators(account: Account, peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> { return account.postbox.transaction { transaction -> Signal<[ExportedInvitationCreator], NoError> in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var isCreator = false diff --git a/submodules/TelegramCore/Sources/JoinLink.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift similarity index 94% rename from submodules/TelegramCore/Sources/JoinLink.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift index cd8fbc8486..fccad10f9c 100644 --- a/submodules/TelegramCore/Sources/JoinLink.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift @@ -29,7 +29,7 @@ public enum ExternalJoiningChatState { case peek(PeerId, Int32) } -public func joinChatInteractively(with hash: String, account: Account) -> Signal { +func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal { return account.network.request(Api.functions.messages.importChatInvite(hash: hash)) |> mapError { error -> JoinLinkError in switch error.errorDescription { @@ -59,7 +59,7 @@ public func joinChatInteractively(with hash: String, account: Account) -> Signal } } -public func joinLinkInformation(_ hash: String, account: Account) -> Signal { +func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal { return account.network.request(Api.functions.messages.checkChatInvite(hash: hash)) |> map(Optional.init) |> `catch` { _ -> Signal in diff --git a/submodules/TelegramCore/Sources/NotificationExceptionsList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationExceptionsList.swift similarity index 96% rename from submodules/TelegramCore/Sources/NotificationExceptionsList.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationExceptionsList.swift index cf27345ec7..68e2452832 100644 --- a/submodules/TelegramCore/Sources/NotificationExceptionsList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationExceptionsList.swift @@ -19,7 +19,7 @@ public final class NotificationExceptionsList: Equatable { } } -public func notificationExceptionsList(postbox: Postbox, network: Network) -> Signal { +func _internal_notificationExceptionsList(postbox: Postbox, network: Network) -> Signal { return network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 1, peer: nil)) |> retryRequest |> mapToSignal { result -> Signal in diff --git a/submodules/TelegramCore/Sources/PeerPhotoUpdater.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift similarity index 89% rename from submodules/TelegramCore/Sources/PeerPhotoUpdater.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift index 1b787357bb..0072a8a713 100644 --- a/submodules/TelegramCore/Sources/PeerPhotoUpdater.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift @@ -15,8 +15,8 @@ public enum UploadPeerPhotoError { case generic } -public func updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - return updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: resource.flatMap({ uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) +func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) } public struct UploadedPeerPhotoData { @@ -37,7 +37,7 @@ enum UploadedPeerPhotoDataContent { case error } -public func uploadedPeerPhoto(postbox: Postbox, network: Network, resource: MediaResource) -> Signal { +func _internal_uploadedPeerPhoto(postbox: Postbox, network: Network, resource: MediaResource) -> Signal { return multipartUpload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false) |> map { result -> UploadedPeerPhotoData in return UploadedPeerPhotoData(resource: resource, content: .result(result)) @@ -47,7 +47,7 @@ public func uploadedPeerPhoto(postbox: Postbox, network: Network, resource: Medi } } -public func uploadedPeerVideo(postbox: Postbox, network: Network, messageMediaPreuploadManager: MessageMediaPreuploadManager?, resource: MediaResource) -> Signal { +func _internal_uploadedPeerVideo(postbox: Postbox, network: Network, messageMediaPreuploadManager: MessageMediaPreuploadManager?, resource: MediaResource) -> Signal { if let messageMediaPreuploadManager = messageMediaPreuploadManager { return messageMediaPreuploadManager.upload(network: network, postbox: postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .video), hintFileSize: nil, hintFileIsLarge: false) |> map { result -> UploadedPeerPhotoData in @@ -67,11 +67,11 @@ public func uploadedPeerVideo(postbox: Postbox, network: Network, messageMediaPr } } -public func updatePeerPhoto(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peerId: PeerId, photo: Signal?, video: Signal? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - return updatePeerPhotoInternal(postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId, peer: postbox.loadedPeerWithId(peerId), photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) +func _internal_updatePeerPhoto(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peerId: PeerId, photo: Signal?, video: Signal? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updatePeerPhotoInternal(postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId, peer: postbox.loadedPeerWithId(peerId), photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) } -public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peer: Signal, photo: Signal?, video: Signal?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { +func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peer: Signal, photo: Signal?, video: Signal?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { return peer |> mapError { _ in return .generic } |> mapToSignal { peer -> Signal in @@ -260,7 +260,7 @@ public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateMan } |> mapToSignal { result, resource, videoResource -> Signal in if case .complete = result { - return fetchAndUpdateCachedPeerData(accountPeerId: accountPeerId, peerId: peer.id, network: network, postbox: postbox) + return _internal_fetchAndUpdateCachedPeerData(accountPeerId: accountPeerId, peerId: peer.id, network: network, postbox: postbox) |> castError(UploadPeerPhotoError.self) |> mapToSignal { status -> Signal in return postbox.transaction { transaction in @@ -335,7 +335,7 @@ public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateMan } } -public func updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal { +func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal { switch reference { case let .cloud(imageId, accessHash, fileReference): return network.request(Api.functions.photos.updateProfilePhoto(id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference)))) @@ -352,7 +352,7 @@ public func updatePeerPhotoExisting(network: Network, reference: TelegramMediaIm } } -public func removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal { +func _internal_removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal { if let reference = reference { switch reference { case let .cloud(imageId, accessHash, fileReference): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/RemovePeerChat.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RemovePeerChat.swift index f5c31ba638..3858dcf79e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/RemovePeerChat.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RemovePeerChat.swift @@ -34,7 +34,7 @@ func _internal_removePeerChat(account: Account, transaction: Transaction, mediaB } }) } - updateChatListFiltersInteractively(transaction: transaction, { filters in + _internal_updateChatListFiltersInteractively(transaction: transaction, { filters in var filters = filters for i in 0 ..< filters.count { if filters[i].data.includePeers.peers.contains(peerId) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index c21dd1d8fe..cc30d9219f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftSignalKit import Postbox import SyncCore @@ -327,5 +328,136 @@ public extension TelegramEngine { public func removeRecentlyUsedInlineBot(peerId: PeerId) -> Signal { return _internal_removeRecentlyUsedInlineBot(account: self.account, peerId: peerId) } + + public func uploadedPeerPhoto(resource: MediaResource) -> Signal { + return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource) + } + + public func uploadedPeerVideo(resource: MediaResource) -> Signal { + return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource) + } + + public func updatePeerPhoto(peerId: PeerId, photo: Signal?, video: Signal? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updatePeerPhoto(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, accountPeerId: self.account.peerId, peerId: peerId, photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) + } + + public func requestUpdateChatListFilter(id: Int32, filter: ChatListFilter?) -> Signal { + return _internal_requestUpdateChatListFilter(postbox: self.account.postbox, network: self.account.network, id: id, filter: filter) + } + + public func requestUpdateChatListFilterOrder(ids: [Int32]) -> Signal { + return _internal_requestUpdateChatListFilterOrder(account: self.account, ids: ids) + } + + public func generateNewChatListFilterId(filters: [ChatListFilter]) -> Int32 { + return _internal_generateNewChatListFilterId(filters: filters) + } + + public func updateChatListFiltersInteractively(_ f: @escaping ([ChatListFilter]) -> [ChatListFilter]) -> Signal<[ChatListFilter], NoError> { + return _internal_updateChatListFiltersInteractively(postbox: self.account.postbox, f) + } + + public func updatedChatListFilters() -> Signal<[ChatListFilter], NoError> { + return _internal_updatedChatListFilters(postbox: self.account.postbox) + } + + public func updatedChatListFiltersInfo() -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> { + return _internal_updatedChatListFiltersInfo(postbox: self.account.postbox) + } + + public func currentChatListFilters() -> Signal<[ChatListFilter], NoError> { + return _internal_currentChatListFilters(postbox: self.account.postbox) + } + + public func markChatListFeaturedFiltersAsSeen() -> Signal { + return _internal_markChatListFeaturedFiltersAsSeen(postbox: self.account.postbox) + } + + public func updateChatListFeaturedFilters() -> Signal { + return _internal_updateChatListFeaturedFilters(postbox: self.account.postbox, network: self.account.network) + } + + public func unmarkChatListFeaturedFiltersAsSeen() -> Signal { + return self.account.postbox.transaction { transaction in + _internal_unmarkChatListFeaturedFiltersAsSeen(transaction: transaction) + } + |> ignoreValues + } + + public func checkPeerChatServiceActions(peerId: PeerId) -> Signal { + return _internal_checkPeerChatServiceActions(postbox: self.account.postbox, peerId: peerId) + } + + public func createPeerExportedInvitation(peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal { + return _internal_createPeerExportedInvitation(account: self.account, peerId: peerId, expireDate: expireDate, usageLimit: usageLimit) + } + + public func editPeerExportedInvitation(peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal { + return _internal_editPeerExportedInvitation(account: self.account, peerId: peerId, link: link, expireDate: expireDate, usageLimit: usageLimit) + } + + public func revokePeerExportedInvitation(peerId: PeerId, link: String) -> Signal { + return _internal_revokePeerExportedInvitation(account: self.account, peerId: peerId, link: link) + } + + public func deletePeerExportedInvitation(peerId: PeerId, link: String) -> Signal { + return _internal_deletePeerExportedInvitation(account: self.account, peerId: peerId, link: link) + } + + public func deleteAllRevokedPeerExportedInvitations(peerId: PeerId, adminId: PeerId) -> Signal { + return _internal_deleteAllRevokedPeerExportedInvitations(account: self.account, peerId: peerId, adminId: adminId) + } + + public func peerExportedInvitationsCreators(peerId: PeerId) -> Signal<[ExportedInvitationCreator], NoError> { + return _internal_peerExportedInvitationsCreators(account: self.account, peerId: peerId) + } + + public func peerExportedInvitations(peerId: PeerId, adminId: PeerId?, revoked: Bool, forceUpdate: Bool) -> PeerExportedInvitationsContext { + return PeerExportedInvitationsContext(account: self.account, peerId: peerId, adminId: adminId, revoked: revoked, forceUpdate: forceUpdate) + } + + public func peerInvitationImporters(peerId: PeerId, invite: ExportedInvitation) -> PeerInvitationImportersContext { + return PeerInvitationImportersContext(account: self.account, peerId: peerId, invite: invite) + } + + public func notificationExceptionsList() -> Signal { + return _internal_notificationExceptionsList(postbox: self.account.postbox, network: self.account.network) + } + + public func fetchAndUpdateCachedPeerData(peerId: PeerId) -> Signal { + return _internal_fetchAndUpdateCachedPeerData(accountPeerId: self.account.peerId, peerId: peerId, network: self.account.network, postbox: self.account.postbox) + } + + public func toggleItemPinned(location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal { + return _internal_toggleItemPinned(postbox: self.account.postbox, location: location, itemId: itemId) + } + + public func getPinnedItemIds(location: TogglePeerChatPinnedLocation) -> Signal<[PinnedItemId], NoError> { + return self.account.postbox.transaction { transaction -> [PinnedItemId] in + return _internal_getPinnedItemIds(transaction: transaction, location: location) + } + } + + public func reorderPinnedItemIds(location: TogglePeerChatPinnedLocation, itemIds: [PinnedItemId]) -> Signal { + return self.account.postbox.transaction { transaction -> Bool in + return _internal_reorderPinnedItemIds(transaction: transaction, location: location, itemIds: itemIds) + } + } + + public func joinChatInteractively(with hash: String) -> Signal { + return _internal_joinChatInteractively(with: hash, account: self.account) + } + + public func joinLinkInformation(_ hash: String) -> Signal { + return _internal_joinLinkInformation(hash, account: self.account) + } + + public func updatePeerTitle(peerId: PeerId, title: String) -> Signal { + return _internal_updatePeerTitle(account: self.account, peerId: peerId, title: title) + } + + public func updatePeerDescription(peerId: PeerId, description: String?) -> Signal { + return _internal_updatePeerDescription(account: self.account, peerId: peerId, description: description) + } } } diff --git a/submodules/TelegramCore/Sources/TogglePeerChatPinned.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TogglePeerChatPinned.swift similarity index 86% rename from submodules/TelegramCore/Sources/TogglePeerChatPinned.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/TogglePeerChatPinned.swift index ab3a0de39e..a49b9b445d 100644 --- a/submodules/TelegramCore/Sources/TogglePeerChatPinned.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TogglePeerChatPinned.swift @@ -14,7 +14,7 @@ public enum TogglePeerChatPinnedResult { case limitExceeded(Int) } -public func toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal { +func _internal_toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal { return postbox.transaction { transaction -> TogglePeerChatPinnedResult in switch location { case let .group(groupId): @@ -60,7 +60,7 @@ public func toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLoc } case let .filter(filterId): var result: TogglePeerChatPinnedResult = .done - updateChatListFiltersInteractively(transaction: transaction, { filters in + _internal_updateChatListFiltersInteractively(transaction: transaction, { filters in var filters = filters if let index = filters.firstIndex(where: { $0.id == filterId }) { switch itemId { @@ -81,13 +81,13 @@ public func toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLoc } } -public func getPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation) -> [PinnedItemId] { +func _internal_getPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation) -> [PinnedItemId] { switch location { case let .group(groupId): return transaction.getPinnedItemIds(groupId: groupId) case let .filter(filterId): var itemIds: [PinnedItemId] = [] - let _ = updateChatListFiltersInteractively(transaction: transaction, { filters in + let _ = _internal_updateChatListFiltersInteractively(transaction: transaction, { filters in if let index = filters.firstIndex(where: { $0.id == filterId }) { itemIds = filters[index].data.includePeers.pinnedPeers.map { peerId in return .peer(peerId) @@ -99,7 +99,7 @@ public func getPinnedItemIds(transaction: Transaction, location: TogglePeerChatP } } -public func reorderPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation, itemIds: [PinnedItemId]) -> Bool { +func _internal_reorderPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation, itemIds: [PinnedItemId]) -> Bool { switch location { case let .group(groupId): if transaction.getPinnedItemIds(groupId: groupId) != itemIds { @@ -111,7 +111,7 @@ public func reorderPinnedItemIds(transaction: Transaction, location: TogglePeerC } case let .filter(filterId): var result: Bool = false - updateChatListFiltersInteractively(transaction: transaction, { filters in + _internal_updateChatListFiltersInteractively(transaction: transaction, { filters in var filters = filters if let index = filters.firstIndex(where: { $0.id == filterId }) { let peerIds: [PeerId] = itemIds.map { itemId -> PeerId in diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift similarity index 99% rename from submodules/TelegramCore/Sources/UpdateCachedPeerData.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index aaba92ba02..90f1c544d3 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -124,7 +124,7 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, network: } } -public func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { +func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { return postbox.combinedView(keys: [.basicPeer(rawPeerId)]) |> mapToSignal { views -> Signal in if accountPeerId == rawPeerId { diff --git a/submodules/TelegramCore/Sources/UpdatePeerInfo.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift similarity index 94% rename from submodules/TelegramCore/Sources/UpdatePeerInfo.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift index 66b71dcd08..792c2490fe 100644 --- a/submodules/TelegramCore/Sources/UpdatePeerInfo.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift @@ -10,7 +10,7 @@ public enum UpdatePeerTitleError { case generic } -public func updatePeerTitle(account: Account, peerId: PeerId, title: String) -> Signal { +func _internal_updatePeerTitle(account: Account, peerId: PeerId, title: String) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId) { if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { @@ -58,7 +58,7 @@ public enum UpdatePeerDescriptionError { case generic } -public func updatePeerDescription(account: Account, peerId: PeerId, description: String?) -> Signal { +func _internal_updatePeerDescription(account: Account, peerId: PeerId, description: String?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId) { if (peer is TelegramChannel || peer is TelegramGroup), let inputPeer = apiInputPeer(peer) { diff --git a/submodules/TelegramCore/Sources/DeepLinkInfo.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resolve/DeepLinkInfo.swift similarity index 87% rename from submodules/TelegramCore/Sources/DeepLinkInfo.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Resolve/DeepLinkInfo.swift index c56272ad08..c517df7d6c 100644 --- a/submodules/TelegramCore/Sources/DeepLinkInfo.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resolve/DeepLinkInfo.swift @@ -10,7 +10,7 @@ public struct DeepLinkInfo { public let updateApp: Bool } -public func getDeepLinkInfo(network: Network, path: String) -> Signal { +func _internal_getDeepLinkInfo(network: Network, path: String) -> Signal { return network.request(Api.functions.help.getDeepLinkInfo(path: path)) |> retryRequest |> map { value -> DeepLinkInfo? in switch value { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resolve/TelegramEngineResolve.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resolve/TelegramEngineResolve.swift new file mode 100644 index 0000000000..909b39dda8 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resolve/TelegramEngineResolve.swift @@ -0,0 +1,16 @@ +import SwiftSignalKit +import Postbox + +public extension TelegramEngine { + final class Resolve { + private let account: Account + + init(account: Account) { + self.account = account + } + + public func getDeepLinkInfo(path: String) -> Signal { + return _internal_getDeepLinkInfo(network: self.account.network, path: path) + } + } +} diff --git a/submodules/TelegramCore/Sources/CollectCacheUsageStats.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift similarity index 98% rename from submodules/TelegramCore/Sources/CollectCacheUsageStats.swift rename to submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift index 1c3bc503de..f668e7e65f 100644 --- a/submodules/TelegramCore/Sources/CollectCacheUsageStats.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift @@ -53,7 +53,7 @@ private final class CacheUsageStatsState { var upperBound: MessageIndex? } -public func collectCacheUsageStats(account: Account, peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal { +func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal { var initialState = CacheUsageStatsState() if let peerId = peerId { initialState.lowerBound = MessageIndex.lowerBound(peerId: peerId) @@ -291,6 +291,6 @@ public func collectCacheUsageStats(account: Account, peerId: PeerId? = nil, addi } } -public func clearCachedMediaResources(account: Account, mediaResourceIds: Set) -> Signal { +func _internal_clearCachedMediaResources(account: Account, mediaResourceIds: Set) -> Signal { return account.postbox.mediaBox.removeCachedResources(mediaResourceIds) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift new file mode 100644 index 0000000000..726005cae6 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -0,0 +1,24 @@ +import SwiftSignalKit +import Postbox + +public extension TelegramEngine { + final class Resources { + private let account: Account + + init(account: Account) { + self.account = account + } + + public func preUpload(id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete: (()->Void)? = nil) { + return self.account.messageMediaPreuploadManager.add(network: self.account.network, postbox: self.account.postbox, id: id, encrypt: encrypt, tag: tag, source: source, onComplete: onComplete) + } + + public func collectCacheUsageStats(peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal { + return _internal_collectCacheUsageStats(account: self.account, peerId: peerId, additionalCachePaths: additionalCachePaths, logFilesPath: logFilesPath) + } + + public func clearCachedMediaResources(mediaResourceIds: Set) -> Signal { + return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index 36c8520316..0f1464fbf8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -346,7 +346,7 @@ func _internal_searchGifs(account: Account, query: String, nextOffset: String = return account.postbox.loadedPeerWithId(peerId) } |> mapToSignal { peer -> Signal in - return requestChatContextResults(account: account, botId: peer.id, peerId: account.peerId, query: query, offset: nextOffset) + return _internal_requestChatContextResults(account: account, botId: peer.id, peerId: account.peerId, query: query, offset: nextOffset) |> map { results -> ChatContextResultCollection? in return results?.results } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift b/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift index f6a5546d13..d4902923e1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift @@ -36,10 +36,6 @@ public final class TelegramEngine { return Stickers(account: self.account) }() - public lazy var peerManagement: PeerManagement = { - return PeerManagement(account: self.account) - }() - public lazy var localization: Localization = { return Localization(account: self.account) }() @@ -63,6 +59,14 @@ public final class TelegramEngine { public lazy var contacts: Contacts = { return Contacts(account: self.account) }() + + public lazy var resources: Resources = { + return Resources(account: self.account) + }() + + public lazy var resolve: Resolve = { + return Resolve(account: self.account) + }() } public final class TelegramEngineUnauthorized { diff --git a/submodules/TelegramCore/Sources/MediaResourceNetworkStatsTag.swift b/submodules/TelegramCore/Sources/Utils/MediaResourceNetworkStatsTag.swift similarity index 81% rename from submodules/TelegramCore/Sources/MediaResourceNetworkStatsTag.swift rename to submodules/TelegramCore/Sources/Utils/MediaResourceNetworkStatsTag.swift index 8518603109..8d008623d3 100644 --- a/submodules/TelegramCore/Sources/MediaResourceNetworkStatsTag.swift +++ b/submodules/TelegramCore/Sources/Utils/MediaResourceNetworkStatsTag.swift @@ -9,7 +9,7 @@ public enum MediaResourceStatsCategory { case call } -public final class TelegramMediaResourceFetchTag: MediaResourceFetchTag { +final class TelegramMediaResourceFetchTag: MediaResourceFetchTag { public let statsCategory: MediaResourceStatsCategory public init(statsCategory: MediaResourceStatsCategory) { diff --git a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift b/submodules/TelegramCore/Sources/Utils/UpdateMessageMedia.swift similarity index 100% rename from submodules/TelegramCore/Sources/UpdateMessageMedia.swift rename to submodules/TelegramCore/Sources/Utils/UpdateMessageMedia.swift diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 624ad218b6..7580569fdc 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -391,18 +391,18 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati ), controlSecondaryColor: UIColor(rgb: 0xffffff, alpha: 0.5), freeInputField: PresentationInputFieldTheme( - backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.5), - strokeColor: UIColor(rgb: 0xffffff, alpha: 0.5), - placeholderColor: UIColor(rgb: 0x4d4d4d), + backgroundColor: UIColor(rgb: 0x272728), + strokeColor: UIColor(rgb: 0x272728), + placeholderColor: UIColor(rgb: 0x98989e), primaryColor: UIColor(rgb: 0xffffff), - controlColor: UIColor(rgb: 0x4d4d4d) + controlColor: UIColor(rgb: 0x98989e) ), freePlainInputField: PresentationInputFieldTheme( - backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.5), - strokeColor: UIColor(rgb: 0xffffff, alpha: 0.5), - placeholderColor: UIColor(rgb: 0x4d4d4d), + backgroundColor: UIColor(rgb: 0x272728), + strokeColor: UIColor(rgb: 0x272728), + placeholderColor: UIColor(rgb: 0x98989e), primaryColor: UIColor(rgb: 0xffffff), - controlColor: UIColor(rgb: 0x4d4d4d) + controlColor: UIColor(rgb: 0x98989e) ), mediaPlaceholderColor: UIColor(rgb: 0xffffff).mixedWith(UIColor(rgb: 0x1c1c1d), alpha: 0.9), scrollIndicatorColor: UIColor(rgb: 0xffffff, alpha: 0.3), diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index 4a3ea191e3..1d1ebd05a6 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -184,12 +184,12 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme ), controlSecondaryColor: mainSecondaryTextColor?.withAlphaComponent(0.5), freeInputField: list.freeInputField.withUpdated( - backgroundColor: mainSecondaryTextColor?.withAlphaComponent(0.5), - strokeColor: mainSecondaryTextColor?.withAlphaComponent(0.5) + backgroundColor: accentColor?.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), + strokeColor: accentColor?.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12) ), freePlainInputField: list.freePlainInputField.withUpdated( - backgroundColor: mainSecondaryTextColor?.withAlphaComponent(0.5), - strokeColor: mainSecondaryTextColor?.withAlphaComponent(0.5) + backgroundColor: accentColor?.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), + strokeColor: accentColor?.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12) ), mediaPlaceholderColor: UIColor(rgb: 0xffffff).mixedWith(mainBackgroundColor ?? list.itemBlocksBackgroundColor, alpha: 0.9), pageIndicatorInactiveColor: mainSecondaryTextColor?.withAlphaComponent(0.4), @@ -634,18 +634,18 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres ), controlSecondaryColor: mainSecondaryTextColor.withAlphaComponent(0.5), freeInputField: PresentationInputFieldTheme( - backgroundColor: mainSecondaryTextColor.withAlphaComponent(0.5), - strokeColor: mainSecondaryTextColor.withAlphaComponent(0.5), - placeholderColor: UIColor(rgb: 0x4d4d4d), + backgroundColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), + strokeColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), + placeholderColor: mainSecondaryTextColor.withAlphaComponent(0.5), primaryColor: .white, - controlColor: UIColor(rgb: 0x4d4d4d) + controlColor: mainSecondaryTextColor.withAlphaComponent(0.5) ), freePlainInputField: PresentationInputFieldTheme( - backgroundColor: mainSecondaryTextColor.withAlphaComponent(0.5), - strokeColor: mainSecondaryTextColor.withAlphaComponent(0.5), - placeholderColor: UIColor(rgb: 0x4d4d4d), + backgroundColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), + strokeColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), + placeholderColor: mainSecondaryTextColor.withAlphaComponent(0.5), primaryColor: .white, - controlColor: UIColor(rgb: 0x4d4d4d) + controlColor: mainSecondaryTextColor.withAlphaComponent(0.5) ), mediaPlaceholderColor: UIColor(rgb: 0xffffff).mixedWith(mainBackgroundColor, alpha: 0.9), scrollIndicatorColor: UIColor(white: 1.0, alpha: 0.3), @@ -685,7 +685,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres unreadBadgeInactiveBackgroundColor: mainSecondaryTextColor.withAlphaComponent(0.4), unreadBadgeInactiveTextColor: additionalBackgroundColor, pinnedBadgeColor: mainSecondaryTextColor.withAlphaComponent(0.5), - pinnedSearchBarColor: mainInputColor, + pinnedSearchBarColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), regularSearchBarColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), sectionHeaderFillColor: mainBackgroundColor, sectionHeaderTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index e44f2868b6..6de22efbc3 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -663,7 +663,7 @@ public struct PresentationResourcesChat { return theme.image(PresentationResourceKey.chatInstantMessageInfoBackgroundImage.rawValue, { theme in return generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.chat.message.mediaDateAndStatusFillColor.cgColor) + context.setFillColor(theme.chat.message.mediaDateAndStatusFillColor.withAlphaComponent(0.3).cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) })?.stretchableImage(withLeftCapWidth: 12, topCapHeight: 12) }) diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 34ea3cd855..ad8e0acac8 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -157,7 +157,7 @@ public final class AccountContextImpl: AccountContext { public let cachedGroupCallContexts: AccountGroupCallContextCache - public init(sharedContext: SharedAccountContextImpl, account: Account, /*tonContext: StoredTonContext?, */limitsConfiguration: LimitsConfiguration, contentSettings: ContentSettings, appConfiguration: AppConfiguration, temp: Bool = false) + public init(sharedContext: SharedAccountContextImpl, account: Account, limitsConfiguration: LimitsConfiguration, contentSettings: ContentSettings, appConfiguration: AppConfiguration, temp: Bool = false) { self.sharedContextImpl = sharedContext self.account = account @@ -166,7 +166,7 @@ public final class AccountContextImpl: AccountContext { self.downloadedMediaStoreManager = DownloadedMediaStoreManagerImpl(postbox: account.postbox, accountManager: sharedContext.accountManager) if let locationManager = self.sharedContextImpl.locationManager { - self.liveLocationManager = LiveLocationManagerImpl(account: account, locationManager: locationManager, inForeground: sharedContext.applicationBindings.applicationInForeground) + self.liveLocationManager = LiveLocationManagerImpl(engine: self.engine, account: account, locationManager: locationManager, inForeground: sharedContext.applicationBindings.applicationInForeground) } else { self.liveLocationManager = nil } diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift b/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift index 3df08bd4d1..8d1675a6a4 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift @@ -705,7 +705,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail } |> mapToSignal { resource -> Signal in if let resource = resource { - return uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: nil, resource: resource) |> map(Optional.init) + return TelegramEngineUnauthorized(account: account).auth.uploadedPeerVideo(resource: resource) |> map(Optional.init) } else { return .single(nil) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 8f5ca4753c..85de671d29 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -566,6 +566,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var openMessageByAction: Bool = false for media in message.media { + if let file = media as? TelegramMediaFile, file.isInstantVideo { + if strongSelf.chatDisplayNode.isInputViewFocused { + strongSelf.returnInputViewFocus = true + strongSelf.chatDisplayNode.dismissInput() + } + } if let action = media as? TelegramMediaAction { switch action.action { case .pinnedMessageUpdated: @@ -615,7 +621,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G statusController?.dismiss() } strongSelf.present(statusController, in: .window(.root)) - strongSelf.createVoiceChatDisposable.set((createGroupCall(account: strongSelf.context.account, peerId: message.id.peerId, title: nil, scheduleDate: nil) + strongSelf.createVoiceChatDisposable.set((strongSelf.context.engine.calls.createGroupCall(peerId: message.id.peerId, title: nil, scheduleDate: nil) |> deliverOnMainQueue).start(next: { [weak self] info in guard let strongSelf = self else { return @@ -4192,6 +4198,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G override public func loadDisplayNode() { self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, backgroundNode: self.chatBackgroundNode, controller: self) + if let currentItem = self.tempVoicePlaylistCurrentItem { + self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem) + } + self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode in guard let strongSelf = self else { return @@ -7032,6 +7042,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + private var returnInputViewFocus = false + override public func viewDidAppear(_ animated: Bool) { #if DEBUG if #available(iOSApplicationExtension 12.0, iOS 12.0, *) { @@ -7110,16 +7122,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.voicePlaylistDidEndTimestamp = CACurrentMediaTime() raiseToListen.activateBasedOnProximity(delay: 0.0) } + + if strongSelf.returnInputViewFocus { + strongSelf.returnInputViewFocus = false + strongSelf.chatDisplayNode.ensureInputViewFocused() + } } self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { return } - 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, nil), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil) - } - } + + strongSelf.chatDisplayNode.historyNode.voicePlaylistItemChanged(previousItem, currentItem) +// if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId { +// self.controllerInteraction?.currentlyPlayingMessageId = currentItem.messageId +// if 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, nil), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil) +// } +// } +// } } } @@ -7153,7 +7175,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if case let .peer(peerId) = self.chatLocation { - let _ = checkPeerChatServiceActions(postbox: self.context.account.postbox, peerId: peerId).start() + let _ = self.context.engine.peers.checkPeerChatServiceActions(peerId: peerId).start() } if self.chatDisplayNode.frameForInputActionButton() != nil { @@ -7280,7 +7302,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } strongSelf.peekTimerDisposable.set( - (joinChatInteractively(with: peekData.linkData, account: strongSelf.context.account) + (strongSelf.context.engine.peers.joinChatInteractively(with: peekData.linkData) |> deliverOnMainQueue).start(next: { peerId in guard let strongSelf = self else { return @@ -8314,7 +8336,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let peerId = peer.id - let _ = (collectCacheUsageStats(account: strongSelf.context.account, peerId: peer.id) + let _ = (strongSelf.context.engine.resources.collectCacheUsageStats(peerId: peer.id) |> deliverOnMainQueue).start(next: { [weak self, weak controller] result in controller?.dismiss() @@ -8433,7 +8455,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - var signal = clearCachedMediaResources(account: strongSelf.context.account, mediaResourceIds: clearResourceIds) + var signal = strongSelf.context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -9866,10 +9888,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false) { + if !canSendMessagesToChat(self.presentationInterfaceState) { + return + } + let peerId = self.chatLocation.peerId - - if let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, hideVia: hideVia), canSendMessagesToChat(self.presentationInterfaceState) { - let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId + + let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId + + if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, results: results, result: result, replyToMessageId: replyMessageId, hideVia: hideVia, silentPosting: silentPosting) { self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in @@ -9891,9 +9918,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } }, nil) - var messages = [message.withUpdatedReplyToMessageId(replyMessageId)] - messages = self.transformEnqueueMessages(messages, silentPosting: silentPosting) - self.sendMessages(messages) } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 26c42c8961..99eaf5d0e5 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -99,7 +99,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { selection = .none } - groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false))) } else { let selection: ChatHistoryMessageSelection if let selectedMessages = selectedMessages { @@ -107,7 +107,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { selection = .none } - entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) } } else { let selection: ChatHistoryMessageSelection @@ -116,7 +116,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { selection = .none } - entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) } } @@ -167,11 +167,11 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, if messages.count > 1, let groupInfo = messages[0].groupInfo { var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = [] for message in messages { - groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id]))) + groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false))) } entries.insert(.MessageGroupEntry(groupInfo, groupMessages, presentationData), at: 0) } else { - entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id])), at: 0) + entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id], isPlaying: false)), at: 0) } let replyCount = view.entries.isEmpty ? 0 : 1 diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift index a3ffe24b0a..95ae7fa8f8 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift @@ -17,12 +17,14 @@ public struct ChatMessageEntryAttributes: Equatable { let isContact: Bool let contentTypeHint: ChatMessageEntryContentType let updatingMedia: ChatUpdatingMessageMedia? + let isPlaying: Bool - init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?) { + init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?, isPlaying: Bool) { self.rank = rank self.isContact = isContact self.contentTypeHint = contentTypeHint self.updatingMedia = updatingMedia + self.isPlaying = isPlaying } public init() { @@ -30,6 +32,7 @@ public struct ChatMessageEntryAttributes: Equatable { self.isContact = false self.contentTypeHint = .generic self.updatingMedia = nil + self.isPlaying = false } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 2750e3eaa8..74be953d83 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -17,6 +17,7 @@ import AppBundle import ListMessageItem import AccountContext import ChatInterfaceState +import ChatListUI extension ChatReplyThreadMessage { var effectiveTopId: MessageId { @@ -30,69 +31,6 @@ struct ChatTopVisibleMessageRange: Equatable { var isLast: Bool } -private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { - private let selectionGestureActivationThreshold: CGFloat = 5.0 - - var recognized: Bool? = nil - var initialLocation: CGPoint = CGPoint() - - var shouldBegin: (() -> Bool)? - - override init(target: Any?, action: Selector?) { - super.init(target: target, action: action) - - self.minimumNumberOfTouches = 2 - self.maximumNumberOfTouches = 2 - } - - override func reset() { - super.reset() - - self.recognized = nil - } - - override func touchesBegan(_ touches: Set, with event: UIEvent) { - super.touchesBegan(touches, with: event) - - if let shouldBegin = self.shouldBegin, !shouldBegin() { - self.state = .failed - } else { - let touch = touches.first! - self.initialLocation = touch.location(in: self.view) - } - } - - - override func touchesMoved(_ touches: Set, with event: UIEvent) { - let location = touches.first!.location(in: self.view) - let translation = location.offsetBy(dx: -self.initialLocation.x, dy: -self.initialLocation.y) - - let touchesArray = Array(touches) - if self.recognized == nil, touchesArray.count == 2 { - if let firstTouch = touchesArray.first, let secondTouch = touchesArray.last { - let firstLocation = firstTouch.location(in: self.view) - let secondLocation = secondTouch.location(in: self.view) - - func distance(_ v1: CGPoint, _ v2: CGPoint) -> CGFloat { - let dx = v1.x - v2.x - let dy = v1.y - v2.y - return sqrt(dx * dx + dy * dy) - } - if distance(firstLocation, secondLocation) > 200.0 { - self.state = .failed - } - } - if self.state != .failed && (abs(translation.y) >= selectionGestureActivationThreshold) { - self.recognized = true - } - } - - if let recognized = self.recognized, recognized { - super.touchesMoved(touches, with: event) - } - } -} - private let historyMessageCount: Int = 90 public enum ChatHistoryListDisplayHeaders { @@ -373,7 +311,7 @@ private final class ChatHistoryTransactionOpaqueState { } } -private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], subject: ChatControllerSubject?) -> ChatMessageItemAssociatedData { +private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?) -> ChatMessageItemAssociatedData { var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var contactsPeerIds: Set = Set() var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown @@ -422,8 +360,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist } } - let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers) - return associatedData + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId) } private extension ChatHistoryLocationInput { @@ -590,7 +527,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } } - + + private let currentlyPlayingMessageIdPromise = ValuePromise(nil) + private var appliedPlayingMessageId: MessageIndex? = nil + private(set) var isScrollAtBottomPosition = false public var isScrollAtBottomPositionUpdated: (() -> Void)? @@ -894,8 +834,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.pendingRemovedMessagesPromise.get(), animatedEmojiStickers, customChannelDiscussionReadState, - customThreadOutgoingReadState - ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState in + customThreadOutgoingReadState, + self.currentlyPlayingMessageIdPromise.get() + ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId in func applyHole() { Queue.mainQueue().async { if let strongSelf = self { @@ -978,7 +919,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { reverse = reverseValue } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId) let filteredEntries = chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, pendingRemovedMessages: pendingRemovedMessages, associatedData: associatedData, updatingMedia: updatingMedia, customChannelDiscussionReadState: customChannelDiscussionReadState, customThreadOutgoingReadState: customThreadOutgoingReadState) let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0 @@ -993,7 +934,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } assert(update.1 >= previousVersion) } - + if scrollPosition == nil, let originalScrollPosition = originalScrollPosition { switch originalScrollPosition { case let .index(index, position, _, _, highlight): @@ -1032,12 +973,22 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } } - let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, initialData: initialData?.initialData, keyboardButtonsMessage: view.topTaggedMessages.first, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode()) + + var scrollAnimationCurve: ListViewAnimationCurve? = nil + if let strongSelf = self, strongSelf.appliedPlayingMessageId != currentlyPlayingMessageId, let currentlyPlayingMessageId = currentlyPlayingMessageId { + updatedScrollPosition = .index(index: .message(currentlyPlayingMessageId), position: .center(.bottom), directionHint: .Down, animated: true, highlight: true) + scrollAnimationCurve = .Spring(duration: 0.4) + } + + let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: view.topTaggedMessages.first, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode()) let mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, transition: rawTransition) Queue.mainQueue().async { guard let strongSelf = self else { return } + if strongSelf.appliedPlayingMessageId != currentlyPlayingMessageId { + strongSelf.appliedPlayingMessageId = currentlyPlayingMessageId + } strongSelf.enqueueHistoryViewTransition(mappedTransition) } } @@ -2352,6 +2303,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.selectionScrollDisplayLink?.isPaused = false } + + func voicePlaylistItemChanged(_ previousItem: SharedMediaPlaylistItem?, _ currentItem: SharedMediaPlaylistItem?) -> Void { + if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId { + self.currentlyPlayingMessageIdPromise.set(currentItem.messageIndex) + } else { + self.currentlyPlayingMessageIdPromise.set(nil) + } + } + private var currentSendAnimationCorrelationId: Int64? func setCurrentSendAnimationCorrelationId(_ value: Int64?) { self.currentSendAnimationCorrelationId = value diff --git a/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift b/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift index 263a553cbc..0387adf15b 100644 --- a/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift +++ b/submodules/TelegramUI/Sources/ChatInstantVideoMessageDurationNode.swift @@ -5,7 +5,7 @@ import SwiftSignalKit import Display import UniversalMediaPlayer -private let textFont = Font.regular(11.0) +private let textFont = Font.with(size: 11.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) private struct ChatInstantVideoMessageDurationNodeState: Equatable { let hours: Int32? diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 8684fe0875..3863d95b40 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -149,7 +149,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee signal = .single({ _ in return .hashtags([]) }) } - let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = recentlyUsedHashtags(postbox: context.account.postbox) + let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags() |> map { hashtags -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in let normalizedQuery = query.lowercased() var result: [String] = [] @@ -279,7 +279,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee |> castError(ChatContextQueryError.self) |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let contextResults = requestChatContextResults(account: context.account, botId: user.id, peerId: chatPeer.id, query: query, location: context.sharedContext.locationManager.flatMap { locationManager -> Signal<(Double, Double)?, NoError> in + let contextResults = context.engine.messages.requestChatContextResults(botId: user.id, peerId: chatPeer.id, query: query, location: context.sharedContext.locationManager.flatMap { locationManager -> Signal<(Double, Double)?, NoError> in return `deferred` { Queue.mainQueue().async { requestBotLocationStatus(user.id) diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index e5ca7a3c71..d01b40ed2e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -520,8 +520,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else if file.isInstantVideo { + let displaySize = CGSize(width: 212.0, height: 212.0) let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) - let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload) + let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, displaySize, displaySize, 0.0, .bubble, automaticDownload) initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight contentInstantVideoSizeAndApply = (videoLayout, apply) } else if file.isVideo { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 034af0b2fe..eb5199ca91 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -710,20 +710,19 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode func animateFromMicInput(micInputNode: UIView, transition: CombinedTransition) -> ContextExtractedContentContainingNode? { for contentNode in self.contentNodes { if let contentNode = contentNode as? ChatMessageFileBubbleContentNode { - if let statusContainerNode = contentNode.interactiveFileNode.statusContainerNode { - let scale = statusContainerNode.contentRect.height / 100.0 - micInputNode.transform = CGAffineTransform(scaleX: scale, y: scale) - micInputNode.center = CGPoint(x: statusContainerNode.contentRect.midX, y: statusContainerNode.contentRect.midY) - statusContainerNode.contentNode.view.addSubview(micInputNode) + let statusContainerNode = contentNode.interactiveFileNode.statusContainerNode + let scale = statusContainerNode.contentRect.height / 100.0 + micInputNode.transform = CGAffineTransform(scaleX: scale, y: scale) + micInputNode.center = CGPoint(x: statusContainerNode.contentRect.midX, y: statusContainerNode.contentRect.midY) + statusContainerNode.contentNode.view.addSubview(micInputNode) - transition.horizontal.updateAlpha(layer: micInputNode.layer, alpha: 0.0, completion: { [weak micInputNode] _ in - micInputNode?.removeFromSuperview() - }) + transition.horizontal.updateAlpha(layer: micInputNode.layer, alpha: 0.0, completion: { [weak micInputNode] _ in + micInputNode?.removeFromSuperview() + }) - transition.horizontal.animateTransformScale(node: statusContainerNode.contentNode, from: 1.0 / scale) + transition.horizontal.animateTransformScale(node: statusContainerNode.contentNode, from: 1.0 / scale) - return statusContainerNode - } + return statusContainerNode } } return nil diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 473d47fe08..9dc3b1afb5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -31,8 +31,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? + private var appliedParams: ListViewItemLayoutParams? private var appliedItem: ChatMessageItem? private var appliedForwardInfo: (Peer?, String?)? + private var appliedHasAvatar = false + private var appliedCurrentlyPlaying = false + private var appliedAutomaticDownload = false private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardBackgroundNode: ASImageNode? @@ -217,6 +221,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let currentItem = self.appliedItem let currentForwardInfo = self.appliedForwardInfo + let currentPlaying = self.appliedCurrentlyPlaying return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil) @@ -318,7 +323,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { deliveryFailedInset += 24.0 } - let displaySize = layoutConstants.instantVideo.dimensions + var isPlaying = false + var displaySize = layoutConstants.instantVideo.dimensions + let maximumDisplaySize = CGSize(width: params.width - 20.0, height: params.width - 20.0) + if item.associatedData.currentlyPlayingMessageId == item.message.index { + isPlaying = true + displaySize = maximumDisplaySize + } var automaticDownload = true for media in item.message.media { @@ -332,7 +343,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { isReplyThread = true } - let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload) + let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, isPlaying ? 1.0 : 0.0, .free, automaticDownload) let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize) @@ -495,30 +506,40 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { return (ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets), { [weak self] animation, _ in if let strongSelf = self { - strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize) - strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize) - strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize) - strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize) - - strongSelf.appliedItem = item - strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature) - - strongSelf.updateAccessibilityData(accessibilityData) - let transition: ContainedViewLayoutTransition if animation.isAnimated { transition = .animated(duration: 0.2, curve: .spring) } else { transition = .immediate } - strongSelf.interactiveVideoNode.frame = videoFrame + + strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize) + strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize) + strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layoutSize) + strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize) + + strongSelf.appliedParams = params + strongSelf.appliedItem = item + strongSelf.appliedHasAvatar = hasAvatar + strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature) + strongSelf.appliedCurrentlyPlaying = isPlaying + + strongSelf.updateAccessibilityData(accessibilityData) + + + let videoLayoutData: ChatMessageInstantVideoItemLayoutData if incoming { videoLayoutData = .constrained(left: 0.0, right: max(0.0, availableContentWidth - videoFrame.width)) } else { videoLayoutData = .constrained(left: max(0.0, availableContentWidth - videoFrame.width), right: 0.0) } - videoApply(videoLayoutData, transition) + + if currentItem != nil && currentPlaying != isPlaying { + } else { + strongSelf.interactiveVideoNode.frame = videoFrame + videoApply(videoLayoutData, transition) + } strongSelf.contextSourceNode.contentRect = videoFrame strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect @@ -1024,4 +1045,75 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } + + override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { + super.animateFrameTransition(progress, currentValue) + + guard let item = self.appliedItem, let params = self.appliedParams, progress > 0.0, let (initialHeight, targetHeight) = self.apparentHeightTransition else { + return + } + + let layoutConstants = chatMessageItemLayoutConstants(self.layoutConstants, params: params, presentationData: item.presentationData) + let incoming = item.message.effectivelyIncoming(item.context.account.peerId) + + var isReplyThread = false + if case .replyThread = item.chatLocation { + isReplyThread = true + } + + var isPlaying = false + var displaySize = layoutConstants.instantVideo.dimensions + let maximumDisplaySize = CGSize(width: params.width - 20.0, height: params.width - 20.0) + if item.associatedData.currentlyPlayingMessageId == item.message.index { + isPlaying = true + } + + let avatarInset: CGFloat + if self.appliedHasAvatar { + avatarInset = layoutConstants.avatarDiameter + } else { + avatarInset = 0.0 + } + + let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) + var deliveryFailedInset: CGFloat = 0.0 + if isFailed { + deliveryFailedInset += 24.0 + } + + let makeVideoLayout = self.interactiveVideoNode.asyncLayout() + + let initialSize: CGSize + let targetSize: CGSize + let animationProgress: CGFloat = (currentValue - initialHeight) / (targetHeight - initialHeight) + let scaleProgress: CGFloat + if currentValue < targetHeight { + initialSize = displaySize + targetSize = maximumDisplaySize + scaleProgress = animationProgress + } else if currentValue > targetHeight { + initialSize = maximumDisplaySize + targetSize = displaySize + scaleProgress = 1.0 - animationProgress + } else { + initialSize = isPlaying ? maximumDisplaySize : displaySize + targetSize = initialSize + scaleProgress = isPlaying ? 1.0 : 0.0 + } + displaySize = CGSize(width: initialSize.width + (targetSize.width - initialSize.width) * animationProgress, height: initialSize.height + (targetSize.height - initialSize.height) * animationProgress) + + let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned) && !isReplyThread, isItemEdited: false), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, maximumDisplaySize, scaleProgress, .free, self.appliedAutomaticDownload) + + let availableContentWidth = params.width - params.leftInset - params.rightInset - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left + let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize) + self.interactiveVideoNode.frame = videoFrame + + let videoLayoutData: ChatMessageInstantVideoItemLayoutData + if incoming { + videoLayoutData = .constrained(left: 0.0, right: max(0.0, availableContentWidth - videoFrame.width)) + } else { + videoLayoutData = .constrained(left: max(0.0, availableContentWidth - videoFrame.width), right: 0.0) + } + videoApply(videoLayoutData, .immediate) + } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 625ab8e1a8..6cdc96120a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -39,9 +39,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private let consumableContentNode: ASImageNode private var iconNode: TransformImageNode? - private(set) var statusContainerNode: ContextExtractedContentContainingNode? + let statusContainerNode: ContextExtractedContentContainingNode private var statusNode: SemanticStatusNode? - private var playbackAudioLevelView: VoiceBlobView? + private var playbackAudioLevelNode: VoiceBlobNode? private var streamingStatusNode: SemanticStatusNode? private var tapRecognizer: UITapGestureRecognizer? @@ -73,7 +73,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { guard self.visibility != oldValue else { return } if !self.visibility { - self.playbackAudioLevelView?.stopAnimating() + self.playbackAudioLevelNode?.stopAnimating() } } } @@ -140,9 +140,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.addSubnode(self.descriptionNode) self.addSubnode(self.fetchingTextNode) self.addSubnode(self.fetchingCompactTextNode) - if let statusContainerNode = self.statusContainerNode { - self.addSubnode(statusContainerNode) - } + self.addSubnode(self.statusContainerNode) } deinit { @@ -673,7 +671,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { return } strongSelf.inputAudioLevel = CGFloat(value) - strongSelf.playbackAudioLevelView?.updateLevel(CGFloat(value)) + strongSelf.playbackAudioLevelNode?.updateLevel(CGFloat(value)) })) } @@ -692,19 +690,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { strongSelf.statusNode?.displaysAsynchronously = !presentationData.isPreview strongSelf.statusNode?.frame = CGRect(origin: CGPoint(), size: progressFrame.size) - strongSelf.statusContainerNode?.frame = progressFrame - strongSelf.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) - strongSelf.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) + strongSelf.statusContainerNode.frame = progressFrame + strongSelf.statusContainerNode.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) + strongSelf.statusContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) - strongSelf.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0) + strongSelf.playbackAudioLevelNode?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0) strongSelf.progressFrame = progressFrame strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame strongSelf.fileIconImage = fileIconImage - strongSelf.statusContainerNode?.frame = progressFrame - strongSelf.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) - strongSelf.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) - if let updatedFetchControls = updatedFetchControls { let _ = strongSelf.fetchControls.swap(updatedFetchControls) if automaticDownload { @@ -954,26 +948,27 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor, image: image, overlayForegroundNodeColor: presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor) self.statusNode = statusNode - self.statusContainerNode?.contentNode.insertSubnode(statusNode, at: 0) - self.statusContainerNode?.frame = progressFrame - self.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) - self.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) + self.statusContainerNode.contentNode.insertSubnode(statusNode, at: 0) + self.statusContainerNode.frame = progressFrame + self.statusContainerNode.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size) + self.statusContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) statusNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size) } else if let statusNode = self.statusNode { statusNode.backgroundNodeColor = backgroundNodeColor } - if state != .none && isVoice && self.playbackAudioLevelView == nil && false { + if state != .none && isVoice && self.playbackAudioLevelNode == nil { let blobFrame = progressFrame.insetBy(dx: -12.0, dy: -12.0) - let playbackAudioLevelView = VoiceBlobView( - frame: blobFrame, + let playbackAudioLevelNode = VoiceBlobNode( maxLevel: 0.3, smallBlobRange: (0, 0), mediumBlobRange: (0.7, 0.8), bigBlobRange: (0.8, 0.9) ) - self.playbackAudioLevelView = playbackAudioLevelView - self.view.addSubview(playbackAudioLevelView) + playbackAudioLevelNode.isUserInteractionEnabled = false + playbackAudioLevelNode.frame = blobFrame + self.playbackAudioLevelNode = playbackAudioLevelNode + self.insertSubnode(playbackAudioLevelNode, belowSubnode: self.statusContainerNode) let maskRect = CGRect(origin: .zero, size: blobFrame.size) let playbackMaskLayer = CAShapeLayer() @@ -983,9 +978,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22)) maskPath.append(UIBezierPath(rect: maskRect)) playbackMaskLayer.path = maskPath.cgPath - playbackAudioLevelView.layer.mask = playbackMaskLayer + playbackAudioLevelNode.layer.mask = playbackMaskLayer } - self.playbackAudioLevelView?.setColor(presentationData.theme.theme.chat.inputPanel.actionControlFillColor) + self.playbackAudioLevelNode?.setColor(messageTheme.mediaActiveControlColor) if streamingState != .none && self.streamingStatusNode == nil { let streamingStatusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor) @@ -1012,9 +1007,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { switch state { case .pause: - self.playbackAudioLevelView?.startAnimating() + self.playbackAudioLevelNode?.startAnimating() default: - self.playbackAudioLevelView?.stopAnimating() + self.playbackAudioLevelNode?.stopAnimating() } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 43a8dbb043..b263ed4117 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -83,6 +83,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } + private var animating = false + override init() { self.secretVideoPlaceholderBackground = ASImageNode() self.secretVideoPlaceholderBackground.isLayerBacked = true @@ -130,7 +132,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.view.addGestureRecognizer(recognizer) } - func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) { + func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) { let previousFile = self.media let currentItem = self.item @@ -138,7 +140,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let makeDateAndStatusLayout = self.dateAndStatusNode.asyncLayout() - return { item, width, displaySize, statusDisplayType, automaticDownload in + return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusDisplayType, automaticDownload in var secretVideoPlaceholderBackgroundImage: UIImage? var updatedInfoBackgroundImage: UIImage? var updatedMuteIconImage: UIImage? @@ -163,7 +165,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { secretVideoPlaceholderBackgroundImage = PresentationResourcesChat.chatInstantVideoBackgroundImage(theme.theme, wallpaper: !theme.wallpaper.isEmpty) } - let imageSize = displaySize + let imageSize = maximumDisplaySize + let imageScale = displaySize.width / maximumDisplaySize.width let updatedMessageId = item.message.id != currentItem?.message.id @@ -294,20 +297,24 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring) - var contentSize = imageSize + var displayVideoFrame = videoFrame + displayVideoFrame.size.width *= imageScale + displayVideoFrame.size.height *= imageScale + + var contentSize = displayVideoFrame.size var dateAndStatusOverflow = false - if case .bubble = statusDisplayType, videoFrame.maxX + dateAndStatusSize.width > width { + if case .bubble = statusDisplayType, displayVideoFrame.maxX + dateAndStatusSize.width > width { contentSize.height += dateAndStatusSize.height + 2.0 contentSize.width = max(contentSize.width, dateAndStatusSize.width) dateAndStatusOverflow = true } - let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floor(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width))) + let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floorToScreenPixels(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width))) return (result, { [weak self] layoutData, transition in if let strongSelf = self { strongSelf.item = item - strongSelf.videoFrame = videoFrame + strongSelf.videoFrame = displayVideoFrame strongSelf.secretProgressIcon = secretProgressIcon strongSelf.automaticDownload = automaticDownload @@ -327,10 +334,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let infoBackgroundImage = strongSelf.infoBackgroundNode.image, let muteImage = strongSelf.muteIconNode.image { let infoWidth = muteImage.size.width - let infoBackgroundFrame = CGRect(origin: CGPoint(x: floor(videoFrame.minX + (videoFrame.size.width - infoWidth) / 2.0), y: videoFrame.maxY - infoBackgroundImage.size.height - 8.0), size: CGSize(width: infoWidth, height: infoBackgroundImage.size.height)) - transition.updateFrame(node: strongSelf.infoBackgroundNode, frame: infoBackgroundFrame) + let infoBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(displayVideoFrame.minX + (displayVideoFrame.size.width - infoWidth) / 2.0), y: displayVideoFrame.maxY - infoBackgroundImage.size.height - 8.0), size: CGSize(width: infoWidth, height: infoBackgroundImage.size.height)) + strongSelf.infoBackgroundNode.frame = infoBackgroundFrame let muteIconFrame = CGRect(origin: CGPoint(x: infoBackgroundFrame.width - muteImage.size.width, y: 0.0), size: muteImage.size) - transition.updateFrame(node: strongSelf.muteIconNode, frame: muteIconFrame) + strongSelf.muteIconNode.frame = muteIconFrame } if let updatedFile = updatedFile, updatedMedia { @@ -340,21 +347,21 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { strongSelf.fetchedThumbnailDisposable.set(nil) } } - + dateAndStatusApply(false) switch layoutData { case let .unconstrained(width): let dateAndStatusOrigin: CGPoint if dateAndStatusOverflow { - dateAndStatusOrigin = CGPoint(x: videoFrame.minX - 4.0, y: videoFrame.maxY + 2.0) + dateAndStatusOrigin = CGPoint(x: displayVideoFrame.minX - 4.0, y: displayVideoFrame.maxY + 2.0) } else { - dateAndStatusOrigin = CGPoint(x: min(floor(videoFrame.midX) + 55.0, width - dateAndStatusSize.width - 4.0), y: videoFrame.height - dateAndStatusSize.height) + dateAndStatusOrigin = CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, width - dateAndStatusSize.width - 4.0), y: displayVideoFrame.height - dateAndStatusSize.height) } strongSelf.dateAndStatusNode.frame = CGRect(origin: dateAndStatusOrigin, size: dateAndStatusSize) case let .constrained(_, right): - strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, videoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: videoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) + strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, displayVideoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: displayVideoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) } - + var updatedPlayerStatusSignal: Signal? if let telegramFile = updatedFile { if updatedMedia { @@ -471,7 +478,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } if let durationNode = strongSelf.durationNode { - durationNode.frame = CGRect(origin: CGPoint(x: videoFrame.midX - 56.0, y: videoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0)) + durationNode.frame = CGRect(origin: CGPoint(x: displayVideoFrame.midX - 56.0 - 25.0 * scaleProgress, y: displayVideoFrame.maxY - 18.0), size: CGSize(width: 1.0, height: 1.0)) durationNode.isSeen = !notConsumed let size = durationNode.size if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 { @@ -481,12 +488,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } if let videoNode = strongSelf.videoNode { - videoNode.frame = videoFrame + videoNode.bounds = CGRect(origin: CGPoint(), size: videoFrame.size) + videoNode.transform = CATransform3DMakeScale(imageScale, imageScale, 1.0) + videoNode.position = displayVideoFrame.center videoNode.updateLayout(size: arguments.boundingSize, transition: .immediate) } - strongSelf.secretVideoPlaceholderBackground.frame = videoFrame + strongSelf.secretVideoPlaceholderBackground.frame = displayVideoFrame - let placeholderFrame = videoFrame.insetBy(dx: 2.0, dy: 2.0) + let placeholderFrame = displayVideoFrame.insetBy(dx: 2.0, dy: 2.0) strongSelf.secretVideoPlaceholder.frame = placeholderFrame let makeSecretPlaceholderLayout = strongSelf.secretVideoPlaceholder.asyncLayout() let arguments = TransformImageArguments(corners: ImageCorners(radius: placeholderFrame.size.width / 2.0), imageSize: placeholderFrame.size, boundingSize: placeholderFrame.size, intrinsicInsets: UIEdgeInsets()) @@ -599,7 +608,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if self.statusNode == nil { let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor) self.isUserInteractionEnabled = false - statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floor((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floor((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) + statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floorToScreenPixels((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floorToScreenPixels((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) self.statusNode = statusNode self.addSubnode(statusNode) } @@ -671,7 +680,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let current = self.playbackStatusNode { playbackStatusNode = current } else { - playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.8)) + playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.6)) self.addSubnode(playbackStatusNode) self.playbackStatusNode = playbackStatusNode } @@ -800,16 +809,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { return nil } - static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) { + static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ maximumDisplaySize: CGSize, _ scaleProgress: CGFloat, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) { let makeLayout = node?.asyncLayout() - return { item, width, displaySize, statusType, automaticDownload in + return { item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload in var createdNode: ChatMessageInteractiveInstantVideoNode? let sizeAndApplyLayout: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) if let makeLayout = makeLayout { - sizeAndApplyLayout = makeLayout(item, width, displaySize, statusType, automaticDownload) + sizeAndApplyLayout = makeLayout(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload) } else { let node = ChatMessageInteractiveInstantVideoNode() - sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, statusType, automaticDownload) + sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, maximumDisplaySize, scaleProgress, statusType, automaticDownload) createdNode = node } return (sizeAndApplyLayout.0, { [weak node] layoutData, transition in diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 5c0076a5bb..b24da72856 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -168,8 +168,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.InviteLink_ContextRevoke, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - let _ = (revokePeerExportedInvitation(account: strongSelf.context.account, peerId: peer.id, link: invite.link) - + let _ = (strongSelf.context.engine.peers.revokePeerExportedInvitation(peerId: peer.id, link: invite.link) |> deliverOnMainQueue).start(completed: { [weak self] in self?.eventLogContext.reload() }) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index cbe30e00d4..fb249f2872 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -175,6 +175,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe }, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in + }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { [weak self] peer, message, _ in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/CreateChannelController.swift b/submodules/TelegramUI/Sources/CreateChannelController.swift index 328d02ee96..becff63fc7 100644 --- a/submodules/TelegramUI/Sources/CreateChannelController.swift +++ b/submodules/TelegramUI/Sources/CreateChannelController.swift @@ -273,7 +273,7 @@ public func createChannelController(context: AccountContext) -> ViewController { return $0.avatar } if let _ = updatingAvatar { - let _ = updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in + let _ = context.engine.peers.updatePeerPhoto(peerId: peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) }).start() } @@ -329,7 +329,7 @@ public func createChannelController(context: AccountContext) -> ViewController { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil) - uploadedAvatar.set(uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource)) + uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: resource)) uploadedVideoAvatar = nil updateState { current in var current = current @@ -363,7 +363,7 @@ public func createChannelController(context: AccountContext) -> ViewController { return nil } } - let uploadInterface = LegacyLiveUploadInterface(account: context.account) + let uploadInterface = LegacyLiveUploadInterface(context: context) let signal: SSignal if let asset = asset as? AVAsset { signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)! @@ -427,7 +427,7 @@ public func createChannelController(context: AccountContext) -> ViewController { } } - uploadedAvatar.set(uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: photoResource)) + uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: photoResource)) let promise = Promise() promise.set(signal @@ -436,7 +436,7 @@ public func createChannelController(context: AccountContext) -> ViewController { } |> mapToSignal { resource -> Signal in if let resource = resource { - return uploadedPeerVideo(postbox: context.account.postbox, network: context.account.network, messageMediaPreuploadManager: context.account.messageMediaPreuploadManager, resource: resource) |> map(Optional.init) + return context.engine.peers.uploadedPeerVideo(resource: resource) |> map(Optional.init) } else { return .single(nil) } diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index b8b8f38f43..d5294d8722 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -482,7 +482,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] return $0.avatar } if let _ = updatingAvatar { - return updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) }) |> ignoreValues @@ -576,7 +576,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil) - uploadedAvatar.set(uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource)) + uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: resource)) uploadedVideoAvatar = nil updateState { current in var current = current @@ -611,7 +611,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] return nil } } - let uploadInterface = LegacyLiveUploadInterface(account: context.account) + let uploadInterface = LegacyLiveUploadInterface(context: context) let signal: SSignal if let asset = asset as? AVAsset { signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)! @@ -675,7 +675,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } } - uploadedAvatar.set(uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: photoResource)) + uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: photoResource)) let promise = Promise() promise.set(signal @@ -684,7 +684,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } |> mapToSignal { resource -> Signal in if let resource = resource { - return uploadedPeerVideo(postbox: context.account.postbox, network: context.account.network, messageMediaPreuploadManager: context.account.messageMediaPreuploadManager, resource: resource) |> map(Optional.init) + return context.engine.peers.uploadedPeerVideo(resource: resource) |> map(Optional.init) } else { return .single(nil) } diff --git a/submodules/TelegramUI/Sources/GifPaneSearchContentNode.swift b/submodules/TelegramUI/Sources/GifPaneSearchContentNode.swift index d7de6f7637..6c79d3ebfc 100644 --- a/submodules/TelegramUI/Sources/GifPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Sources/GifPaneSearchContentNode.swift @@ -46,7 +46,7 @@ func paneGifSearchForQuery(context: AccountContext, query: String, offset: Strin } |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?, Bool, Bool), NoError> in if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let results = requestContextResults(account: context.account, botId: user.id, query: query, peerId: context.account.peerId, offset: offset ?? "", incompleteResults: incompleteResults, staleCachedResults: staleCachedResults, limit: 1) + let results = requestContextResults(context: context, botId: user.id, query: query, peerId: context.account.peerId, offset: offset ?? "", incompleteResults: incompleteResults, staleCachedResults: staleCachedResults, limit: 1) |> map { results -> (ChatPresentationInputQueryResult?, Bool, Bool) in return (.contextRequestResult(user, results?.results), results != nil, results?.isStale ?? false) } diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index c97dfa1c68..13d813efca 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -141,7 +141,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { } }, removeRequested: { [weak self] text in if let strongSelf = self { - let _ = removeRecentlyUsedHashtag(postbox: strongSelf.context.account.postbox, string: text).start() + let _ = strongSelf.context.engine.messages.removeRecentlyUsedHashtag(string: text).start() strongSelf.revealedHashtag = nil } }) diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index 2fd0b0eb73..81340bfc95 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -234,7 +234,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont let geoPoint = currentProcessedResults.geoPoint.flatMap { geoPoint -> (Double, Double) in return (geoPoint.latitude, geoPoint.longitude) } - self.loadMoreDisposable.set((requestChatContextResults(account: self.context.account, botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(geoPoint), offset: nextOffset) + self.loadMoreDisposable.set((self.context.engine.messages.requestChatContextResults(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(geoPoint), offset: nextOffset) |> map { results -> ChatContextResultCollection? in return results?.results } diff --git a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift b/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift index fdf26c0233..ff02ac1547 100644 --- a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift +++ b/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift @@ -93,7 +93,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode { let lineWidth: CGFloat = 4.0 - let pathDiameter = bounds.size.width - lineWidth + let pathDiameter = bounds.size.width - lineWidth - 8.0 let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise:true) path.lineWidth = lineWidth diff --git a/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift b/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift index cc92ef8e82..ce69f1bad7 100644 --- a/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift +++ b/submodules/TelegramUI/Sources/LegacyInstantVideoController.swift @@ -144,7 +144,7 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, legacyController.view.disablesInteractiveTransitionGestureRecognizer = true var uploadInterface: LegacyLiveUploadInterface? if peerId.namespace != Namespaces.Peer.SecretChat { - uploadInterface = LegacyLiveUploadInterface(account: context.account) + uploadInterface = LegacyLiveUploadInterface(context: context) } var slowmodeValidUntil: Int32 = 0 diff --git a/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift b/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift index fb02894766..1cab57392c 100644 --- a/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift +++ b/submodules/TelegramUI/Sources/OverlayInstantVideoDecoration.swift @@ -37,7 +37,7 @@ final class OverlayInstantVideoDecoration: UniversalVideoDecoration { self.contentContainerNode.clipsToBounds = true self.foregroundContainerNode = ASDisplayNode() - self.progressNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.8)) + self.progressNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.6)) self.foregroundContainerNode.addSubnode(self.progressNode) self.foregroundNode = self.foregroundContainerNode } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 1a5062a83c..c4b9964353 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -649,7 +649,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen canManageInvitations = true } if canManageInvitations { - let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true) + let invitationsContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: true) invitationsContextPromise.set(.single(invitationsContext)) invitationsStatePromise.set(invitationsContext.state |> map(Optional.init)) } @@ -812,7 +812,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen canManageInvitations = true } if canManageInvitations { - let invitationsContext = PeerExportedInvitationsContext(account: context.account, peerId: peerId, adminId: nil, revoked: false, forceUpdate: true) + let invitationsContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: true) invitationsContextPromise.set(.single(invitationsContext)) invitationsStatePromise.set(invitationsContext.state |> map(Optional.init)) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index d2bf44a3e6..2ab5172acb 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2354,7 +2354,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self?.updateProfileVideo(image, asset: asset, adjustments: adjustments) } galleryController.removedEntry = { [weak self] entry in - let _ = self?.headerNode.avatarListNode.listContainerNode.deleteItem(PeerInfoAvatarListItem(entry: entry)) + if let item = PeerInfoAvatarListItem(entry: entry) { + let _ = self?.headerNode.avatarListNode.listContainerNode.deleteItem(item) + } } strongSelf.hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in self?.headerNode.updateAvatarIsHidden(entry: entry) @@ -2607,7 +2609,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD var hasProgress = false if title != group.title { updateDataSignals.append( - updatePeerTitle(account: strongSelf.context.account, peerId: group.id, title: title) + strongSelf.context.engine.peers.updatePeerTitle(peerId: group.id, title: title) |> ignoreValues |> mapError { _ in return Void() } ) @@ -2615,7 +2617,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } if description != (data.cachedData as? CachedGroupData)?.about { updateDataSignals.append( - updatePeerDescription(account: strongSelf.context.account, peerId: group.id, description: description.isEmpty ? nil : description) + strongSelf.context.engine.peers.updatePeerDescription(peerId: group.id, description: description.isEmpty ? nil : description) |> ignoreValues |> mapError { _ in return Void() } ) @@ -2665,7 +2667,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD var hasProgress = false if title != channel.title { updateDataSignals.append( - updatePeerTitle(account: strongSelf.context.account, peerId: channel.id, title: title) + strongSelf.context.engine.peers.updatePeerTitle(peerId: channel.id, title: title) |> ignoreValues |> mapError { _ in return Void() } ) @@ -2673,7 +2675,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } if description != (data.cachedData as? CachedChannelData)?.about { updateDataSignals.append( - updatePeerDescription(account: strongSelf.context.account, peerId: channel.id, description: description.isEmpty ? nil : description) + strongSelf.context.engine.peers.updatePeerDescription(peerId: channel.id, description: description.isEmpty ? nil : description) |> ignoreValues |> mapError { _ in return Void() } ) @@ -2750,7 +2752,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD if self.isSettings { self.notificationExceptions.set(.single(NotificationExceptionsList(peers: [:], settings: [:])) |> then( - notificationExceptionsList(postbox: context.account.postbox, network: context.account.network) + context.engine.peers.notificationExceptionsList() |> map(Optional.init) )) self.privacySettings.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) @@ -2835,7 +2837,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) { - self.displayAsPeersPromise.set(cachedGroupCallDisplayAsAvailablePeers(account: context.account, peerId: peerId)) + self.displayAsPeersPromise.set(context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId)) } } @@ -3993,13 +3995,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD private func scheduleGroupCall() { self.context.scheduleGroupCall(peerId: self.peerId) -// -// -// let time = Int32(Date().timeIntervalSince1970 + 86400) -// self.activeActionDisposable.set((createGroupCall(account: self.context.account, peerId: self.peerId, title: nil, scheduleDate: time) -// |> deliverOnMainQueue).start(next: { [weak self] info in -// -// })) } private func createAndJoinGroupCall(peerId: PeerId, joinAsPeerId: PeerId?) { @@ -4025,7 +4020,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() - let createSignal = createGroupCall(account: strongSelf.context.account, peerId: peerId, title: nil, scheduleDate: nil) + let createSignal = strongSelf.context.engine.calls.createGroupCall(peerId: peerId, title: nil, scheduleDate: nil) |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() @@ -4387,7 +4382,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } strongSelf.openVoiceChatDisplayAsPeerSelection(completion: { joinAsPeerId in - let _ = updateGroupCallJoinAsPeer(account: context.account, peerId: peerId, joinAs: joinAsPeerId).start() + let _ = context.engine.calls.updateGroupCallJoinAsPeer(peerId: peerId, joinAs: joinAsPeerId).start() self?.openVoiceChatOptions(defaultJoinAsPeerId: joinAsPeerId, gesture: nil, contextController: c) }, gesture: gesture, contextController: c, result: f, backAction: { [weak self] c in self?.openVoiceChatOptions(defaultJoinAsPeerId: defaultJoinAsPeerId, gesture: nil, contextController: c) @@ -5003,7 +4998,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD if case let .image(reference, _, _, _) = item { if let reference = reference { if remove { - let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start() + let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start() } let dismiss = self.headerNode.avatarListNode.listContainerNode.deleteItem(item) if dismiss { @@ -5044,9 +5039,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self.headerNode.ignoreCollapse = false let postbox = self.context.account.postbox - let signal = self.isSettings ? updateAccountPhoto(account: self.context.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in + let signal = self.isSettings ? self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) - }) : updatePeerPhoto(postbox: postbox, network: self.context.account.network, stateManager: self.context.account.stateManager, accountPeerId: self.context.account.peerId, peerId: self.peerId, photo: uploadedPeerPhoto(postbox: postbox, network: self.context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + }) : self.context.engine.peers.updatePeerPhoto(peerId: self.peerId, photo: self.context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) }) self.updateAvatarDisposable.set((signal @@ -5094,6 +5089,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } let account = self.context.account + let context = self.context let signal = Signal { [weak self] subscriber in let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in if let paintingData = adjustments.paintingData, paintingData.hasAnimation { @@ -5102,7 +5098,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD return nil } } - let uploadInterface = LegacyLiveUploadInterface(account: account) + let uploadInterface = LegacyLiveUploadInterface(context: context) let signal: SSignal if let asset = asset as? AVAsset { signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)! @@ -5178,11 +5174,11 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self.updateAvatarDisposable.set((signal |> mapToSignal { videoResource -> Signal in if isSettings { - return updateAccountPhoto(account: account, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in + return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) }) } else { - return updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: photoResource), video: uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: context.engine.peers.uploadedPeerVideo(resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) }) } @@ -5303,7 +5299,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } } let postbox = strongSelf.context.account.postbox - strongSelf.updateAvatarDisposable.set((updatePeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, accountPeerId: strongSelf.context.account.peerId, peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in + strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.peers.updatePeerPhoto(peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) }) |> deliverOnMainQueue).start(next: { result in diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index f6aade2b53..631e63ce85 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -59,7 +59,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem { let message: Message init(message: Message) { - self.id = PeerMessagesMediaPlaylistItemId(messageId: message.id) + self.id = PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index) self.message = message } diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index cf107d2836..386b553b7f 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -7,7 +7,7 @@ import Display import MergeLists import AccountContext -func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNode?) -> ChatHistoryViewTransition { +func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, scrollAnimationCurve: ListViewAnimationCurve?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNode?) -> ChatHistoryViewTransition { var mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) let allUpdated = fromView?.associatedData != toView.associatedData if reverse { @@ -137,13 +137,15 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var scrolledToIndex: MessageHistoryAnchorIndex? var scrolledToSomeIndex = false + let curve: ListViewAnimationCurve = scrollAnimationCurve ?? .Default(duration: nil) + if let scrollPosition = scrollPosition { switch scrollPosition { case let .unread(unreadIndex): var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if case .UnreadEntry = entry { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: curve, directionHint: .Down) break } index -= 1 @@ -153,7 +155,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if entry.index >= unreadIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: curve, directionHint: .Down) break } index -= 1 @@ -164,7 +166,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = 0 for entry in toView.filteredEntries.reversed() { if entry.index < unreadIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(0.0), animated: false, curve: curve, directionHint: .Down) break } index += 1 @@ -174,7 +176,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if entry.index >= scrollIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .top(relativeOffset), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .top(relativeOffset), animated: false, curve: curve, directionHint: .Down) break } index -= 1 @@ -184,7 +186,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = 0 for entry in toView.filteredEntries.reversed() { if entry.index < scrollIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Down) + scrollToItem = ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: curve, directionHint: .Down) break } index += 1 @@ -197,7 +199,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { if scrollIndex.isLessOrEqual(to: entry.index) { - scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) + scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: directionHint) break } index -= 1 @@ -208,7 +210,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie for entry in toView.filteredEntries.reversed() { if !scrollIndex.isLess(than: entry.index) { scrolledToSomeIndex = true - scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint) + scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: directionHint) break } index += 1 diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift index 6808bbc0a1..fd1c032325 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift @@ -340,7 +340,7 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex let geoPoint = currentProcessedResults.geoPoint.flatMap { geoPoint -> (Double, Double) in return (geoPoint.latitude, geoPoint.longitude) } - self.loadMoreDisposable.set((requestChatContextResults(account: self.context.account, botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(geoPoint), offset: nextOffset) + self.loadMoreDisposable.set((self.context.engine.messages.requestChatContextResults(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(geoPoint), offset: nextOffset) |> map { results -> ChatContextResultCollection? in return results?.results } diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 847b29d139..cea1f85d45 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -34,27 +34,16 @@ private protocol BroadcastPartSource: AnyObject { private final class NetworkBroadcastPartSource: BroadcastPartSource { private let queue: Queue - private let account: Account + private let engine: TelegramEngine private let callId: Int64 private let accessHash: Int64 private var dataSource: AudioBroadcastDataSource? - - #if DEBUG - private let debugDumpDirectory: TempBoxDirectory? - #endif - init(queue: Queue, account: Account, callId: Int64, accessHash: Int64) { + init(queue: Queue, engine: TelegramEngine, callId: Int64, accessHash: Int64) { self.queue = queue - self.account = account + self.engine = engine self.callId = callId self.accessHash = accessHash - - #if DEBUG - self.debugDumpDirectory = nil - /*let debugDumpDirectory = TempBox.shared.tempDirectory() - self.debugDumpDirectory = debugDumpDirectory - print("Debug streaming dump path: \(debugDumpDirectory.path)")*/ - #endif } func requestPart(timestampMilliseconds: Int64, durationMilliseconds: Int64, completion: @escaping (OngoingGroupCallBroadcastPart) -> Void, rejoinNeeded: @escaping () -> Void) -> Disposable { @@ -69,11 +58,12 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource { if let dataSourceValue = self.dataSource { dataSource = .single(dataSourceValue) } else { - dataSource = getAudioBroadcastDataSource(account: self.account, callId: self.callId, accessHash: self.accessHash) + dataSource = self.engine.calls.getAudioBroadcastDataSource(callId: self.callId, accessHash: self.accessHash) } let callId = self.callId let accessHash = self.accessHash + let engine = self.engine let queue = self.queue let signal = dataSource @@ -81,7 +71,7 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource { |> mapToSignal { [weak self] dataSource -> Signal in if let dataSource = dataSource { self?.dataSource = dataSource - return getAudioBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds) + return engine.calls.getAudioBroadcastPart(dataSource: dataSource, callId: callId, accessHash: accessHash, timestampIdMilliseconds: timestampIdMilliseconds, durationMilliseconds: durationMilliseconds) |> map(Optional.init) } else { return .single(nil) @@ -89,10 +79,6 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource { } } |> deliverOn(self.queue) - - #if DEBUG - let debugDumpDirectory = self.debugDumpDirectory - #endif return signal.start(next: { result in guard let result = result else { @@ -102,12 +88,6 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource { let part: OngoingGroupCallBroadcastPart switch result.status { case let .data(dataValue): - #if DEBUG - if let debugDumpDirectory = debugDumpDirectory { - let _ = try? dataValue.write(to: URL(fileURLWithPath: debugDumpDirectory.path + "/" + "\(timestampIdMilliseconds).ogg")) - } - #endif - part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .success, oggData: dataValue) case .notReady: part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .notReady, oggData: Data()) @@ -137,12 +117,12 @@ private final class OngoingGroupCallBroadcastPartTaskImpl : NSObject, OngoingGro public final class OngoingGroupCallContext { public struct AudioStreamData { - public var account: Account + public var engine: TelegramEngine public var callId: Int64 public var accessHash: Int64 - public init(account: Account, callId: Int64, accessHash: Int64) { - self.account = account + public init(engine: TelegramEngine, callId: Int64, accessHash: Int64) { + self.engine = engine self.callId = callId self.accessHash = accessHash } @@ -218,6 +198,115 @@ public final class OngoingGroupCallContext { self.maxQuality = maxQuality } } + + public final class VideoFrameData { + public final class NativeBuffer { + public let pixelBuffer: CVPixelBuffer + + init(pixelBuffer: CVPixelBuffer) { + self.pixelBuffer = pixelBuffer + } + } + + public final class NV12Buffer { + private let wrapped: CallVideoFrameNV12Buffer + + public var width: Int { + return Int(self.wrapped.width) + } + + public var height: Int { + return Int(self.wrapped.height) + } + + public var y: Data { + return self.wrapped.y + } + + public var strideY: Int { + return Int(self.wrapped.strideY) + } + + public var uv: Data { + return self.wrapped.uv + } + + public var strideUV: Int { + return Int(self.wrapped.strideUV) + } + + init(wrapped: CallVideoFrameNV12Buffer) { + self.wrapped = wrapped + } + } + + public final class I420Buffer { + private let wrapped: CallVideoFrameI420Buffer + + public var width: Int { + return Int(self.wrapped.width) + } + + public var height: Int { + return Int(self.wrapped.height) + } + + public var y: Data { + return self.wrapped.y + } + + public var strideY: Int { + return Int(self.wrapped.strideY) + } + + public var u: Data { + return self.wrapped.u + } + + public var strideU: Int { + return Int(self.wrapped.strideU) + } + + public var v: Data { + return self.wrapped.v + } + + public var strideV: Int { + return Int(self.wrapped.strideV) + } + + init(wrapped: CallVideoFrameI420Buffer) { + self.wrapped = wrapped + } + } + + public enum Buffer { + case native(NativeBuffer) + case nv12(NV12Buffer) + case i420(I420Buffer) + } + + public let buffer: Buffer + public let width: Int + public let height: Int + public let orientation: OngoingCallVideoOrientation + + init(frameData: CallVideoFrameData) { + if let nativeBuffer = frameData.buffer as? CallVideoFrameNativePixelBuffer { + self.buffer = .native(NativeBuffer(pixelBuffer: nativeBuffer.pixelBuffer)) + } else if let nv12Buffer = frameData.buffer as? CallVideoFrameNV12Buffer { + self.buffer = .nv12(NV12Buffer(wrapped: nv12Buffer)) + } else if let i420Buffer = frameData.buffer as? CallVideoFrameI420Buffer { + self.buffer = .i420(I420Buffer(wrapped: i420Buffer)) + } else { + preconditionFailure() + } + + self.width = Int(frameData.width) + self.height = Int(frameData.height) + self.orientation = OngoingCallVideoOrientation(frameData.orientation) + } + } private final class Impl { let queue: Queue @@ -242,7 +331,7 @@ public final class OngoingGroupCallContext { var audioLevelsUpdatedImpl: (([NSNumber]) -> Void)? if let audioStreamData = audioStreamData { - let broadcastPartsSource = NetworkBroadcastPartSource(queue: queue, account: audioStreamData.account, callId: audioStreamData.callId, accessHash: audioStreamData.accessHash) + let broadcastPartsSource = NetworkBroadcastPartSource(queue: queue, engine: audioStreamData.engine, callId: audioStreamData.callId, accessHash: audioStreamData.accessHash) self.broadcastPartsSource = broadcastPartsSource } @@ -615,6 +704,27 @@ public final class OngoingGroupCallContext { }) } + func video(endpointId: String) -> Signal { + let queue = self.queue + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + + queue.async { + guard let strongSelf = self else { + return + } + let innerDisposable = strongSelf.context.addVideoOutput(withEndpointId: endpointId) { videoFrameData in + subscriber.putNext(OngoingGroupCallContext.VideoFrameData(frameData: videoFrameData)) + } + disposable.set(ActionDisposable { + innerDisposable.dispose() + }) + } + + return disposable + } + } + func addExternalAudioData(data: Data) { self.context.addExternalAudioData(data) } @@ -778,6 +888,18 @@ public final class OngoingGroupCallContext { } } + public func video(endpointId: String) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.video(endpointId: endpointId).start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + public func addExternalAudioData(data: Data) { self.impl.with { impl in impl.addExternalAudioData(data: data) diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index d02f3014a6..f46761cea9 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -451,6 +451,27 @@ public final class OngoingCallVideoCapturer { } self.impl.submitPixelBuffer(pixelBuffer, rotation: videoRotation.orientation) } + + public func video() -> Signal { + let queue = Queue.mainQueue() + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + + queue.async { + guard let strongSelf = self else { + return + } + let innerDisposable = strongSelf.impl.addVideoOutput { videoFrameData in + subscriber.putNext(OngoingGroupCallContext.VideoFrameData(frameData: videoFrameData)) + } + disposable.set(ActionDisposable { + innerDisposable.dispose() + }) + } + + return disposable + } + } } extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProtocol { diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index c83010f703..87c1645925 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -110,6 +110,60 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { #endif @end +@interface GroupCallDisposable : NSObject + +- (void)dispose; + +@end + +@protocol CallVideoFrameBuffer + +@end + +@interface CallVideoFrameNativePixelBuffer : NSObject + +@property (nonatomic, readonly) CVPixelBufferRef _Nonnull pixelBuffer; + +@end + +@interface CallVideoFrameNV12Buffer : NSObject + +@property (nonatomic, readonly) int width; +@property (nonatomic, readonly) int height; + +@property (nonatomic, strong, readonly) NSData * _Nonnull y; +@property (nonatomic, readonly) int strideY; + +@property (nonatomic, strong, readonly) NSData * _Nonnull uv; +@property (nonatomic, readonly) int strideUV; + +@end + +@interface CallVideoFrameI420Buffer : NSObject + +@property (nonatomic, readonly) int width; +@property (nonatomic, readonly) int height; + +@property (nonatomic, strong, readonly) NSData * _Nonnull y; +@property (nonatomic, readonly) int strideY; + +@property (nonatomic, strong, readonly) NSData * _Nonnull u; +@property (nonatomic, readonly) int strideU; + +@property (nonatomic, strong, readonly) NSData * _Nonnull v; +@property (nonatomic, readonly) int strideV; + +@end + +@interface CallVideoFrameData : NSObject + +@property (nonatomic, strong, readonly) id _Nonnull buffer; +@property (nonatomic, readonly) int width; +@property (nonatomic, readonly) int height; +@property (nonatomic, readonly) OngoingCallVideoOrientationWebrtc orientation; + +@end + @interface OngoingCallThreadLocalContextVideoCapturer : NSObject - (instancetype _Nonnull)initWithDeviceId:(NSString * _Nonnull)deviceId keepLandscape:(bool)keepLandscape; @@ -131,6 +185,8 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { - (void)submitPixelBuffer:(CVPixelBufferRef _Nonnull)pixelBuffer rotation:(OngoingCallVideoOrientationWebrtc)rotation; #endif +- (GroupCallDisposable * _Nonnull)addVideoOutput:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink; + @end @interface OngoingCallThreadLocalContextWebrtc : NSObject @@ -291,6 +347,7 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) { - (void)switchAudioOutput:(NSString * _Nonnull)deviceId; - (void)switchAudioInput:(NSString * _Nonnull)deviceId; - (void)makeIncomingVideoViewWithEndpointId:(NSString * _Nonnull)endpointId requestClone:(bool)requestClone completion:(void (^_Nonnull)(UIView * _Nullable, UIView * _Nullable))completion; +- (GroupCallDisposable * _Nonnull)addVideoOutputWithEndpointId:(NSString * _Nonnull)endpointId sink:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink; - (void)addExternalAudioData:(NSData * _Nonnull)data; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 53d02b469b..f78eae73ac 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -32,6 +32,9 @@ #import "VideoCaptureInterfaceImpl.h" +#include "sdk/objc/native/src/objc_frame_buffer.h" +#import "components/video_frame_buffer/RTCCVPixelBuffer.h" + @implementation OngoingCallConnectionDescriptionWebrtc - (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId hasStun:(bool)hasStun hasTurn:(bool)hasTurn ip:(NSString * _Nonnull)ip port:(int32_t)port username:(NSString * _Nonnull)username password:(NSString * _Nonnull)password { @@ -221,10 +224,216 @@ @end +@interface GroupCallDisposable () { + dispatch_block_t _block; +} + +@end + +@implementation GroupCallDisposable + +- (instancetype)initWithBlock:(dispatch_block_t _Nonnull)block { + self = [super init]; + if (self != nil) { + _block = [block copy]; + } + return self; +} + +- (void)dispose { + if (_block) { + _block(); + } +} + +@end + +@implementation CallVideoFrameNativePixelBuffer + +- (instancetype)initWithPixelBuffer:(CVPixelBufferRef)pixelBuffer { + self = [super init]; + if (self != nil) { + assert(pixelBuffer != nil); + + _pixelBuffer = CVPixelBufferRetain(pixelBuffer); + } + return self; +} + +- (void)dealloc { + CVPixelBufferRelease(_pixelBuffer); +} + +@end + +@implementation CallVideoFrameNV12Buffer + +- (instancetype)initWithBuffer:(rtc::scoped_refptr)nv12Buffer { + self = [super init]; + if (self != nil) { + _width = nv12Buffer->width(); + _height = nv12Buffer->height(); + + _strideY = nv12Buffer->StrideY(); + _strideUV = nv12Buffer->StrideUV(); + + _y = [[NSData alloc] initWithBytesNoCopy:(void *)nv12Buffer->DataY() length:nv12Buffer->StrideY() * _height deallocator:^(__unused void * _Nonnull bytes, __unused NSUInteger length) { + nv12Buffer.get(); + }]; + + _uv = [[NSData alloc] initWithBytesNoCopy:(void *)nv12Buffer->DataUV() length:nv12Buffer->StrideUV() * _height deallocator:^(__unused void * _Nonnull bytes, __unused NSUInteger length) { + nv12Buffer.get(); + }]; + } + return self; +} + +@end + +@implementation CallVideoFrameI420Buffer + +- (instancetype)initWithBuffer:(rtc::scoped_refptr)i420Buffer { + self = [super init]; + if (self != nil) { + _width = i420Buffer->width(); + _height = i420Buffer->height(); + + _strideY = i420Buffer->StrideY(); + _strideU = i420Buffer->StrideU(); + _strideV = i420Buffer->StrideV(); + + _y = [[NSData alloc] initWithBytesNoCopy:(void *)i420Buffer->DataY() length:i420Buffer->StrideY() * _height deallocator:^(__unused void * _Nonnull bytes, __unused NSUInteger length) { + i420Buffer.get(); + }]; + + _u = [[NSData alloc] initWithBytesNoCopy:(void *)i420Buffer->DataU() length:i420Buffer->StrideU() * _height deallocator:^(__unused void * _Nonnull bytes, __unused NSUInteger length) { + i420Buffer.get(); + }]; + + _v = [[NSData alloc] initWithBytesNoCopy:(void *)i420Buffer->DataV() length:i420Buffer->StrideV() * _height deallocator:^(__unused void * _Nonnull bytes, __unused NSUInteger length) { + i420Buffer.get(); + }]; + } + return self; +} + +@end + +@interface CallVideoFrameData () { +} + +@end + +@implementation CallVideoFrameData + +- (instancetype)initWithBuffer:(id)buffer frame:(webrtc::VideoFrame const &)frame { + self = [super init]; + if (self != nil) { + _buffer = buffer; + + _width = frame.width(); + _height = frame.height(); + + switch (frame.rotation()) { + case webrtc::kVideoRotation_0: { + _orientation = OngoingCallVideoOrientation0; + break; + } + case webrtc::kVideoRotation_90: { + _orientation = OngoingCallVideoOrientation90; + break; + } + case webrtc::kVideoRotation_180: { + _orientation = OngoingCallVideoOrientation180; + break; + } + case webrtc::kVideoRotation_270: { + _orientation = OngoingCallVideoOrientation270; + break; + } + default: { + _orientation = OngoingCallVideoOrientation0; + break; + } + } + } + return self; +} + +@end + +namespace { + +class GroupCallVideoSinkAdapter : public rtc::VideoSinkInterface { +public: + GroupCallVideoSinkAdapter(void (^frameReceived)(webrtc::VideoFrame const &)) { + _frameReceived = [frameReceived copy]; + } + + void OnFrame(const webrtc::VideoFrame& nativeVideoFrame) override { + @autoreleasepool { + if (_frameReceived) { + _frameReceived(nativeVideoFrame); + } + } + } + +private: + void (^_frameReceived)(webrtc::VideoFrame const &); +}; + +} + +@interface GroupCallVideoSink : NSObject { + std::shared_ptr _adapter; +} + +@end + +@implementation GroupCallVideoSink + +- (instancetype)initWithSink:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink { + self = [super init]; + if (self != nil) { + void (^storedSink)(CallVideoFrameData * _Nonnull) = [sink copy]; + + _adapter.reset(new GroupCallVideoSinkAdapter(^(webrtc::VideoFrame const &videoFrame) { + id mappedBuffer = nil; + + if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kNative) { + id nativeBuffer = static_cast(videoFrame.video_frame_buffer().get())->wrapped_frame_buffer(); + if ([nativeBuffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]) { + RTCCVPixelBuffer *pixelBuffer = (RTCCVPixelBuffer *)nativeBuffer; + mappedBuffer = [[CallVideoFrameNativePixelBuffer alloc] initWithPixelBuffer:pixelBuffer.pixelBuffer]; + } + } else if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kNV12) { + rtc::scoped_refptr nv12Buffer = (webrtc::NV12BufferInterface *)videoFrame.video_frame_buffer().get(); + mappedBuffer = [[CallVideoFrameNV12Buffer alloc] initWithBuffer:nv12Buffer]; + } else if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kI420) { + rtc::scoped_refptr i420Buffer = (webrtc::I420BufferInterface *)videoFrame.video_frame_buffer().get(); + mappedBuffer = [[CallVideoFrameI420Buffer alloc] initWithBuffer:i420Buffer]; + } + + if (storedSink && mappedBuffer) { + storedSink([[CallVideoFrameData alloc] initWithBuffer:mappedBuffer frame:videoFrame]); + } + })); + } + return self; +} + +- (std::shared_ptr>)sink { + return _adapter; +} + +@end @interface OngoingCallThreadLocalContextVideoCapturer () { bool _keepLandscape; std::shared_ptr> _croppingBuffer; + + int _nextSinkId; + NSMutableDictionary *_sinks; } @end @@ -312,6 +521,32 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls: #endif +- (GroupCallDisposable * _Nonnull)addVideoOutput:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink { + int sinkId = _nextSinkId; + _nextSinkId += 1; + + GroupCallVideoSink *storedSink = [[GroupCallVideoSink alloc] initWithSink:sink]; + _sinks[@(sinkId)] = storedSink; + + auto sinkReference = [storedSink sink]; + + tgcalls::StaticThreads::getThreads()->getMediaThread()->PostTask(RTC_FROM_HERE, [interface = _interface, sinkReference]() { + interface->setOutput(sinkReference); + }); + + __weak OngoingCallThreadLocalContextVideoCapturer *weakSelf = self; + return [[GroupCallDisposable alloc] initWithBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + __strong OngoingCallThreadLocalContextVideoCapturer *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf->_sinks removeObjectForKey:@(sinkId)]; + }); + }]; +} + - (void)switchVideoInput:(NSString * _Nonnull)deviceId { std::string resolvedId = deviceId.UTF8String; if (_keepLandscape) { @@ -1071,11 +1306,14 @@ private: @interface GroupCallThreadLocalContext () { id _queue; - + std::unique_ptr _instance; OngoingCallThreadLocalContextVideoCapturer *_videoCapturer; - + void (^_networkStateUpdated)(GroupCallNetworkState); + + int _nextSinkId; + NSMutableDictionary *_sinks; } @end @@ -1096,6 +1334,8 @@ private: self = [super init]; if (self != nil) { _queue = queue; + + _sinks = [[NSMutableDictionary alloc] init]; _networkStateUpdated = [networkStateUpdated copy]; _videoCapturer = videoCapturer; @@ -1477,6 +1717,31 @@ private: } } +- (GroupCallDisposable * _Nonnull)addVideoOutputWithEndpointId:(NSString * _Nonnull)endpointId sink:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink { + int sinkId = _nextSinkId; + _nextSinkId += 1; + + GroupCallVideoSink *storedSink = [[GroupCallVideoSink alloc] initWithSink:sink]; + _sinks[@(sinkId)] = storedSink; + + if (_instance) { + _instance->addIncomingVideoOutput(endpointId.UTF8String, [storedSink sink]); + } + + __weak GroupCallThreadLocalContext *weakSelf = self; + id queue = _queue; + return [[GroupCallDisposable alloc] initWithBlock:^{ + [queue dispatch:^{ + __strong GroupCallThreadLocalContext *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf->_sinks removeObjectForKey:@(sinkId)]; + }]; + }]; +} + - (void)addExternalAudioData:(NSData * _Nonnull)data { if (_instance) { std::vector samples; diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 1d00d49002..ef79634980 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 1d00d49002333436bb9069558cd718dd770dade1 +Subproject commit ef796349808b187b80b75ea1876b940f2882fcbb diff --git a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift index 533cd32c5e..81249019eb 100644 --- a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift +++ b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift @@ -755,7 +755,7 @@ final class WatchLocationHandler: WatchRequestHandler { guard let peerId = peerId else { return .single(nil) } - return requestChatContextResults(account: context.account, botId: peerId, peerId: context.account.peerId, query: "", location: .single((args.coordinate.latitude, args.coordinate.longitude)), offset: "") + return context.engine.messages.requestChatContextResults(botId: peerId, peerId: context.account.peerId, query: "", location: .single((args.coordinate.latitude, args.coordinate.longitude)), offset: "") |> map { results -> ChatContextResultCollection? in return results?.results } diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 71c802442d..3fc012211b 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -10,8 +10,8 @@ import LegacyComponents import TelegramUIPreferences import AccountContext -public func requestContextResults(account: Account, botId: PeerId, query: String, peerId: PeerId, offset: String = "", existingResults: ChatContextResultCollection? = nil, incompleteResults: Bool = false, staleCachedResults: Bool = false, limit: Int = 60) -> Signal { - return requestChatContextResults(account: account, botId: botId, peerId: peerId, query: query, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) +public func requestContextResults(context: AccountContext, botId: PeerId, query: String, peerId: PeerId, offset: String = "", existingResults: ChatContextResultCollection? = nil, incompleteResults: Bool = false, staleCachedResults: Bool = false, limit: Int = 60) -> Signal { + return context.engine.messages.requestChatContextResults(botId: botId, peerId: peerId, query: query, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) |> `catch` { error -> Signal in return .single(nil) } @@ -40,7 +40,7 @@ public func requestContextResults(account: Account, botId: PeerId, query: String updated = true } if let collection = collection, collection.results.count < limit, let nextOffset = collection.nextOffset, updated { - let nextResults = requestContextResults(account: account, botId: botId, query: query, peerId: peerId, offset: nextOffset, existingResults: collection, limit: limit) + let nextResults = requestContextResults(context: context, botId: botId, query: query, peerId: peerId, offset: nextOffset, existingResults: collection, limit: limit) if collection.results.count > 10 { return .single(RequestChatContextResultsResult(results: collection, isStale: resultsStruct?.isStale ?? false)) |> then(nextResults) @@ -445,6 +445,7 @@ public final class WebSearchController: ViewController { } let account = self.context.account + let context = self.context let contextBot = self.context.engine.peers.resolvePeerByName(name: name) |> mapToSignal { peerId -> Signal in if let peerId = peerId { @@ -459,7 +460,7 @@ public final class WebSearchController: ViewController { } |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let results = requestContextResults(account: account, botId: user.id, query: query, peerId: peerId, limit: 64) + let results = requestContextResults(context: context, botId: user.id, query: query, peerId: peerId, limit: 64) |> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in return { _ in return .contextRequestResult(user, results?.results) diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift index b2b279f61d..450ee647df 100644 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift @@ -577,7 +577,7 @@ class WebSearchControllerNode: ASDisplayNode { let geoPoint = currentProcessedResults.geoPoint.flatMap { geoPoint -> (Double, Double) in return (geoPoint.latitude, geoPoint.longitude) } - self.loadMoreDisposable.set((requestChatContextResults(account: self.context.account, botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(geoPoint), offset: nextOffset) + self.loadMoreDisposable.set((self.context.engine.messages.requestChatContextResults(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(geoPoint), offset: nextOffset) |> deliverOnMainQueue).start(next: { [weak self] nextResults in guard let strongSelf = self, let nextResults = nextResults else { return diff --git a/third-party/openh264/BUILD b/third-party/openh264/BUILD index 101692249c..0fde708c6f 100644 --- a/third-party/openh264/BUILD +++ b/third-party/openh264/BUILD @@ -40,7 +40,7 @@ arm_specific_sources = [ ] arm_specific_textual_hdrs = [ - "third_party/openh264/third_party/openh264/src/codec/common/arm/arm_arch_common_macro.S", + "third_party/openh264/src/codec/common/arm/arm_arch_common_macro.S", ] arm64_specific_copts = [