mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-20 13:19:16 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
ce911f3f9b
@ -1 +1 @@
|
||||
8tRwQybvfoDddhSIfdMSOMv4FZd9LSHiWmObmx6d7rE=
|
||||
gCh0ST/jBZ+NM8mvcBcsd12A5FMFT4q6fETcWd5elO0=
|
||||
|
||||
@ -22,15 +22,20 @@ public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId {
|
||||
}
|
||||
|
||||
public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation {
|
||||
case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId)
|
||||
case messages(chatLocation: ChatLocation, tagMask: MessageTags, at: MessageId)
|
||||
case singleMessage(MessageId)
|
||||
case recentActions(Message)
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?)
|
||||
|
||||
public var playlistId: PeerMessagesMediaPlaylistId {
|
||||
switch self {
|
||||
case let .messages(peerId, _, _):
|
||||
return .peer(peerId)
|
||||
case let .messages(chatLocation, _, _):
|
||||
switch chatLocation {
|
||||
case let .peer(peerId):
|
||||
return .peer(peerId)
|
||||
case let .replyThread(replyThreaMessage):
|
||||
return .peer(replyThreaMessage.messageId.peerId)
|
||||
}
|
||||
case let .singleMessage(id):
|
||||
return .peer(id.peerId)
|
||||
case let .recentActions(message):
|
||||
@ -59,8 +64,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
|
||||
|
||||
public static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool {
|
||||
switch lhs {
|
||||
case let .messages(peerId, tagMask, at):
|
||||
if case .messages(peerId, tagMask, at) = rhs {
|
||||
case let .messages(chatLocation, tagMask, at):
|
||||
if case .messages(chatLocation, tagMask, at) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
||||
@ -13,6 +13,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
import AccountContext
|
||||
import TelegramCallsUI
|
||||
|
||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
@ -74,6 +75,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case alternativeFolderTabs(Bool)
|
||||
case playerEmbedding(Bool)
|
||||
case playlistPlayback(Bool)
|
||||
case voiceConference
|
||||
case preferredVideoCodec(Int, String, String?, Bool)
|
||||
case disableVideoAspectScaling(Bool)
|
||||
case enableVoipTcp(Bool)
|
||||
@ -90,7 +92,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback:
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback, .voiceConference:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .preferredVideoCodec:
|
||||
return DebugControllerSection.videoExperiments.rawValue
|
||||
@ -155,8 +157,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 24
|
||||
case .playlistPlayback:
|
||||
return 25
|
||||
case .voiceConference:
|
||||
return 26
|
||||
case let .preferredVideoCodec(index, _, _, _):
|
||||
return 26 + index
|
||||
return 27 + index
|
||||
case .disableVideoAspectScaling:
|
||||
return 100
|
||||
case .enableVoipTcp:
|
||||
@ -648,6 +652,15 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case .voiceConference:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Voice Conference (Test)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
let controller = GroupCallController(context: context)
|
||||
controller.navigationPresentation = .modal
|
||||
arguments.pushController(controller)
|
||||
})
|
||||
case let .preferredVideoCodec(_, title, value, isSelected):
|
||||
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
@ -725,6 +738,8 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||
|
||||
entries.append(.voiceConference)
|
||||
|
||||
let codecs: [(String, String?)] = [
|
||||
("No Preference", nil),
|
||||
("H265", "H265"),
|
||||
|
||||
147
submodules/TelegramCallsUI/Sources/GroupCallController.swift
Normal file
147
submodules/TelegramCallsUI/Sources/GroupCallController.swift
Normal file
@ -0,0 +1,147 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TelegramVoip
|
||||
import TelegramAudio
|
||||
import AccountContext
|
||||
|
||||
public final class GroupCallController: ViewController {
|
||||
private final class Node: ViewControllerTracingNode {
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
|
||||
private var callContext: GroupCallContext?
|
||||
private var callDisposable: Disposable?
|
||||
private var memberCountDisposable: Disposable?
|
||||
private var isMutedDisposable: Disposable?
|
||||
private let audioSessionActive = Promise<Bool>(false)
|
||||
|
||||
private var memberCount: Int = 0
|
||||
private let memberCountNode: ImmediateTextNode
|
||||
|
||||
private var isMuted: Bool = false
|
||||
private let isMutedNode: ImmediateTextNode
|
||||
private let muteButton: HighlightableButtonNode
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.memberCountNode = ImmediateTextNode()
|
||||
self.isMutedNode = ImmediateTextNode()
|
||||
|
||||
self.muteButton = HighlightableButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.memberCountNode)
|
||||
|
||||
self.muteButton.addSubnode(self.isMutedNode)
|
||||
self.addSubnode(self.muteButton)
|
||||
|
||||
let audioSessionActive = self.audioSessionActive
|
||||
self.callDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .voiceCall, manualActivate: { audioSessionControl in
|
||||
audioSessionControl.activate({ _ in })
|
||||
audioSessionActive.set(.single(true))
|
||||
}, deactivate: {
|
||||
return Signal { subscriber in
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
}, availableOutputsChanged: { _, _ in
|
||||
})
|
||||
|
||||
let callContext = GroupCallContext(audioSessionActive: self.audioSessionActive.get())
|
||||
self.callContext = callContext
|
||||
|
||||
self.memberCountDisposable = (callContext.memberCount
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.memberCount = value
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
})
|
||||
|
||||
self.isMutedDisposable = (callContext.isMuted
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isMuted = value
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
})
|
||||
|
||||
self.muteButton.addTarget(self, action: #selector(self.muteButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.callDisposable?.dispose()
|
||||
self.memberCountDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func muteButtonPressed() {
|
||||
self.callContext?.toggleIsMuted()
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = layout
|
||||
|
||||
self.memberCountNode.attributedText = NSAttributedString(string: "Members: \(self.memberCount)", font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
|
||||
self.isMutedNode.attributedText = NSAttributedString(string: self.isMuted ? "Unmute" : "Mute", font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemAccentColor)
|
||||
|
||||
let textSize = self.memberCountNode.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: 100.0))
|
||||
let isMutedSize = self.isMutedNode.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: 100.0))
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: floor((layout.size.height - textSize.width) / 2.0)), size: textSize)
|
||||
transition.updateFrameAdditiveToCenter(node: self.memberCountNode, frame: textFrame)
|
||||
|
||||
let isMutedFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - isMutedSize.width) / 2.0), y: textFrame.maxY + 12.0), size: isMutedSize)
|
||||
transition.updateFrame(node: self.muteButton, frame: isMutedFrame)
|
||||
self.isMutedNode.frame = CGRect(origin: CGPoint(), size: isMutedFrame.size)
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
|
||||
private var controllerNode: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(context: self.context)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
||||
@ -170,6 +170,10 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
public let outgoingDateAndStatusRepliesIcon: UIImage
|
||||
public let mediaRepliesIcon: UIImage
|
||||
public let freeRepliesIcon: UIImage
|
||||
public let incomingDateAndStatusPinnedIcon: UIImage
|
||||
public let outgoingDateAndStatusPinnedIcon: UIImage
|
||||
public let mediaPinnedIcon: UIImage
|
||||
public let freePinnedIcon: UIImage
|
||||
|
||||
public let dateStaticBackground: UIImage
|
||||
public let dateFloatingBackground: UIImage
|
||||
@ -333,6 +337,12 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
||||
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
||||
|
||||
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
||||
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
||||
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||
self.mediaPinnedIcon = generateTintedImage(image: pinnedImage, color: .white)!
|
||||
self.freePinnedIcon = generateTintedImage(image: pinnedImage, color: serviceColor.primaryText)!
|
||||
|
||||
self.radialIndicatorFileIconIncoming = emptyImage
|
||||
self.radialIndicatorFileIconOutgoing = emptyImage
|
||||
} else {
|
||||
@ -438,6 +448,12 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
||||
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
||||
|
||||
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
||||
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
||||
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||
self.mediaPinnedIcon = generateTintedImage(image: pinnedImage, color: .white)!
|
||||
self.freePinnedIcon = generateTintedImage(image: pinnedImage, color: serviceColor.primaryText)!
|
||||
|
||||
self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
|
||||
self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Chat/Message/Pinned.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/Pinned.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "messagepin.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/Pinned.imageset/messagepin.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/Pinned.imageset/messagepin.pdf
vendored
Normal file
Binary file not shown.
@ -358,6 +358,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
public var purposefulAction: (() -> Void)?
|
||||
|
||||
private let scrolledToMessageId = ValuePromise<MessageId?>(nil, ignoreRepeated: true)
|
||||
|
||||
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil) {
|
||||
let _ = ChatControllerCount.modify { value in
|
||||
return value + 1
|
||||
@ -3258,11 +3260,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
|> restartIfError
|
||||
|
||||
struct ReferenceMessage {
|
||||
var id: MessageId
|
||||
var isScrolled: Bool
|
||||
}
|
||||
|
||||
let referenceMessage: Signal<ReferenceMessage?, NoError>
|
||||
if latest {
|
||||
referenceMessage = .single(nil)
|
||||
} else {
|
||||
referenceMessage = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
self.scrolledToMessageId.get(),
|
||||
self.chatDisplayNode.historyNode.topVisibleMessageRange.get()
|
||||
)
|
||||
|> map { scrolledToMessageId, topVisibleMessageRange -> ReferenceMessage? in
|
||||
if let scrolledToMessageId = scrolledToMessageId {
|
||||
return ReferenceMessage(id: scrolledToMessageId, isScrolled: true)
|
||||
} else if let topVisibleMessageRange = topVisibleMessageRange {
|
||||
return ReferenceMessage(id: topVisibleMessageRange.upperBound, isScrolled: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
topPinnedMessage = combineLatest(
|
||||
replyHistory,
|
||||
latest ? .single(nil) : self.chatDisplayNode.historyNode.topVisibleMessageRange.get()
|
||||
referenceMessage
|
||||
)
|
||||
|> map { update, topVisibleMessageRange -> ChatPinnedMessage? in
|
||||
|> map { update, referenceMessage -> ChatPinnedMessage? in
|
||||
var message: ChatPinnedMessage?
|
||||
switch update {
|
||||
case .Loading:
|
||||
@ -3278,9 +3305,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var matches = false
|
||||
if message == nil {
|
||||
matches = true
|
||||
} else if let topVisibleMessageRange = topVisibleMessageRange {
|
||||
if entry.message.id <= topVisibleMessageRange.upperBound {
|
||||
matches = true
|
||||
} else if let referenceMessage = referenceMessage {
|
||||
if referenceMessage.isScrolled {
|
||||
if entry.message.id < referenceMessage.id {
|
||||
matches = true
|
||||
}
|
||||
} else {
|
||||
if entry.message.id <= referenceMessage.id {
|
||||
matches = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
matches = true
|
||||
@ -3318,6 +3351,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
self.chatDisplayNode.historyNode.beganDragging = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.scrolledToMessageId.set(nil)
|
||||
}
|
||||
|
||||
self.chatDisplayNode.peerView = self.peerView
|
||||
|
||||
let initialData = self.chatDisplayNode.historyNode.initialData
|
||||
@ -3645,6 +3686,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId)
|
||||
controllerInteraction.highlightedState = highlightedState
|
||||
strongSelf.updateItemNodesHighlightedStates(animated: false)
|
||||
strongSelf.scrolledToMessageId.set(index.id)
|
||||
|
||||
strongSelf.messageContextDisposable.set((Signal<Void, NoError>.complete() |> delay(0.7, queue: Queue.mainQueue())).start(completed: {
|
||||
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
|
||||
@ -4874,7 +4916,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}
|
||||
}, unpinMessage: { [weak self] id in
|
||||
}, unpinMessage: { [weak self] id, askForConfirmation in
|
||||
if let strongSelf = self {
|
||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||
var canManagePin = false
|
||||
@ -4896,7 +4938,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if canManagePin {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UnpinMessageAlert, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_Unpin, action: {
|
||||
let action: () -> Void = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let strongSelf = self {
|
||||
let disposable: MetaDisposable
|
||||
if let current = strongSelf.unpinMessageDisposable {
|
||||
@ -4907,7 +4952,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
disposable.set(requestUpdatePinnedMessage(account: strongSelf.context.account, peerId: peer.id, update: .clear(id: id)).start())
|
||||
}
|
||||
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root))
|
||||
}
|
||||
if askForConfirmation {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UnpinMessageAlert, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_Unpin, action: {
|
||||
action()
|
||||
}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root))
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
} else {
|
||||
if let pinnedMessage = strongSelf.presentationInterfaceState.pinnedMessage {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
|
||||
@ -526,6 +526,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
var maxVisibleMessageIndexUpdated: ((MessageIndex) -> Void)?
|
||||
|
||||
var scrolledToIndex: ((MessageHistoryAnchorIndex) -> Void)?
|
||||
var beganDragging: (() -> Void)?
|
||||
|
||||
private let hasVisiblePlayableItemNodesPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
var hasVisiblePlayableItemNodes: Signal<Bool, NoError> {
|
||||
@ -1121,6 +1122,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self.beganInteractiveDragging = { [weak self] in
|
||||
self?.isInteractivelyScrollingValue = true
|
||||
self?.isInteractivelyScrollingPromise.set(true)
|
||||
self?.beganDragging?()
|
||||
}
|
||||
|
||||
self.didEndScrolling = { [weak self] in
|
||||
|
||||
@ -664,7 +664,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Unpin, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unpin"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.unpinMessage(pinnedSelectedMessageId)
|
||||
interfaceInteraction.unpinMessage(pinnedSelectedMessageId, false)
|
||||
f(.default)
|
||||
})))
|
||||
} else {
|
||||
|
||||
@ -702,7 +702,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal, reactionCount: dateReactionCount)
|
||||
|
||||
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies)
|
||||
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
|
||||
|
||||
@ -572,7 +572,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies)
|
||||
statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, message.tags.contains(.pinned))
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
||||
@ -609,9 +609,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if let strongSelf = self {
|
||||
for contentNode in strongSelf.contentNodes {
|
||||
var translatedPoint: CGPoint?
|
||||
let convertedNodeFrame = contentNode.convert(contentNode.bounds, to: strongSelf)
|
||||
let convertedNodeFrame = contentNode.view.convert(contentNode.bounds, to: strongSelf.view)
|
||||
if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
|
||||
translatedPoint = strongSelf.convert(point, to: contentNode)
|
||||
translatedPoint = strongSelf.view.convert(point, to: contentNode.view)
|
||||
}
|
||||
contentNode.updateTouchesAtPoint(translatedPoint)
|
||||
}
|
||||
@ -905,7 +905,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode),
|
||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)),
|
||||
mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction], Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode),
|
||||
mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction], Int, Bool) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode),
|
||||
currentShareButtonNode: HighlightableButtonNode?,
|
||||
layoutConstants: ChatMessageItemLayoutConstants,
|
||||
currentItem: ChatMessageItem?,
|
||||
@ -1442,7 +1442,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies)
|
||||
mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, message.tags.contains(.pinned))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2873,7 +2873,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
loop: for contentNode in self.contentNodes {
|
||||
let convertedLocation = self.convert(location, to: contentNode)
|
||||
let convertedLocation = self.view.convert(location, to: contentNode.view)
|
||||
|
||||
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
|
||||
switch tapAction {
|
||||
@ -2982,9 +2982,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
var tapMessage: Message? = item.content.firstMessage
|
||||
var selectAll = true
|
||||
loop: for contentNode in self.contentNodes {
|
||||
let convertedLocation = self.convert(location, to: contentNode)
|
||||
let convertedLocation = self.view.convert(location, to: contentNode.view)
|
||||
|
||||
let convertedNodeFrame = contentNode.convert(contentNode.bounds, to: self)
|
||||
let convertedNodeFrame = contentNode.view.convert(contentNode.bounds, to: self.view)
|
||||
if !convertedNodeFrame.contains(location) {
|
||||
continue loop
|
||||
} else if contentNode is ChatMessageMediaBubbleContentNode {
|
||||
|
||||
@ -196,7 +196,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var statusApply: ((Bool) -> Void)?
|
||||
|
||||
if let statusType = statusType {
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies)
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
self.addSubnode(self.dateNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> Void) {
|
||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int, _ isPinned: Bool) -> (CGSize, (Bool) -> Void) {
|
||||
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
|
||||
var checkReadNode = self.checkReadNode
|
||||
@ -200,7 +200,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
|
||||
let previousLayoutSize = self.layoutSize
|
||||
|
||||
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replyCount in
|
||||
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replyCount, isPinned in
|
||||
let dateColor: UIColor
|
||||
var backgroundImage: UIImage?
|
||||
var outgoingStatus: ChatMessageDateAndStatusOutgoingType?
|
||||
@ -234,6 +234,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
if replyCount != 0 {
|
||||
repliesImage = graphics.incomingDateAndStatusRepliesIcon
|
||||
} else if isPinned {
|
||||
repliesImage = graphics.incomingDateAndStatusPinnedIcon
|
||||
}
|
||||
case let .BubbleOutgoing(status):
|
||||
dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
@ -248,6 +250,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
if replyCount != 0 {
|
||||
repliesImage = graphics.outgoingDateAndStatusRepliesIcon
|
||||
} else if isPinned {
|
||||
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
|
||||
}
|
||||
case .ImageIncoming:
|
||||
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
||||
@ -262,6 +266,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
if replyCount != 0 {
|
||||
repliesImage = graphics.mediaRepliesIcon
|
||||
} else if isPinned {
|
||||
repliesImage = graphics.mediaPinnedIcon
|
||||
}
|
||||
case let .ImageOutgoing(status):
|
||||
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
||||
@ -277,6 +283,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
if replyCount != 0 {
|
||||
repliesImage = graphics.mediaRepliesIcon
|
||||
} else if isPinned {
|
||||
repliesImage = graphics.mediaPinnedIcon
|
||||
}
|
||||
case .FreeIncoming:
|
||||
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
||||
@ -292,6 +300,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
if replyCount != 0 {
|
||||
repliesImage = graphics.freeRepliesIcon
|
||||
} else if isPinned {
|
||||
repliesImage = graphics.freePinnedIcon
|
||||
}
|
||||
case let .FreeOutgoing(status):
|
||||
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
||||
@ -308,6 +318,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
if replyCount != 0 {
|
||||
repliesImage = graphics.freeRepliesIcon
|
||||
} else if isPinned {
|
||||
repliesImage = graphics.freePinnedIcon
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,6 +523,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
|
||||
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
|
||||
replyCountLayoutAndApply = layoutAndApply
|
||||
} else if isPinned {
|
||||
reactionInset += 12.0
|
||||
}
|
||||
|
||||
leftInset += reactionInset
|
||||
@ -817,17 +831,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) {
|
||||
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int, _ isPinned: Bool) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) {
|
||||
let currentLayout = node?.asyncLayout()
|
||||
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies in
|
||||
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies, isPinned in
|
||||
let resultNode: ChatMessageDateAndStatusNode
|
||||
let resultSizeAndApply: (CGSize, (Bool) -> Void)
|
||||
if let node = node, let currentLayout = currentLayout {
|
||||
resultNode = node
|
||||
resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies)
|
||||
resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies, isPinned)
|
||||
} else {
|
||||
resultNode = ChatMessageDateAndStatusNode()
|
||||
resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies)
|
||||
resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies, isPinned)
|
||||
}
|
||||
|
||||
return (resultSizeAndApply.0, { animated in
|
||||
|
||||
@ -327,7 +327,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, reactionCount: dateReactionCount)
|
||||
|
||||
let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies)
|
||||
let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies, message.tags.contains(.pinned))
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
@ -285,7 +285,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
} else {
|
||||
maxDateAndStatusWidth = width - videoFrame.midX - 85.0
|
||||
}
|
||||
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies)
|
||||
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
|
||||
var contentSize = imageSize
|
||||
var dateAndStatusOverflow = false
|
||||
|
||||
@ -246,7 +246,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var statusApply: ((Bool) -> Void)?
|
||||
|
||||
if let statusType = statusType {
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies)
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
@ -231,7 +231,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var statusApply: ((Bool) -> Void)?
|
||||
|
||||
if let statusType = statusType {
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies)
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
@ -1074,7 +1074,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var statusApply: ((Bool) -> Void)?
|
||||
|
||||
if let statusType = statusType {
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies)
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var statusApply: ((Bool) -> Void)?
|
||||
|
||||
if let statusType = statusType {
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies)
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
@ -382,7 +382,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, reactionCount: dateReactionCount)
|
||||
|
||||
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies)
|
||||
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
|
||||
|
||||
@ -169,7 +169,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var statusApply: ((Bool) -> Void)?
|
||||
|
||||
if let statusType = statusType {
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies)
|
||||
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned))
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
|
||||
let unblockPeer: () -> Void
|
||||
let pinMessage: (MessageId) -> Void
|
||||
let unpinMessage: (MessageId) -> Void
|
||||
let unpinMessage: (MessageId, Bool) -> Void
|
||||
let shareAccountContact: () -> Void
|
||||
let reportPeer: () -> Void
|
||||
let presentPeerContact: () -> Void
|
||||
@ -171,7 +171,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
|
||||
unblockPeer: @escaping () -> Void,
|
||||
pinMessage: @escaping (MessageId) -> Void,
|
||||
unpinMessage: @escaping (MessageId) -> Void,
|
||||
unpinMessage: @escaping (MessageId, Bool) -> Void,
|
||||
shareAccountContact: @escaping () -> Void,
|
||||
reportPeer: @escaping () -> Void,
|
||||
presentPeerContact: @escaping () -> Void,
|
||||
|
||||
@ -362,7 +362,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
|
||||
@objc func closePressed() {
|
||||
if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
|
||||
interfaceInteraction.unpinMessage(message.message.id)
|
||||
interfaceInteraction.unpinMessage(message.message.id, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
return false
|
||||
}, unblockPeer: {
|
||||
}, pinMessage: { _ in
|
||||
}, unpinMessage: { _ in
|
||||
}, unpinMessage: { _, _ in
|
||||
}, shareAccountContact: {
|
||||
}, reportPeer: {
|
||||
}, presentPeerContact: {
|
||||
|
||||
@ -130,7 +130,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
} else if params.standalone {
|
||||
location = .recentActions(params.message)
|
||||
} else {
|
||||
location = .messages(peerId: params.message.id.peerId, tagMask: .voiceOrInstantVideo, at: params.message.id)
|
||||
location = .messages(chatLocation: params.chatLocation ?? .peer(params.message.id.peerId), tagMask: .voiceOrInstantVideo, at: params.message.id)
|
||||
}
|
||||
playerType = .voice
|
||||
} else if file.isMusic && params.message.tags.contains(.music) {
|
||||
@ -139,7 +139,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
} else if params.standalone {
|
||||
location = .recentActions(params.message)
|
||||
} else {
|
||||
location = .messages(peerId: params.message.id.peerId, tagMask: .music, at: params.message.id)
|
||||
location = .messages(chatLocation: params.chatLocation ?? .peer(params.message.id.peerId), tagMask: .music, at: params.message.id)
|
||||
}
|
||||
playerType = .music
|
||||
} else {
|
||||
@ -150,7 +150,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
}
|
||||
playerType = (file.isVoice || file.isInstantVideo) ? .voice : .music
|
||||
}
|
||||
params.context.sharedContext.mediaManager.setPlaylist((params.context.account, PeerMessagesMediaPlaylist(postbox: params.context.account.postbox, network: params.context.account.network, location: location)), type: playerType, control: control)
|
||||
params.context.sharedContext.mediaManager.setPlaylist((params.context.account, PeerMessagesMediaPlaylist(context: params.context, location: location, chatLocationContextHolder: params.chatLocationContextHolder)), type: playerType, control: control)
|
||||
return true
|
||||
case let .gallery(gallery):
|
||||
params.dismissInput()
|
||||
|
||||
@ -52,6 +52,7 @@ import PeerInfoUI
|
||||
import ListMessageItem
|
||||
import GalleryData
|
||||
import ChatInterfaceState
|
||||
import TelegramVoip
|
||||
|
||||
protocol PeerInfoScreenItem: class {
|
||||
var id: AnyHashable { get }
|
||||
@ -405,7 +406,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
return false
|
||||
}, unblockPeer: {
|
||||
}, pinMessage: { _ in
|
||||
}, unpinMessage: { _ in
|
||||
}, unpinMessage: { _, _ in
|
||||
}, shareAccountContact: {
|
||||
}, reportPeer: {
|
||||
}, presentPeerContact: {
|
||||
@ -3153,6 +3154,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.controller?.present(shareController, in: .window(.root))
|
||||
}
|
||||
|
||||
private let groupCallDisposable = MetaDisposable()
|
||||
private var groupCall: GroupCallContext?
|
||||
|
||||
private func requestCall(isVideo: Bool) {
|
||||
guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else {
|
||||
return
|
||||
|
||||
@ -307,9 +307,9 @@ private struct PlaybackStack {
|
||||
}
|
||||
|
||||
final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let context: AccountContext
|
||||
private let messagesLocation: PeerMessagesPlaylistLocation
|
||||
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?
|
||||
|
||||
var location: SharedMediaPlaylistLocation {
|
||||
return self.messagesLocation
|
||||
@ -338,13 +338,13 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
return self.stateValue.get()
|
||||
}
|
||||
|
||||
init(postbox: Postbox, network: Network, location: PeerMessagesPlaylistLocation) {
|
||||
init(context: AccountContext, location: PeerMessagesPlaylistLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.id = location.playlistId
|
||||
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.context = context
|
||||
self.chatLocationContextHolder = chatLocationContextHolder
|
||||
self.messagesLocation = location
|
||||
|
||||
switch self.messagesLocation {
|
||||
@ -446,7 +446,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
self.currentlyObservedMessageId = item?.message.id
|
||||
if let id = item?.message.id {
|
||||
let key: PostboxViewKey = .messages(Set([id]))
|
||||
self.currentlyObservedMessageDisposable.set((self.postbox.combinedView(keys: [key])
|
||||
self.currentlyObservedMessageDisposable.set((self.context.account.postbox.combinedView(keys: [key])
|
||||
|> filter { views in
|
||||
if let view = views.views[key] as? MessagesView {
|
||||
if !view.messages.isEmpty {
|
||||
@ -479,15 +479,15 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
switch anchor {
|
||||
case let .messageId(messageId):
|
||||
switch self.messagesLocation {
|
||||
case let .messages(peerId, tagMask, _):
|
||||
let historySignal = self.postbox.messageAtId(messageId)
|
||||
case let .messages(chatLocation, tagMask, _):
|
||||
let historySignal = self.context.account.postbox.messageAtId(messageId)
|
||||
|> take(1)
|
||||
|> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in
|
||||
guard let message = message else {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
return self.context.account.postbox.aroundMessageHistoryViewForLocation(self.context.chatLocationInput(for: chatLocation, contextHolder: self.chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
|
||||
return .single((message, aroundMessages))
|
||||
@ -539,7 +539,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
}
|
||||
}))
|
||||
default:
|
||||
self.navigationDisposable.set((self.postbox.messageAtId(messageId)
|
||||
self.navigationDisposable.set((self.context.account.postbox.messageAtId(messageId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] message in
|
||||
if let strongSelf = self {
|
||||
@ -559,7 +559,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
}
|
||||
case let .index(index):
|
||||
switch self.messagesLocation {
|
||||
case let .messages(peerId, tagMask, _):
|
||||
case let .messages(chatLocation, tagMask, _):
|
||||
let inputIndex: Signal<MessageIndex, NoError>
|
||||
let looping = self.looping
|
||||
switch self.order {
|
||||
@ -567,7 +567,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
inputIndex = .single(index)
|
||||
case .random:
|
||||
var playbackStack = self.playbackStack
|
||||
inputIndex = self.postbox.transaction { transaction -> MessageIndex in
|
||||
inputIndex = self.context.account.postbox.transaction { transaction -> MessageIndex in
|
||||
if case let .random(previous) = navigation, previous {
|
||||
let _ = playbackStack.pop()
|
||||
while true {
|
||||
@ -580,12 +580,12 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
}
|
||||
}
|
||||
}
|
||||
return transaction.findRandomMessage(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: tagMask, ignoreIds: (playbackStack.ids, playbackStack.set)) ?? index
|
||||
return transaction.findRandomMessage(peerId: chatLocation.peerId, namespace: Namespaces.Message.Cloud, tag: tagMask, ignoreIds: (playbackStack.ids, playbackStack.set)) ?? index
|
||||
}
|
||||
}
|
||||
let historySignal = inputIndex
|
||||
|> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(inputIndex), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
return self.context.account.postbox.aroundMessageHistoryViewForLocation(self.context.chatLocationInput(for: chatLocation, contextHolder: self.chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), anchor: .index(inputIndex), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||
let position: NavigatedMessageFromViewPosition
|
||||
switch navigation {
|
||||
@ -615,7 +615,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
} else {
|
||||
viewIndex = .lowerBound
|
||||
}
|
||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
return self.context.account.postbox.aroundMessageHistoryViewForLocation(self.context.chatLocationInput(for: chatLocation, contextHolder: self.chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||
let position: NavigatedMessageFromViewPosition
|
||||
switch navigation {
|
||||
@ -657,7 +657,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
}
|
||||
}))
|
||||
case .singleMessage:
|
||||
self.navigationDisposable.set((self.postbox.messageAtId(index.id)
|
||||
self.navigationDisposable.set((self.context.account.postbox.messageAtId(index.id)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] message in
|
||||
if let strongSelf = self {
|
||||
@ -815,7 +815,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||
default:
|
||||
break
|
||||
}
|
||||
let _ = markMessageContentAsConsumedInteractively(postbox: self.postbox, messageId: item.message.id).start()
|
||||
let _ = markMessageContentAsConsumedInteractively(postbox: self.context.account.postbox, messageId: item.message.id).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1469
submodules/TelegramVoip/Sources/GroupCallContext.swift
Normal file
1469
submodules/TelegramVoip/Sources/GroupCallContext.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
||||
#ifndef GroupCallThreadLocalContext_h
|
||||
#define GroupCallThreadLocalContext_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
||||
|
||||
@interface GroupCallThreadLocalContext : NSObject
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer;
|
||||
|
||||
- (void)emitOffer;
|
||||
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial;
|
||||
- (void)setIsMuted:(bool)isMuted;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,57 @@
|
||||
|
||||
#import <TgVoipWebrtc/GroupCallThreadLocalContext.h>
|
||||
|
||||
#import "group/GroupInstanceImpl.h"
|
||||
|
||||
@interface GroupCallThreadLocalContext () {
|
||||
id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
|
||||
|
||||
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GroupCallThreadLocalContext
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_queue = queue;
|
||||
|
||||
tgcalls::GroupInstanceDescriptor descriptor;
|
||||
__weak GroupCallThreadLocalContext *weakSelf = self;
|
||||
descriptor.sdpAnswerEmitted = [weakSelf, queue, relaySdpAnswer](std::string const &sdpAnswer) {
|
||||
NSString *string = [NSString stringWithUTF8String:sdpAnswer.c_str()];
|
||||
[queue dispatch:^{
|
||||
__strong GroupCallThreadLocalContext *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
relaySdpAnswer(string);
|
||||
}];
|
||||
};
|
||||
|
||||
_instance.reset(new tgcalls::GroupInstanceImpl(std::move(descriptor)));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)emitOffer {
|
||||
if (_instance) {
|
||||
_instance->emitOffer();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial {
|
||||
if (_instance) {
|
||||
_instance->setOfferSdp([offerSdp UTF8String], isPartial);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsMuted:(bool)isMuted {
|
||||
if (_instance) {
|
||||
_instance->setIsMuted(isMuted);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,7 +1,7 @@
|
||||
#ifndef WEBRTC_IOS
|
||||
#import "OngoingCallThreadLocalContext.h"
|
||||
#else
|
||||
#import <TgVoip/OngoingCallThreadLocalContext.h>
|
||||
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 64f96a1b4fcfb8afdb0fb7749082cb42cdad7901
|
||||
Subproject commit 4f7501d281b851e6302b2a2d7298c733eee82414
|
||||
2
third-party/webrtc/BUILD
vendored
2
third-party/webrtc/BUILD
vendored
@ -1,4 +1,4 @@
|
||||
use_gn_build = True
|
||||
use_gn_build = False
|
||||
|
||||
webrtc_libs = [
|
||||
"libwebrtc.a",
|
||||
|
||||
2
third-party/webrtc/webrtc-ios
vendored
2
third-party/webrtc/webrtc-ios
vendored
@ -1 +1 @@
|
||||
Subproject commit 11255bcfff3180210a012f368e2d2bcd169b6877
|
||||
Subproject commit 782743c7931d09c1d2e4a0cf6cd349ee45452f1d
|
||||
Loading…
x
Reference in New Issue
Block a user