mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Merge branch 'master' into experimental-2
# Conflicts: # submodules/ChatListUI/Sources/ChatContextMenus.swift # submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift
This commit is contained in:
commit
617bcc314e
@ -1 +1 @@
|
||||
268a141d3791e0f6bf2abfab3c268435844d35f72071cae44704f8ba5e7c7dfd
|
||||
4f0d2d13a70664d3029d9b97935089df0426fe53745965d175408752838b80dd
|
@ -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<PeerId> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) {
|
||||
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<PeerId> = 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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<CachedChannelData.ActiveCall?, NoError> = 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Never, NoError> { 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<Never, NoError> { 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
|
||||
}
|
||||
}
|
||||
|
@ -892,7 +892,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
})
|
||||
|> mapToSignal { selectedPeerIdsAndFilterId -> Signal<(ChatListSelectionOptions, Set<PeerId>)?, 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<PeerId>)? 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<Bool, NoError> 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
|
||||
|
||||
|
@ -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<PeerId, Peer>()
|
||||
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 })
|
||||
|
@ -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?()
|
||||
|
@ -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()
|
||||
|
@ -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<PeerId, Peer>()
|
||||
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 })
|
||||
|
@ -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<PeerId>, filterId: Int32?) -> Signal<ChatListSelectionOptions, NoError> {
|
||||
func chatListSelectionOptions(context: AccountContext, peerIds: Set<PeerId>, filterId: Int32?) -> Signal<ChatListSelectionOptions, NoError> {
|
||||
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<PeerId>, 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<PeerId>, 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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<Bool, NoError> 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<UITouch>, 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<UITouch>, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Bool, NoError>.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 {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -1414,7 +1414,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
|
||||
return 64;
|
||||
|
||||
case TGMediaVideoConversionPresetVideoMessage:
|
||||
return 32;
|
||||
return 64;
|
||||
|
||||
case TGMediaVideoConversionPresetAnimation:
|
||||
case TGMediaVideoConversionPresetProfile:
|
||||
|
@ -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];
|
||||
|
@ -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<MediaResourceData>()
|
||||
private let dataValue = Atomic<MediaResourceData?>(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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -44,11 +44,11 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
||||
|
||||
private var invalidationTimer: (SwiftSignalKit.Timer, Int32)?
|
||||
|
||||
public init(account: Account, locationManager: DeviceLocationManager, inForeground: Signal<Bool, NoError>) {
|
||||
public init(engine: TelegramEngine, account: Account, locationManager: DeviceLocationManager, inForeground: Signal<Bool, NoError>) {
|
||||
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])
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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<Void, Void>
|
||||
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<Void, Void>
|
||||
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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ private final class TimeBasedCleanupImpl {
|
||||
let generalPaths = self.generalPaths
|
||||
let shortLivedPaths = self.shortLivedPaths
|
||||
let scanOnce = Signal<Never, NoError> { subscriber in
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
var removedShortLivedCount: Int = 0
|
||||
var removedGeneralCount: Int = 0
|
||||
var removedGeneralLimitCount: Int = 0
|
||||
|
@ -148,12 +148,18 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, E>(queue: Que
|
||||
}, initialValues: [:], queue: queue)
|
||||
}
|
||||
|
||||
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), E> {
|
||||
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>) -> 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<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>) -> 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<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
|
||||
if signals.count == 0 {
|
||||
return single([T](), E.self)
|
||||
|
@ -13,6 +13,7 @@ swift_library(
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/rlottie:RLottieBinding",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +394,7 @@ func cacheUsageStats(context: AccountContext) -> Signal<CacheUsageStatsResult?,
|
||||
containerPath + "/Documents/tempcache_v1/store",
|
||||
]
|
||||
return .single(nil)
|
||||
|> 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<Void, NoError> = clearCachedMediaResources(account: context.account, mediaResourceIds: clearResourceIds)
|
||||
var signal: Signal<Void, NoError> = context.engine.resources.clearCachedMediaResources(mediaResourceIds: clearResourceIds)
|
||||
if otherSize.0 {
|
||||
let removeTempFiles: Signal<Void, NoError> = 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)
|
||||
|
||||
|
@ -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<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(notificationExceptionsList(postbox: context.account.postbox, network: context.account.network) |> map(Optional.init))
|
||||
let exceptionsSignal = Signal<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init))
|
||||
|
||||
notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in
|
||||
var users:[PeerId : NotificationExceptionWrapper] = [:]
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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 =
|
||||
"""
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.telegram.TelegramCallsUI</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>TelegramCallsUI</string>
|
||||
"""
|
||||
)
|
||||
|
||||
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",
|
||||
|
49
submodules/TelegramCallsUI/Resources/I420VideoShaders.metal
Normal file
49
submodules/TelegramCallsUI/Resources/I420VideoShaders.metal
Normal file
@ -0,0 +1,49 @@
|
||||
#include <metal_stdlib>
|
||||
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<float, access::sample> textureY[[texture(0)]],
|
||||
texture2d<float, access::sample> textureU[[texture(1)]],
|
||||
texture2d<float, access::sample> 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);
|
||||
}
|
57
submodules/TelegramCallsUI/Resources/NV12VideoShaders.metal
Normal file
57
submodules/TelegramCallsUI/Resources/NV12VideoShaders.metal
Normal file
@ -0,0 +1,57 @@
|
||||
#include <metal_stdlib>
|
||||
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<float, access::sample> textureY, texture2d<float, access::sample> 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<float, access::sample> textureY[[texture(0)]],
|
||||
texture2d<float, access::sample> 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<float, access::sample> texture[[texture(0)]]
|
||||
) {
|
||||
constexpr sampler s(address::clamp_to_edge, filter::linear);
|
||||
|
||||
float4 out = texture.sample(s, in.texcoord);
|
||||
|
||||
return half4(out);
|
||||
}
|
@ -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?
|
||||
|
657
submodules/TelegramCallsUI/Sources/MetalVideoRenderingView.swift
Normal file
657
submodules/TelegramCallsUI/Sources/MetalVideoRenderingView.swift
Normal file
@ -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<Float>
|
||||
) {
|
||||
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<Int>(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<OngoingGroupCallContext.VideoFrameData, NoError>, blur: Bool) {
|
||||
self.renderingContext = renderingContext
|
||||
self.blur = blur
|
||||
|
||||
let vertexBufferArray = Array<Float>(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<ViewReference>()
|
||||
|
||||
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
|
@ -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<GroupCallParticipantsContext.State?, NoError> 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<GroupCallInfo?, CallError>
|
||||
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<GroupCallInviteLinks?, NoError> {
|
||||
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<OngoingGroupCallContext.VideoFrameData, NoError>? {
|
||||
return self.genericCallContext?.video(endpointId: endpointId)
|
||||
}
|
||||
|
||||
public func loadMoreMembers(token: String) {
|
||||
self.participantsContext?.loadMore(token: token)
|
||||
|
@ -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<OngoingGroupCallContext.VideoFrameData, NoError>) {
|
||||
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
|
||||
}
|
||||
}
|
@ -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<OngoingGroupCallContext.VideoFrameData, NoError>, 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
|
||||
}
|
||||
}
|
||||
}
|
@ -918,6 +918,7 @@ public final class VoiceChatController: ViewController {
|
||||
private var requestedVideoSources = Set<String>()
|
||||
private var requestedVideoChannels: [PresentationGroupCallRequestedVideo] = []
|
||||
|
||||
private var videoRenderingContext: VideoRenderingContext
|
||||
private var videoNodes: [String: GroupVideoNode] = [:]
|
||||
private var wideVideoNodes = Set<String>()
|
||||
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<Void, NoError> 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<TelegramMediaResource, UploadPeerPhotoError> { [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<UpdatePeerPhotoStatus, UploadPeerPhotoError> 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)
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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!
|
||||
|
@ -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<UpdatePeerPhotoStatus, SignUpError> in
|
||||
return .complete()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<MediaResourceData, NoError>, onComplete:(()->Void)? = nil) {
|
||||
func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal<MediaResourceData, NoError>, 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<MessageMediaPreuploadManagerContext>
|
||||
|
||||
init() {
|
||||
@ -113,7 +113,7 @@ public final class MessageMediaPreuploadManager {
|
||||
})
|
||||
}
|
||||
|
||||
public func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal<MediaResourceData, NoError>, onComplete:(()->Void)? = nil) {
|
||||
func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal<MediaResourceData, NoError>, onComplete:(()->Void)? = nil) {
|
||||
self.impl.with { context in
|
||||
context.add(network: network, postbox: postbox, id: id, encrypt: encrypt, tag: tag, source: source, onComplete: onComplete)
|
||||
}
|
@ -46,5 +46,17 @@ public extension TelegramEngine {
|
||||
public func registerNotificationToken(token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [PeerId.Id], excludeMutedChats: Bool) -> Signal<Never, NoError> {
|
||||
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<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
|
||||
}
|
||||
|
||||
public func updatePeerPhotoExisting(reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
|
||||
return _internal_updatePeerPhotoExisting(network: self.account.network, reference: reference)
|
||||
}
|
||||
|
||||
public func removeAccountPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
|
||||
return _internal_removeAccountPhoto(network: self.account.network, reference: reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,10 @@ public extension TelegramEngineUnauthorized {
|
||||
public func resendTwoStepRecoveryEmail() -> Signal<Never, ResendTwoStepRecoveryEmailError> {
|
||||
return _internal_resendTwoStepRecoveryEmail(network: self.account.network)
|
||||
}
|
||||
|
||||
public func uploadedPeerVideo(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: nil, resource: resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ public enum GetCurrentGroupCallError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
|
||||
func _internal_getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
|
||||
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<GroupCallInfo, CreateGroupCallError> {
|
||||
func _internal_createGroupCall(account: Account, peerId: PeerId, title: String?, scheduleDate: Int32?) -> Signal<GroupCallInfo, CreateGroupCallError> {
|
||||
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<GroupCallInfo, StartScheduledGroupCallError> {
|
||||
func _internal_startScheduledGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal<GroupCallInfo, StartScheduledGroupCallError> {
|
||||
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<Void, ToggleScheduledGroupCallSubscriptionError> {
|
||||
func _internal_toggleScheduledGroupCallSubscription(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> {
|
||||
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<Never, UpdateGroupCallJoinAsPeerError> {
|
||||
func _internal_updateGroupCallJoinAsPeer(account: Account, peerId: PeerId, joinAs: PeerId) -> Signal<Never, UpdateGroupCallJoinAsPeerError> {
|
||||
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<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
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<JoinGroupCallResult, JoinGroupCallError> {
|
||||
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<JoinGroupCallResult, JoinGroupCallError> {
|
||||
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<JoinGroupCallAsScreencastResult, JoinGroupCallError> {
|
||||
func _internal_joinGroupCallAsScreencast(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal<JoinGroupCallAsScreencastResult, JoinGroupCallError> {
|
||||
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<Never, LeaveGroupCallAsScreencastError> {
|
||||
func _internal_leaveGroupCallAsScreencast(account: Account, callId: Int64, accessHash: Int64) -> Signal<Never, LeaveGroupCallAsScreencastError> {
|
||||
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<Never, LeaveGroupCallError> {
|
||||
func _internal_leaveGroupCall(account: Account, callId: Int64, accessHash: Int64, source: UInt32) -> Signal<Never, LeaveGroupCallError> {
|
||||
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<Never, StopGroupCallError> {
|
||||
func _internal_stopGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal<Never, StopGroupCallError> {
|
||||
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<Never, InviteToGroupCallError> {
|
||||
func _internal_inviteToGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId) -> Signal<Never, InviteToGroupCallError> {
|
||||
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<GroupCallInviteLinks?, NoError> {
|
||||
func _internal_groupCallInviteLinks(account: Account, callId: Int64, accessHash: Int64) -> Signal<GroupCallInviteLinks?, NoError> {
|
||||
let call = Api.InputGroupCall.inputGroupCall(id: callId, accessHash: accessHash)
|
||||
let listenerInvite: Signal<String?, NoError> = 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<Never, EditGroupCallTitleError> {
|
||||
func _internal_editGroupCallTitle(account: Account, callId: Int64, accessHash: Int64, title: String) -> Signal<Never, EditGroupCallTitleError> {
|
||||
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<Never, NoError> {
|
||||
func _internal_clearCachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
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<CachedChannelData.ActiveCall?, NoError> {
|
||||
return fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox)
|
||||
func _internal_updatedCurrentPeerGroupCall(account: Account, peerId: PeerId) -> Signal<CachedChannelData.ActiveCall?, NoError> {
|
||||
return _internal_fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox)
|
||||
|> mapToSignal { _ -> Signal<CachedChannelData.ActiveCall?, NoError> 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<AudioBroadcastDataSource?, NoError> {
|
||||
func _internal_getAudioBroadcastDataSource(account: Account, callId: Int64, accessHash: Int64) -> Signal<AudioBroadcastDataSource?, NoError> {
|
||||
return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), limit: 3))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.phone.GroupCall?, NoError> 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<GetAudioBroadcastPartResult, NoError> {
|
||||
func _internal_getAudioBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64) -> Signal<GetAudioBroadcastPartResult, NoError> {
|
||||
let scale: Int32
|
||||
switch durationMilliseconds {
|
||||
case 1000:
|
@ -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<Void, NoError> {
|
||||
return _internal_saveCallDebugLog(network: self.account.network, callId: callId, log: log)
|
||||
}
|
||||
|
||||
public func getCurrentGroupCall(callId: Int64, accessHash: Int64, peerId: PeerId? = nil) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
|
||||
return _internal_getCurrentGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId)
|
||||
}
|
||||
|
||||
public func createGroupCall(peerId: PeerId, title: String?, scheduleDate: Int32?) -> Signal<GroupCallInfo, CreateGroupCallError> {
|
||||
return _internal_createGroupCall(account: self.account, peerId: peerId, title: title, scheduleDate: scheduleDate)
|
||||
}
|
||||
|
||||
public func startScheduledGroupCall(peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal<GroupCallInfo, StartScheduledGroupCallError> {
|
||||
return _internal_startScheduledGroupCall(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash)
|
||||
}
|
||||
|
||||
public func toggleScheduledGroupCallSubscription(peerId: PeerId, callId: Int64, accessHash: Int64, subscribe: Bool) -> Signal<Void, ToggleScheduledGroupCallSubscriptionError> {
|
||||
return _internal_toggleScheduledGroupCallSubscription(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash, subscribe: subscribe)
|
||||
}
|
||||
|
||||
public func updateGroupCallJoinAsPeer(peerId: PeerId, joinAs: PeerId) -> Signal<Never, UpdateGroupCallJoinAsPeerError> {
|
||||
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<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
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<JoinGroupCallResult, JoinGroupCallError> {
|
||||
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<JoinGroupCallAsScreencastResult, JoinGroupCallError> {
|
||||
return _internal_joinGroupCallAsScreencast(account: self.account, peerId: peerId, callId: callId, accessHash: accessHash, joinPayload: joinPayload)
|
||||
}
|
||||
|
||||
public func leaveGroupCallAsScreencast(callId: Int64, accessHash: Int64) -> Signal<Never, LeaveGroupCallAsScreencastError> {
|
||||
return _internal_leaveGroupCallAsScreencast(account: self.account, callId: callId, accessHash: accessHash)
|
||||
}
|
||||
|
||||
public func leaveGroupCall(callId: Int64, accessHash: Int64, source: UInt32) -> Signal<Never, LeaveGroupCallError> {
|
||||
return _internal_leaveGroupCall(account: self.account, callId: callId, accessHash: accessHash, source: source)
|
||||
}
|
||||
|
||||
public func stopGroupCall(peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal<Never, StopGroupCallError> {
|
||||
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<Never, InviteToGroupCallError> {
|
||||
return _internal_inviteToGroupCall(account: self.account, callId: callId, accessHash: accessHash, peerId: peerId)
|
||||
}
|
||||
|
||||
public func groupCallInviteLinks(callId: Int64, accessHash: Int64) -> Signal<GroupCallInviteLinks?, NoError> {
|
||||
return _internal_groupCallInviteLinks(account: self.account, callId: callId, accessHash: accessHash)
|
||||
}
|
||||
|
||||
public func editGroupCallTitle(callId: Int64, accessHash: Int64, title: String) -> Signal<Never, EditGroupCallTitleError> {
|
||||
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<Never, NoError> {
|
||||
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<CachedChannelData.ActiveCall?, NoError> {
|
||||
return _internal_updatedCurrentPeerGroupCall(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func getAudioBroadcastDataSource(callId: Int64, accessHash: Int64) -> Signal<AudioBroadcastDataSource?, NoError> {
|
||||
return _internal_getAudioBroadcastDataSource(account: self.account, callId: callId, accessHash: accessHash)
|
||||
}
|
||||
|
||||
public func getAudioBroadcastPart(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64, timestampIdMilliseconds: Int64, durationMilliseconds: Int64) -> Signal<GetAudioBroadcastPartResult, NoError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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?
|
@ -30,7 +30,7 @@ func addRecentlyUsedHashtag(transaction: Transaction, string: String) {
|
||||
}
|
||||
}
|
||||
|
||||
public func removeRecentlyUsedHashtag(postbox: Postbox, string: String) -> Signal<Void, NoError> {
|
||||
func _internal_removeRecentlyUsedHashtag(postbox: Postbox, string: String) -> Signal<Void, NoError> {
|
||||
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
|
@ -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<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
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<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
return account.postbox.transaction { transaction -> (bot: Peer, peer: Peer)? in
|
||||
if let bot = transaction.getPeer(botId), let peer = transaction.getPeer(peerId) {
|
||||
return (bot, peer)
|
@ -151,5 +151,25 @@ public extension TelegramEngine {
|
||||
public func exportMessageLink(peerId: PeerId, messageId: MessageId, isThread: Bool = false) -> Signal<String?, NoError> {
|
||||
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<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
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<Void, NoError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
import SwiftSignalKit
|
||||
|
||||
public extension TelegramEngine {
|
||||
final class PeerManagement {
|
||||
private let account: Account
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -367,7 +367,7 @@ public enum RequestUpdateChatListFilterError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func requestUpdateChatListFilter(postbox: Postbox, network: Network, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||
func _internal_requestUpdateChatListFilter(postbox: Postbox, network: Network, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||
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<Never, RequestUpdateChatListFilterOrderError> {
|
||||
func _internal_requestUpdateChatListFilterOrder(account: Account, ids: [Int32]) -> Signal<Never, RequestUpdateChatListFilterOrderError> {
|
||||
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<Never, NoError> {
|
||||
func _internal_markChatListFeaturedFiltersAsSeen(postbox: Postbox) -> Signal<Never, NoError> {
|
||||
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<Never,
|
||||
|> 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<Never, NoError> {
|
||||
func _internal_updateChatListFeaturedFilters(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
|
||||
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<Void, NoError> {
|
||||
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<Never, NoError> 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<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
@ -4,7 +4,7 @@ import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public func checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
func _internal_checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.applyMarkUnread(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, value: false, interactive: true)
|
||||
|
@ -3,11 +3,9 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
|
||||
public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
|
||||
func _internal_revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> 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<ExportedInvitation?, CreatePeerExportedInvitationError> {
|
||||
func _internal_createPeerExportedInvitation(account: Account, peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> 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<ExportedInvitation?, EditPeerExportedInvitationError> {
|
||||
func _internal_editPeerExportedInvitation(account: Account, peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> 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<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> {
|
||||
func _internal_revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> 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<ExportedInvitations?, NoError> {
|
||||
func _internal_peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, adminId: PeerId? = nil, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> 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<Never, DeletePeerExportedInvitationError> {
|
||||
func _internal_deletePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<Never, DeletePeerExportedInvitationError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, DeletePeerExportedInvitationError> 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<Never, NoError> {
|
||||
func _internal_deleteAllRevokedPeerExportedInvitations(account: Account, peerId: PeerId, adminId: PeerId) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> 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
|
@ -29,7 +29,7 @@ public enum ExternalJoiningChatState {
|
||||
case peek(PeerId, Int32)
|
||||
}
|
||||
|
||||
public func joinChatInteractively(with hash: String, account: Account) -> Signal <PeerId?, JoinLinkError> {
|
||||
func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal <PeerId?, JoinLinkError> {
|
||||
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<ExternalJoiningChatState, NoError> {
|
||||
func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<ExternalJoiningChatState, NoError> {
|
||||
return account.network.request(Api.functions.messages.checkChatInvite(hash: hash))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.ChatInvite?, NoError> in
|
@ -19,7 +19,7 @@ public final class NotificationExceptionsList: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public func notificationExceptionsList(postbox: Postbox, network: Network) -> Signal<NotificationExceptionsList, NoError> {
|
||||
func _internal_notificationExceptionsList(postbox: Postbox, network: Network) -> Signal<NotificationExceptionsList, NoError> {
|
||||
return network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 1, peer: nil))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<NotificationExceptionsList, NoError> in
|
@ -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<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
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<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
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<UploadedPeerPhotoData, NoError> {
|
||||
func _internal_uploadedPeerPhoto(postbox: Postbox, network: Network, resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
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<UploadedPeerPhotoData, NoError> {
|
||||
func _internal_uploadedPeerVideo(postbox: Postbox, network: Network, messageMediaPreuploadManager: MessageMediaPreuploadManager?, resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
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<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
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<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
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<Peer, NoError>, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peer: Signal<Peer, NoError>, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
return peer
|
||||
|> mapError { _ in return .generic }
|
||||
|> mapToSignal { peer -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
|
||||
@ -260,7 +260,7 @@ public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateMan
|
||||
}
|
||||
|> mapToSignal { result, resource, videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> 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<UpdatePeerPhotoStatus, UploadPeerPhotoError> 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<TelegramMediaImage?, NoError> {
|
||||
func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
|
||||
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<Void, NoError> {
|
||||
func _internal_removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
|
||||
if let reference = reference {
|
||||
switch reference {
|
||||
case let .cloud(imageId, accessHash, fileReference):
|
@ -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) {
|
||||
|
@ -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<Void, NoError> {
|
||||
return _internal_removeRecentlyUsedInlineBot(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func uploadedPeerPhoto(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource)
|
||||
}
|
||||
|
||||
public func uploadedPeerVideo(resource: MediaResource) -> Signal<UploadedPeerPhotoData, NoError> {
|
||||
return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource)
|
||||
}
|
||||
|
||||
public func updatePeerPhoto(peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
|
||||
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<Never, RequestUpdateChatListFilterError> {
|
||||
return _internal_requestUpdateChatListFilter(postbox: self.account.postbox, network: self.account.network, id: id, filter: filter)
|
||||
}
|
||||
|
||||
public func requestUpdateChatListFilterOrder(ids: [Int32]) -> Signal<Never, RequestUpdateChatListFilterOrderError> {
|
||||
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<Never, NoError> {
|
||||
return _internal_markChatListFeaturedFiltersAsSeen(postbox: self.account.postbox)
|
||||
}
|
||||
|
||||
public func updateChatListFeaturedFilters() -> Signal<Never, NoError> {
|
||||
return _internal_updateChatListFeaturedFilters(postbox: self.account.postbox, network: self.account.network)
|
||||
}
|
||||
|
||||
public func unmarkChatListFeaturedFiltersAsSeen() -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction in
|
||||
_internal_unmarkChatListFeaturedFiltersAsSeen(transaction: transaction)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func checkPeerChatServiceActions(peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return _internal_checkPeerChatServiceActions(postbox: self.account.postbox, peerId: peerId)
|
||||
}
|
||||
|
||||
public func createPeerExportedInvitation(peerId: PeerId, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, CreatePeerExportedInvitationError> {
|
||||
return _internal_createPeerExportedInvitation(account: self.account, peerId: peerId, expireDate: expireDate, usageLimit: usageLimit)
|
||||
}
|
||||
|
||||
public func editPeerExportedInvitation(peerId: PeerId, link: String, expireDate: Int32?, usageLimit: Int32?) -> Signal<ExportedInvitation?, EditPeerExportedInvitationError> {
|
||||
return _internal_editPeerExportedInvitation(account: self.account, peerId: peerId, link: link, expireDate: expireDate, usageLimit: usageLimit)
|
||||
}
|
||||
|
||||
public func revokePeerExportedInvitation(peerId: PeerId, link: String) -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> {
|
||||
return _internal_revokePeerExportedInvitation(account: self.account, peerId: peerId, link: link)
|
||||
}
|
||||
|
||||
public func deletePeerExportedInvitation(peerId: PeerId, link: String) -> Signal<Never, DeletePeerExportedInvitationError> {
|
||||
return _internal_deletePeerExportedInvitation(account: self.account, peerId: peerId, link: link)
|
||||
}
|
||||
|
||||
public func deleteAllRevokedPeerExportedInvitations(peerId: PeerId, adminId: PeerId) -> Signal<Never, NoError> {
|
||||
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<NotificationExceptionsList, NoError> {
|
||||
return _internal_notificationExceptionsList(postbox: self.account.postbox, network: self.account.network)
|
||||
}
|
||||
|
||||
public func fetchAndUpdateCachedPeerData(peerId: PeerId) -> Signal<Bool, NoError> {
|
||||
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<TogglePeerChatPinnedResult, NoError> {
|
||||
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<Bool, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Bool in
|
||||
return _internal_reorderPinnedItemIds(transaction: transaction, location: location, itemIds: itemIds)
|
||||
}
|
||||
}
|
||||
|
||||
public func joinChatInteractively(with hash: String) -> Signal <PeerId?, JoinLinkError> {
|
||||
return _internal_joinChatInteractively(with: hash, account: self.account)
|
||||
}
|
||||
|
||||
public func joinLinkInformation(_ hash: String) -> Signal<ExternalJoiningChatState, NoError> {
|
||||
return _internal_joinLinkInformation(hash, account: self.account)
|
||||
}
|
||||
|
||||
public func updatePeerTitle(peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> {
|
||||
return _internal_updatePeerTitle(account: self.account, peerId: peerId, title: title)
|
||||
}
|
||||
|
||||
public func updatePeerDescription(peerId: PeerId, description: String?) -> Signal<Void, UpdatePeerDescriptionError> {
|
||||
return _internal_updatePeerDescription(account: self.account, peerId: peerId, description: description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public enum TogglePeerChatPinnedResult {
|
||||
case limitExceeded(Int)
|
||||
}
|
||||
|
||||
public func toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal<TogglePeerChatPinnedResult, NoError> {
|
||||
func _internal_toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal<TogglePeerChatPinnedResult, NoError> {
|
||||
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
|
@ -124,7 +124,7 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, network:
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal<Bool, NoError> {
|
||||
func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal<Bool, NoError> {
|
||||
return postbox.combinedView(keys: [.basicPeer(rawPeerId)])
|
||||
|> mapToSignal { views -> Signal<Bool, NoError> in
|
||||
if accountPeerId == rawPeerId {
|
@ -10,7 +10,7 @@ public enum UpdatePeerTitleError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func updatePeerTitle(account: Account, peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> {
|
||||
func _internal_updatePeerTitle(account: Account, peerId: PeerId, title: String) -> Signal<Void, UpdatePeerTitleError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, UpdatePeerTitleError> 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<Void, UpdatePeerDescriptionError> {
|
||||
func _internal_updatePeerDescription(account: Account, peerId: PeerId, description: String?) -> Signal<Void, UpdatePeerDescriptionError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, UpdatePeerDescriptionError> in
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
if (peer is TelegramChannel || peer is TelegramGroup), let inputPeer = apiInputPeer(peer) {
|
@ -10,7 +10,7 @@ public struct DeepLinkInfo {
|
||||
public let updateApp: Bool
|
||||
}
|
||||
|
||||
public func getDeepLinkInfo(network: Network, path: String) -> Signal<DeepLinkInfo?, NoError> {
|
||||
func _internal_getDeepLinkInfo(network: Network, path: String) -> Signal<DeepLinkInfo?, NoError> {
|
||||
return network.request(Api.functions.help.getDeepLinkInfo(path: path)) |> retryRequest
|
||||
|> map { value -> DeepLinkInfo? in
|
||||
switch value {
|
@ -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<DeepLinkInfo?, NoError> {
|
||||
return _internal_getDeepLinkInfo(network: self.account.network, path: path)
|
||||
}
|
||||
}
|
||||
}
|
@ -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<CacheUsageStatsResult, NoError> {
|
||||
func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, additionalCachePaths: [String] = [], logFilesPath: String? = nil) -> Signal<CacheUsageStatsResult, NoError> {
|
||||
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<WrappedMediaResourceId>) -> Signal<Void, NoError> {
|
||||
func _internal_clearCachedMediaResources(account: Account, mediaResourceIds: Set<WrappedMediaResourceId>) -> Signal<Void, NoError> {
|
||||
return account.postbox.mediaBox.removeCachedResources(mediaResourceIds)
|
||||
}
|
@ -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<MediaResourceData, NoError>, 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<CacheUsageStatsResult, NoError> {
|
||||
return _internal_collectCacheUsageStats(account: self.account, peerId: peerId, additionalCachePaths: additionalCachePaths, logFilesPath: logFilesPath)
|
||||
}
|
||||
|
||||
public func clearCachedMediaResources(mediaResourceIds: Set<WrappedMediaResourceId>) -> Signal<Void, NoError> {
|
||||
return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds)
|
||||
}
|
||||
}
|
||||
}
|
@ -346,7 +346,7 @@ func _internal_searchGifs(account: Account, query: String, nextOffset: String =
|
||||
return account.postbox.loadedPeerWithId(peerId)
|
||||
}
|
||||
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> 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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
@ -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),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user