mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-21 05:39:01 +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 {
|
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 singleMessage(MessageId)
|
||||||
case recentActions(Message)
|
case recentActions(Message)
|
||||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?)
|
case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?)
|
||||||
|
|
||||||
public var playlistId: PeerMessagesMediaPlaylistId {
|
public var playlistId: PeerMessagesMediaPlaylistId {
|
||||||
switch self {
|
switch self {
|
||||||
case let .messages(peerId, _, _):
|
case let .messages(chatLocation, _, _):
|
||||||
return .peer(peerId)
|
switch chatLocation {
|
||||||
|
case let .peer(peerId):
|
||||||
|
return .peer(peerId)
|
||||||
|
case let .replyThread(replyThreaMessage):
|
||||||
|
return .peer(replyThreaMessage.messageId.peerId)
|
||||||
|
}
|
||||||
case let .singleMessage(id):
|
case let .singleMessage(id):
|
||||||
return .peer(id.peerId)
|
return .peer(id.peerId)
|
||||||
case let .recentActions(message):
|
case let .recentActions(message):
|
||||||
@ -59,8 +64,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
|
|||||||
|
|
||||||
public static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool {
|
public static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .messages(peerId, tagMask, at):
|
case let .messages(chatLocation, tagMask, at):
|
||||||
if case .messages(peerId, tagMask, at) = rhs {
|
if case .messages(chatLocation, tagMask, at) = rhs {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import ItemListUI
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import OverlayStatusController
|
import OverlayStatusController
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import TelegramCallsUI
|
||||||
|
|
||||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||||
@ -74,6 +75,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case alternativeFolderTabs(Bool)
|
case alternativeFolderTabs(Bool)
|
||||||
case playerEmbedding(Bool)
|
case playerEmbedding(Bool)
|
||||||
case playlistPlayback(Bool)
|
case playlistPlayback(Bool)
|
||||||
|
case voiceConference
|
||||||
case preferredVideoCodec(Int, String, String?, Bool)
|
case preferredVideoCodec(Int, String, String?, Bool)
|
||||||
case disableVideoAspectScaling(Bool)
|
case disableVideoAspectScaling(Bool)
|
||||||
case enableVoipTcp(Bool)
|
case enableVoipTcp(Bool)
|
||||||
@ -90,7 +92,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||||
return DebugControllerSection.experiments.rawValue
|
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
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .preferredVideoCodec:
|
case .preferredVideoCodec:
|
||||||
return DebugControllerSection.videoExperiments.rawValue
|
return DebugControllerSection.videoExperiments.rawValue
|
||||||
@ -155,8 +157,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 24
|
return 24
|
||||||
case .playlistPlayback:
|
case .playlistPlayback:
|
||||||
return 25
|
return 25
|
||||||
|
case .voiceConference:
|
||||||
|
return 26
|
||||||
case let .preferredVideoCodec(index, _, _, _):
|
case let .preferredVideoCodec(index, _, _, _):
|
||||||
return 26 + index
|
return 27 + index
|
||||||
case .disableVideoAspectScaling:
|
case .disableVideoAspectScaling:
|
||||||
return 100
|
return 100
|
||||||
case .enableVoipTcp:
|
case .enableVoipTcp:
|
||||||
@ -648,6 +652,15 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).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):
|
case let .preferredVideoCodec(_, title, value, isSelected):
|
||||||
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
@ -725,6 +738,8 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
|||||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||||
|
|
||||||
|
entries.append(.voiceConference)
|
||||||
|
|
||||||
let codecs: [(String, String?)] = [
|
let codecs: [(String, String?)] = [
|
||||||
("No Preference", nil),
|
("No Preference", nil),
|
||||||
("H265", "H265"),
|
("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 outgoingDateAndStatusRepliesIcon: UIImage
|
||||||
public let mediaRepliesIcon: UIImage
|
public let mediaRepliesIcon: UIImage
|
||||||
public let freeRepliesIcon: 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 dateStaticBackground: UIImage
|
||||||
public let dateFloatingBackground: UIImage
|
public let dateFloatingBackground: UIImage
|
||||||
@ -333,6 +337,12 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
||||||
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
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.radialIndicatorFileIconIncoming = emptyImage
|
||||||
self.radialIndicatorFileIconOutgoing = emptyImage
|
self.radialIndicatorFileIconOutgoing = emptyImage
|
||||||
} else {
|
} else {
|
||||||
@ -438,6 +448,12 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
||||||
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
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.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
|
||||||
self.radialIndicatorFileIconOutgoing = 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" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
},
|
},
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"provides-namespace" : true
|
"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)?
|
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) {
|
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
|
let _ = ChatControllerCount.modify { value in
|
||||||
return value + 1
|
return value + 1
|
||||||
@ -3258,11 +3260,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
|> restartIfError
|
|> 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(
|
topPinnedMessage = combineLatest(
|
||||||
replyHistory,
|
replyHistory,
|
||||||
latest ? .single(nil) : self.chatDisplayNode.historyNode.topVisibleMessageRange.get()
|
referenceMessage
|
||||||
)
|
)
|
||||||
|> map { update, topVisibleMessageRange -> ChatPinnedMessage? in
|
|> map { update, referenceMessage -> ChatPinnedMessage? in
|
||||||
var message: ChatPinnedMessage?
|
var message: ChatPinnedMessage?
|
||||||
switch update {
|
switch update {
|
||||||
case .Loading:
|
case .Loading:
|
||||||
@ -3278,9 +3305,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var matches = false
|
var matches = false
|
||||||
if message == nil {
|
if message == nil {
|
||||||
matches = true
|
matches = true
|
||||||
} else if let topVisibleMessageRange = topVisibleMessageRange {
|
} else if let referenceMessage = referenceMessage {
|
||||||
if entry.message.id <= topVisibleMessageRange.upperBound {
|
if referenceMessage.isScrolled {
|
||||||
matches = true
|
if entry.message.id < referenceMessage.id {
|
||||||
|
matches = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if entry.message.id <= referenceMessage.id {
|
||||||
|
matches = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
matches = true
|
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
|
self.chatDisplayNode.peerView = self.peerView
|
||||||
|
|
||||||
let initialData = self.chatDisplayNode.historyNode.initialData
|
let initialData = self.chatDisplayNode.historyNode.initialData
|
||||||
@ -3645,6 +3686,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId)
|
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId)
|
||||||
controllerInteraction.highlightedState = highlightedState
|
controllerInteraction.highlightedState = highlightedState
|
||||||
strongSelf.updateItemNodesHighlightedStates(animated: false)
|
strongSelf.updateItemNodesHighlightedStates(animated: false)
|
||||||
|
strongSelf.scrolledToMessageId.set(index.id)
|
||||||
|
|
||||||
strongSelf.messageContextDisposable.set((Signal<Void, NoError>.complete() |> delay(0.7, queue: Queue.mainQueue())).start(completed: {
|
strongSelf.messageContextDisposable.set((Signal<Void, NoError>.complete() |> delay(0.7, queue: Queue.mainQueue())).start(completed: {
|
||||||
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
|
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 strongSelf = self {
|
||||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||||
var canManagePin = false
|
var canManagePin = false
|
||||||
@ -4896,7 +4938,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canManagePin {
|
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 {
|
if let strongSelf = self {
|
||||||
let disposable: MetaDisposable
|
let disposable: MetaDisposable
|
||||||
if let current = strongSelf.unpinMessageDisposable {
|
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())
|
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 {
|
} else {
|
||||||
if let pinnedMessage = strongSelf.presentationInterfaceState.pinnedMessage {
|
if let pinnedMessage = strongSelf.presentationInterfaceState.pinnedMessage {
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||||
|
|||||||
@ -526,6 +526,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
var maxVisibleMessageIndexUpdated: ((MessageIndex) -> Void)?
|
var maxVisibleMessageIndexUpdated: ((MessageIndex) -> Void)?
|
||||||
|
|
||||||
var scrolledToIndex: ((MessageHistoryAnchorIndex) -> Void)?
|
var scrolledToIndex: ((MessageHistoryAnchorIndex) -> Void)?
|
||||||
|
var beganDragging: (() -> Void)?
|
||||||
|
|
||||||
private let hasVisiblePlayableItemNodesPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let hasVisiblePlayableItemNodesPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
var hasVisiblePlayableItemNodes: Signal<Bool, NoError> {
|
var hasVisiblePlayableItemNodes: Signal<Bool, NoError> {
|
||||||
@ -1121,6 +1122,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
self.beganInteractiveDragging = { [weak self] in
|
self.beganInteractiveDragging = { [weak self] in
|
||||||
self?.isInteractivelyScrollingValue = true
|
self?.isInteractivelyScrollingValue = true
|
||||||
self?.isInteractivelyScrollingPromise.set(true)
|
self?.isInteractivelyScrollingPromise.set(true)
|
||||||
|
self?.beganDragging?()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.didEndScrolling = { [weak self] in
|
self.didEndScrolling = { [weak self] in
|
||||||
|
|||||||
@ -664,7 +664,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
|||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Unpin, icon: { theme in
|
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)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unpin"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
interfaceInteraction.unpinMessage(pinnedSelectedMessageId)
|
interfaceInteraction.unpinMessage(pinnedSelectedMessageId, false)
|
||||||
f(.default)
|
f(.default)
|
||||||
})))
|
})))
|
||||||
} else {
|
} 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 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 viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||||
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
|
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:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -609,9 +609,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
for contentNode in strongSelf.contentNodes {
|
for contentNode in strongSelf.contentNodes {
|
||||||
var translatedPoint: CGPoint?
|
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) {
|
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)
|
contentNode.updateTouchesAtPoint(translatedPoint)
|
||||||
}
|
}
|
||||||
@ -905,7 +905,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||||
replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode),
|
replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode),
|
||||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)),
|
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?,
|
currentShareButtonNode: HighlightableButtonNode?,
|
||||||
layoutConstants: ChatMessageItemLayoutConstants,
|
layoutConstants: ChatMessageItemLayoutConstants,
|
||||||
currentItem: ChatMessageItem?,
|
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 {
|
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)
|
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
|
||||||
switch tapAction {
|
switch tapAction {
|
||||||
@ -2982,9 +2982,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
var tapMessage: Message? = item.content.firstMessage
|
var tapMessage: Message? = item.content.firstMessage
|
||||||
var selectAll = true
|
var selectAll = true
|
||||||
loop: for contentNode in self.contentNodes {
|
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) {
|
if !convertedNodeFrame.contains(location) {
|
||||||
continue loop
|
continue loop
|
||||||
} else if contentNode is ChatMessageMediaBubbleContentNode {
|
} else if contentNode is ChatMessageMediaBubbleContentNode {
|
||||||
|
|||||||
@ -196,7 +196,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
if let statusType = statusType {
|
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
|
statusSize = size
|
||||||
statusApply = apply
|
statusApply = apply
|
||||||
}
|
}
|
||||||
|
|||||||
@ -180,7 +180,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.dateNode)
|
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)
|
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
||||||
|
|
||||||
var checkReadNode = self.checkReadNode
|
var checkReadNode = self.checkReadNode
|
||||||
@ -200,7 +200,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
let previousLayoutSize = self.layoutSize
|
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
|
let dateColor: UIColor
|
||||||
var backgroundImage: UIImage?
|
var backgroundImage: UIImage?
|
||||||
var outgoingStatus: ChatMessageDateAndStatusOutgoingType?
|
var outgoingStatus: ChatMessageDateAndStatusOutgoingType?
|
||||||
@ -234,6 +234,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if replyCount != 0 {
|
if replyCount != 0 {
|
||||||
repliesImage = graphics.incomingDateAndStatusRepliesIcon
|
repliesImage = graphics.incomingDateAndStatusRepliesIcon
|
||||||
|
} else if isPinned {
|
||||||
|
repliesImage = graphics.incomingDateAndStatusPinnedIcon
|
||||||
}
|
}
|
||||||
case let .BubbleOutgoing(status):
|
case let .BubbleOutgoing(status):
|
||||||
dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||||
@ -248,6 +250,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if replyCount != 0 {
|
if replyCount != 0 {
|
||||||
repliesImage = graphics.outgoingDateAndStatusRepliesIcon
|
repliesImage = graphics.outgoingDateAndStatusRepliesIcon
|
||||||
|
} else if isPinned {
|
||||||
|
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
|
||||||
}
|
}
|
||||||
case .ImageIncoming:
|
case .ImageIncoming:
|
||||||
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
||||||
@ -262,6 +266,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if replyCount != 0 {
|
if replyCount != 0 {
|
||||||
repliesImage = graphics.mediaRepliesIcon
|
repliesImage = graphics.mediaRepliesIcon
|
||||||
|
} else if isPinned {
|
||||||
|
repliesImage = graphics.mediaPinnedIcon
|
||||||
}
|
}
|
||||||
case let .ImageOutgoing(status):
|
case let .ImageOutgoing(status):
|
||||||
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
||||||
@ -277,6 +283,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if replyCount != 0 {
|
if replyCount != 0 {
|
||||||
repliesImage = graphics.mediaRepliesIcon
|
repliesImage = graphics.mediaRepliesIcon
|
||||||
|
} else if isPinned {
|
||||||
|
repliesImage = graphics.mediaPinnedIcon
|
||||||
}
|
}
|
||||||
case .FreeIncoming:
|
case .FreeIncoming:
|
||||||
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
||||||
@ -292,6 +300,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if replyCount != 0 {
|
if replyCount != 0 {
|
||||||
repliesImage = graphics.freeRepliesIcon
|
repliesImage = graphics.freeRepliesIcon
|
||||||
|
} else if isPinned {
|
||||||
|
repliesImage = graphics.freePinnedIcon
|
||||||
}
|
}
|
||||||
case let .FreeOutgoing(status):
|
case let .FreeOutgoing(status):
|
||||||
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
||||||
@ -308,6 +318,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if replyCount != 0 {
|
if replyCount != 0 {
|
||||||
repliesImage = graphics.freeRepliesIcon
|
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)))
|
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
|
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
|
||||||
replyCountLayoutAndApply = layoutAndApply
|
replyCountLayoutAndApply = layoutAndApply
|
||||||
|
} else if isPinned {
|
||||||
|
reactionInset += 12.0
|
||||||
}
|
}
|
||||||
|
|
||||||
leftInset += reactionInset
|
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()
|
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 resultNode: ChatMessageDateAndStatusNode
|
||||||
let resultSizeAndApply: (CGSize, (Bool) -> Void)
|
let resultSizeAndApply: (CGSize, (Bool) -> Void)
|
||||||
if let node = node, let currentLayout = currentLayout {
|
if let node = node, let currentLayout = currentLayout {
|
||||||
resultNode = node
|
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 {
|
} else {
|
||||||
resultNode = ChatMessageDateAndStatusNode()
|
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
|
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 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
|
statusSize = size
|
||||||
statusApply = apply
|
statusApply = apply
|
||||||
}
|
}
|
||||||
|
|||||||
@ -285,7 +285,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
maxDateAndStatusWidth = width - videoFrame.midX - 85.0
|
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 contentSize = imageSize
|
||||||
var dateAndStatusOverflow = false
|
var dateAndStatusOverflow = false
|
||||||
|
|||||||
@ -246,7 +246,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
if let statusType = statusType {
|
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
|
statusSize = size
|
||||||
statusApply = apply
|
statusApply = apply
|
||||||
}
|
}
|
||||||
|
|||||||
@ -231,7 +231,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
if let statusType = statusType {
|
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
|
statusSize = size
|
||||||
statusApply = apply
|
statusApply = apply
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1074,7 +1074,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
if let statusType = statusType {
|
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
|
statusSize = size
|
||||||
statusApply = apply
|
statusApply = apply
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,7 +105,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
if let statusType = statusType {
|
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
|
statusSize = size
|
||||||
statusApply = apply
|
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 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 viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||||
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
|
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
|
||||||
|
|||||||
@ -169,7 +169,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var statusApply: ((Bool) -> Void)?
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
if let statusType = statusType {
|
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
|
statusSize = size
|
||||||
statusApply = apply
|
statusApply = apply
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,7 +95,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
|
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
|
||||||
let unblockPeer: () -> Void
|
let unblockPeer: () -> Void
|
||||||
let pinMessage: (MessageId) -> Void
|
let pinMessage: (MessageId) -> Void
|
||||||
let unpinMessage: (MessageId) -> Void
|
let unpinMessage: (MessageId, Bool) -> Void
|
||||||
let shareAccountContact: () -> Void
|
let shareAccountContact: () -> Void
|
||||||
let reportPeer: () -> Void
|
let reportPeer: () -> Void
|
||||||
let presentPeerContact: () -> Void
|
let presentPeerContact: () -> Void
|
||||||
@ -171,7 +171,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
|
sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
|
||||||
unblockPeer: @escaping () -> Void,
|
unblockPeer: @escaping () -> Void,
|
||||||
pinMessage: @escaping (MessageId) -> Void,
|
pinMessage: @escaping (MessageId) -> Void,
|
||||||
unpinMessage: @escaping (MessageId) -> Void,
|
unpinMessage: @escaping (MessageId, Bool) -> Void,
|
||||||
shareAccountContact: @escaping () -> Void,
|
shareAccountContact: @escaping () -> Void,
|
||||||
reportPeer: @escaping () -> Void,
|
reportPeer: @escaping () -> Void,
|
||||||
presentPeerContact: @escaping () -> Void,
|
presentPeerContact: @escaping () -> Void,
|
||||||
|
|||||||
@ -362,7 +362,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
|
|
||||||
@objc func closePressed() {
|
@objc func closePressed() {
|
||||||
if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
|
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
|
return false
|
||||||
}, unblockPeer: {
|
}, unblockPeer: {
|
||||||
}, pinMessage: { _ in
|
}, pinMessage: { _ in
|
||||||
}, unpinMessage: { _ in
|
}, unpinMessage: { _, _ in
|
||||||
}, shareAccountContact: {
|
}, shareAccountContact: {
|
||||||
}, reportPeer: {
|
}, reportPeer: {
|
||||||
}, presentPeerContact: {
|
}, presentPeerContact: {
|
||||||
|
|||||||
@ -130,7 +130,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
|||||||
} else if params.standalone {
|
} else if params.standalone {
|
||||||
location = .recentActions(params.message)
|
location = .recentActions(params.message)
|
||||||
} else {
|
} 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
|
playerType = .voice
|
||||||
} else if file.isMusic && params.message.tags.contains(.music) {
|
} else if file.isMusic && params.message.tags.contains(.music) {
|
||||||
@ -139,7 +139,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
|||||||
} else if params.standalone {
|
} else if params.standalone {
|
||||||
location = .recentActions(params.message)
|
location = .recentActions(params.message)
|
||||||
} else {
|
} 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
|
playerType = .music
|
||||||
} else {
|
} else {
|
||||||
@ -150,7 +150,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
|||||||
}
|
}
|
||||||
playerType = (file.isVoice || file.isInstantVideo) ? .voice : .music
|
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
|
return true
|
||||||
case let .gallery(gallery):
|
case let .gallery(gallery):
|
||||||
params.dismissInput()
|
params.dismissInput()
|
||||||
|
|||||||
@ -52,6 +52,7 @@ import PeerInfoUI
|
|||||||
import ListMessageItem
|
import ListMessageItem
|
||||||
import GalleryData
|
import GalleryData
|
||||||
import ChatInterfaceState
|
import ChatInterfaceState
|
||||||
|
import TelegramVoip
|
||||||
|
|
||||||
protocol PeerInfoScreenItem: class {
|
protocol PeerInfoScreenItem: class {
|
||||||
var id: AnyHashable { get }
|
var id: AnyHashable { get }
|
||||||
@ -405,7 +406,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||||||
return false
|
return false
|
||||||
}, unblockPeer: {
|
}, unblockPeer: {
|
||||||
}, pinMessage: { _ in
|
}, pinMessage: { _ in
|
||||||
}, unpinMessage: { _ in
|
}, unpinMessage: { _, _ in
|
||||||
}, shareAccountContact: {
|
}, shareAccountContact: {
|
||||||
}, reportPeer: {
|
}, reportPeer: {
|
||||||
}, presentPeerContact: {
|
}, presentPeerContact: {
|
||||||
@ -3153,6 +3154,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
self.controller?.present(shareController, in: .window(.root))
|
self.controller?.present(shareController, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let groupCallDisposable = MetaDisposable()
|
||||||
|
private var groupCall: GroupCallContext?
|
||||||
|
|
||||||
private func requestCall(isVideo: Bool) {
|
private func requestCall(isVideo: Bool) {
|
||||||
guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else {
|
guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -307,9 +307,9 @@ private struct PlaybackStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
||||||
private let postbox: Postbox
|
private let context: AccountContext
|
||||||
private let network: Network
|
|
||||||
private let messagesLocation: PeerMessagesPlaylistLocation
|
private let messagesLocation: PeerMessagesPlaylistLocation
|
||||||
|
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?
|
||||||
|
|
||||||
var location: SharedMediaPlaylistLocation {
|
var location: SharedMediaPlaylistLocation {
|
||||||
return self.messagesLocation
|
return self.messagesLocation
|
||||||
@ -338,13 +338,13 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
return self.stateValue.get()
|
return self.stateValue.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(postbox: Postbox, network: Network, location: PeerMessagesPlaylistLocation) {
|
init(context: AccountContext, location: PeerMessagesPlaylistLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?) {
|
||||||
assert(Queue.mainQueue().isCurrent())
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
self.id = location.playlistId
|
self.id = location.playlistId
|
||||||
|
|
||||||
self.postbox = postbox
|
self.context = context
|
||||||
self.network = network
|
self.chatLocationContextHolder = chatLocationContextHolder
|
||||||
self.messagesLocation = location
|
self.messagesLocation = location
|
||||||
|
|
||||||
switch self.messagesLocation {
|
switch self.messagesLocation {
|
||||||
@ -446,7 +446,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
self.currentlyObservedMessageId = item?.message.id
|
self.currentlyObservedMessageId = item?.message.id
|
||||||
if let id = item?.message.id {
|
if let id = item?.message.id {
|
||||||
let key: PostboxViewKey = .messages(Set([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
|
|> filter { views in
|
||||||
if let view = views.views[key] as? MessagesView {
|
if let view = views.views[key] as? MessagesView {
|
||||||
if !view.messages.isEmpty {
|
if !view.messages.isEmpty {
|
||||||
@ -479,15 +479,15 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
switch anchor {
|
switch anchor {
|
||||||
case let .messageId(messageId):
|
case let .messageId(messageId):
|
||||||
switch self.messagesLocation {
|
switch self.messagesLocation {
|
||||||
case let .messages(peerId, tagMask, _):
|
case let .messages(chatLocation, tagMask, _):
|
||||||
let historySignal = self.postbox.messageAtId(messageId)
|
let historySignal = self.context.account.postbox.messageAtId(messageId)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in
|
|> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in
|
||||||
guard let message = message else {
|
guard let message = message else {
|
||||||
return .single(nil)
|
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
|
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||||
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
|
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
|
||||||
return .single((message, aroundMessages))
|
return .single((message, aroundMessages))
|
||||||
@ -539,7 +539,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
default:
|
default:
|
||||||
self.navigationDisposable.set((self.postbox.messageAtId(messageId)
|
self.navigationDisposable.set((self.context.account.postbox.messageAtId(messageId)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] message in
|
|> deliverOnMainQueue).start(next: { [weak self] message in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -559,7 +559,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
}
|
}
|
||||||
case let .index(index):
|
case let .index(index):
|
||||||
switch self.messagesLocation {
|
switch self.messagesLocation {
|
||||||
case let .messages(peerId, tagMask, _):
|
case let .messages(chatLocation, tagMask, _):
|
||||||
let inputIndex: Signal<MessageIndex, NoError>
|
let inputIndex: Signal<MessageIndex, NoError>
|
||||||
let looping = self.looping
|
let looping = self.looping
|
||||||
switch self.order {
|
switch self.order {
|
||||||
@ -567,7 +567,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
inputIndex = .single(index)
|
inputIndex = .single(index)
|
||||||
case .random:
|
case .random:
|
||||||
var playbackStack = self.playbackStack
|
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 {
|
if case let .random(previous) = navigation, previous {
|
||||||
let _ = playbackStack.pop()
|
let _ = playbackStack.pop()
|
||||||
while true {
|
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
|
let historySignal = inputIndex
|
||||||
|> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in
|
|> 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
|
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||||
let position: NavigatedMessageFromViewPosition
|
let position: NavigatedMessageFromViewPosition
|
||||||
switch navigation {
|
switch navigation {
|
||||||
@ -615,7 +615,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
} else {
|
} else {
|
||||||
viewIndex = .lowerBound
|
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
|
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||||
let position: NavigatedMessageFromViewPosition
|
let position: NavigatedMessageFromViewPosition
|
||||||
switch navigation {
|
switch navigation {
|
||||||
@ -657,7 +657,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
case .singleMessage:
|
case .singleMessage:
|
||||||
self.navigationDisposable.set((self.postbox.messageAtId(index.id)
|
self.navigationDisposable.set((self.context.account.postbox.messageAtId(index.id)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] message in
|
|> deliverOnMainQueue).start(next: { [weak self] message in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -815,7 +815,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
default:
|
default:
|
||||||
break
|
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
|
#ifndef WEBRTC_IOS
|
||||||
#import "OngoingCallThreadLocalContext.h"
|
#import "OngoingCallThreadLocalContext.h"
|
||||||
#else
|
#else
|
||||||
#import <TgVoip/OngoingCallThreadLocalContext.h>
|
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
||||||
#endif
|
#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 = [
|
webrtc_libs = [
|
||||||
"libwebrtc.a",
|
"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