Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-11-26 13:29:42 +04:00
commit f97198d906
20 changed files with 775 additions and 137 deletions

View File

@ -51,7 +51,6 @@ BAZEL_OPTIONS=(\
--spawn_strategy=standalone \
--strategy=SwiftCompile=standalone \
--features=swift.enable_batch_mode \
--apple_generate_dsym \
--swiftcopt=-j${CORE_COUNT_MINUS_ONE} \
)

View File

@ -174,6 +174,25 @@ public struct PresentationGroupCallState: Equatable {
}
}
public struct PresentationGroupCallSummaryState: Equatable {
public var info: GroupCallInfo
public var participantCount: Int
public var callState: PresentationGroupCallState
public var topParticipants: [GroupCallParticipantsContext.Participant]
public init(
info: GroupCallInfo,
participantCount: Int,
callState: PresentationGroupCallState,
topParticipants: [GroupCallParticipantsContext.Participant]
) {
self.info = info
self.participantCount = participantCount
self.callState = callState
self.topParticipants = topParticipants
}
}
public struct PresentationGroupCallMemberState: Equatable {
public var ssrc: UInt32
public var muteState: GroupCallParticipantsContext.Participant.MuteState?
@ -197,9 +216,11 @@ public protocol PresentationGroupCall: class {
var canBeRemoved: Signal<Bool, NoError> { get }
var state: Signal<PresentationGroupCallState, NoError> { get }
var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> { get }
var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> { get }
var audioLevels: Signal<[(PeerId, Float)], NoError> { get }
var myAudioLevel: Signal<Float, NoError> { get }
var isMuted: Signal<Bool, NoError> { get }
func leave() -> Signal<Bool, NoError>
@ -218,5 +239,5 @@ public protocol PresentationCallManager: class {
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get }
func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult
func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool) -> RequestOrJoinGroupCallResult
}

View File

@ -146,9 +146,7 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
super.init()
}
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, animated: Bool, synchronousLoad: Bool) -> CGSize {
let itemSize = CGSize(width: 30.0, height: 30.0)
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, itemSize: CGSize = CGSize(width: 30.0, height: 30.0), animated: Bool, synchronousLoad: Bool) -> CGSize {
var contentWidth: CGFloat = 0.0
let contentHeight: CGFloat = itemSize.height

View File

@ -171,7 +171,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.tabContainerNode = ChatListFilterTabContainerNode()
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary)
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: .all)
self.tabBarItemContextActionType = .always

View File

@ -391,7 +391,7 @@ open class NavigationController: UINavigationController, ContainableController,
}
if let inCallStatusBar = self.inCallStatusBar {
let inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(40.0, layout.safeInsets.top)))
let inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(layout.statusBarHeight ?? 0.0, max(40.0, layout.safeInsets.top))))
if inCallStatusBar.frame.isEmpty {
inCallStatusBar.frame = inCallStatusBarFrame
} else {

View File

@ -33,7 +33,7 @@ public final class HashtagSearchController: TelegramBaseController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none)
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style

View File

@ -6,9 +6,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[571523412] = { return $0.readDouble() }
dict[-1255641564] = { return parseString($0) }
dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($0) }
dict[-1331534976] = { return Api.GroupCall.parse_groupCallPrivate($0) }
dict[1435512961] = { return Api.GroupCall.parse_groupCall($0) }
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
dict[1435512961] = { return Api.GroupCall.parse_groupCall($0) }
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }

View File

@ -1910,19 +1910,18 @@ public struct messages {
}
public extension Api {
public enum GroupCall: TypeConstructorDescription {
case groupCallPrivate(id: Int64, accessHash: Int64, participantsCount: Int32)
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, params: Api.DataJSON?, version: Int32)
case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32)
case groupCall(flags: Int32, id: Int64, accessHash: Int64, participantsCount: Int32, params: Api.DataJSON?, version: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCallPrivate(let id, let accessHash, let participantsCount):
case .groupCallDiscarded(let id, let accessHash, let duration):
if boxed {
buffer.appendInt32(-1331534976)
buffer.appendInt32(2004925620)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
serializeInt32(participantsCount, buffer: buffer, boxed: false)
serializeInt32(duration, buffer: buffer, boxed: false)
break
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
if boxed {
@ -1935,29 +1934,19 @@ public extension Api {
if Int(flags) & Int(1 << 0) != 0 {params!.serialize(buffer, true)}
serializeInt32(version, buffer: buffer, boxed: false)
break
case .groupCallDiscarded(let id, let accessHash, let duration):
if boxed {
buffer.appendInt32(2004925620)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
serializeInt32(duration, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCallPrivate(let id, let accessHash, let participantsCount):
return ("groupCallPrivate", [("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount)])
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount), ("params", params), ("version", version)])
case .groupCallDiscarded(let id, let accessHash, let duration):
return ("groupCallDiscarded", [("id", id), ("accessHash", accessHash), ("duration", duration)])
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("participantsCount", participantsCount), ("params", params), ("version", version)])
}
}
public static func parse_groupCallPrivate(_ reader: BufferReader) -> GroupCall? {
public static func parse_groupCallDiscarded(_ reader: BufferReader) -> GroupCall? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
@ -1968,7 +1957,7 @@ public extension Api {
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.GroupCall.groupCallPrivate(id: _1!, accessHash: _2!, participantsCount: _3!)
return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!)
}
else {
return nil
@ -2002,23 +1991,6 @@ public extension Api {
return nil
}
}
public static func parse_groupCallDiscarded(_ reader: BufferReader) -> GroupCall? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!)
}
else {
return nil
}
}
}
public enum InputGeoPoint: TypeConstructorDescription {

View File

@ -20,6 +20,8 @@ swift_library(
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/Markdown:Markdown",
"//submodules/TelegramCallsUI:TelegramCallsUI",
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
],
visibility = [
"//visibility:public",

View File

@ -0,0 +1,326 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import SyncCore
import Postbox
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import SwiftSignalKit
import AnimatedAvatarSetNode
private let titleFont = Font.semibold(15.0)
private let subtitleFont = Font.regular(13.0)
final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
private let context: AccountContext
private var theme: PresentationTheme
private var strings: PresentationStrings
private let tapAction: () -> Void
private let contentNode: ASDisplayNode
private let tapButton: HighlightTrackingButtonNode
private let joinButton: HighlightableButtonNode
private let joinButtonTitleNode: ImmediateTextNode
private let joinButtonBackgroundNode: ASImageNode
private let micButton: HighlightTrackingButtonNode
private let micButtonForegroundMutedNode: ASImageNode
private let micButtonForegroundUnmutedNode: ASImageNode
private let micButtonBackgroundNode: ASImageNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let muteIconNode: ASImageNode
private let avatarsContext: AnimatedAvatarSetContext
private var avatarsContent: AnimatedAvatarSetContext.Content?
private let avatarsNode: AnimatedAvatarSetNode
private let separatorNode: ASDisplayNode
private let membersDisposable = MetaDisposable()
private let isMutedDisposable = MetaDisposable()
private var currentData: GroupCallPanelData?
private var validLayout: (CGSize, CGFloat, CGFloat)?
init(context: AccountContext, presentationData: PresentationData, tapAction: @escaping () -> Void) {
self.context = context
self.theme = presentationData.theme
self.strings = presentationData.strings
self.tapAction = tapAction
self.contentNode = ASDisplayNode()
self.tapButton = HighlightTrackingButtonNode()
self.joinButton = HighlightableButtonNode()
self.joinButtonTitleNode = ImmediateTextNode()
self.joinButtonBackgroundNode = ASImageNode()
self.micButton = HighlightTrackingButtonNode()
self.micButtonForegroundMutedNode = ASImageNode()
self.micButtonForegroundUnmutedNode = ASImageNode()
self.micButtonBackgroundNode = ASImageNode()
self.titleNode = ImmediateTextNode()
self.textNode = ImmediateTextNode()
self.muteIconNode = ASImageNode()
self.avatarsContext = AnimatedAvatarSetContext()
self.avatarsNode = AnimatedAvatarSetNode()
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
super.init()
self.addSubnode(self.contentNode)
self.tapButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
strongSelf.titleNode.alpha = 0.4
strongSelf.textNode.layer.removeAnimation(forKey: "opacity")
strongSelf.textNode.alpha = 0.4
} else {
strongSelf.titleNode.alpha = 1.0
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.textNode.alpha = 1.0
strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.contentNode.addSubnode(self.titleNode)
self.contentNode.addSubnode(self.textNode)
self.contentNode.addSubnode(self.muteIconNode)
self.contentNode.addSubnode(self.avatarsNode)
self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
self.contentNode.addSubnode(self.tapButton)
self.joinButton.addSubnode(self.joinButtonBackgroundNode)
self.joinButton.addSubnode(self.joinButtonTitleNode)
self.contentNode.addSubnode(self.joinButton)
self.joinButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
self.micButton.addSubnode(self.micButtonBackgroundNode)
self.micButton.addSubnode(self.micButtonForegroundMutedNode)
self.micButton.addSubnode(self.micButtonForegroundUnmutedNode)
self.contentNode.addSubnode(self.micButton)
self.micButton.addTarget(self, action: #selector(self.micTapped), forControlEvents: [.touchUpInside])
self.contentNode.addSubnode(self.separatorNode)
self.updatePresentationData(presentationData)
}
deinit {
self.membersDisposable.dispose()
self.isMutedDisposable.dispose()
}
override func didLoad() {
super.didLoad()
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.micButtonPressGesture(_:)))
longTapRecognizer.minimumPressDuration = 0.01
self.micButton.view.addGestureRecognizer(longTapRecognizer)
}
@objc private func tapped() {
self.tapAction()
}
@objc private func micTapped() {
guard let call = self.currentData?.groupCall else {
return
}
call.toggleIsMuted()
}
@objc private func micButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard let call = self.currentData?.groupCall else {
return
}
switch gestureRecognizer.state {
case .began:
call.setIsMuted(false)
case .ended, .cancelled:
call.setIsMuted(true)
default:
break
}
}
func updatePresentationData(_ presentationData: PresentationData) {
self.theme = presentationData.theme
self.strings = presentationData.strings
self.contentNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor
self.theme = presentationData.theme
self.separatorNode.backgroundColor = presentationData.theme.chat.historyNavigation.strokeColor
self.joinButtonTitleNode.attributedText = NSAttributedString(string: presentationData.strings.Channel_JoinChannel.uppercased(), font: Font.semibold(15.0), textColor: presentationData.theme.chat.inputPanel.actionControlForegroundColor)
self.joinButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: presentationData.theme.chat.inputPanel.actionControlFillColor)
//TODO:localize
self.micButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: UIColor(rgb: 0x30B251))
self.micButtonForegroundMutedNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: .white)
self.micButtonForegroundUnmutedNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: .white)
//TODO:localize
self.titleNode.attributedText = NSAttributedString(string: "Voice Chat", font: Font.semibold(15.0), textColor: presentationData.theme.chat.inputPanel.primaryTextColor)
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: presentationData.theme.chat.inputPanel.secondaryTextColor)
self.muteIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(presentationData.theme)
}
func update(data: GroupCallPanelData) {
let previousData = self.currentData
self.currentData = data
if previousData?.groupCall !== data.groupCall {
let membersText: String
if data.participantCount == 0 {
membersText = self.strings.PeopleNearby_NoMembers
} else {
membersText = self.strings.Conversation_StatusMembers(Int32(data.participantCount))
}
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
self.membersDisposable.set(nil)
self.isMutedDisposable.set(nil)
if let groupCall = data.groupCall {
self.membersDisposable.set((groupCall.summaryState
|> deliverOnMainQueue).start(next: { [weak self] summaryState in
guard let strongSelf = self, let summaryState = summaryState else {
return
}
let membersText: String
if summaryState.participantCount == 0 {
membersText = strongSelf.strings.PeopleNearby_NoMembers
} else {
membersText = strongSelf.strings.Conversation_StatusMembers(Int32(summaryState.participantCount))
}
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
if let (size, leftInset, rightInset) = strongSelf.validLayout {
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
}
}))
self.isMutedDisposable.set((groupCall.isMuted
|> deliverOnMainQueue).start(next: { [weak self] isMuted in
guard let strongSelf = self else {
return
}
strongSelf.micButtonForegroundMutedNode.isHidden = !isMuted
strongSelf.micButtonForegroundUnmutedNode.isHidden = isMuted
}))
}
} else if data.groupCall == nil {
let membersText: String
if data.participantCount == 0 {
membersText = self.strings.PeopleNearby_NoMembers
} else {
membersText = self.strings.Conversation_StatusMembers(Int32(data.participantCount))
}
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
}
if let (size, leftInset, rightInset) = self.validLayout {
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
}
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, leftInset, rightInset)
let panelHeight = size.height
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: size))
self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width - 7.0 - 36.0 - 7.0, height: panelHeight))
if let avatarsContent = self.avatarsContent {
let avatarsSize = self.avatarsNode.update(context: self.context, content: avatarsContent, itemSize: CGSize(width: 32.0, height: 32.0), animated: true, synchronousLoad: true)
transition.updateFrame(node: self.avatarsNode, frame: CGRect(origin: CGPoint(x: 7.0, y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize))
}
let joinButtonTitleSize = self.joinButtonTitleNode.updateLayout(CGSize(width: 150.0, height: .greatestFiniteMagnitude))
let joinButtonSize = CGSize(width: joinButtonTitleSize.width + 20.0, height: 28.0)
let joinButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 7.0 - joinButtonSize.width, y: floor((panelHeight - joinButtonSize.height) / 2.0)), size: joinButtonSize)
transition.updateFrame(node: self.joinButton, frame: joinButtonFrame)
transition.updateFrame(node: self.joinButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: joinButtonFrame.size))
transition.updateFrame(node: self.joinButtonTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((joinButtonFrame.width - joinButtonTitleSize.width) / 2.0), y: floorToScreenPixels((joinButtonFrame.height - joinButtonTitleSize.height) / 2.0)), size: joinButtonTitleSize))
let micButtonSize = CGSize(width: 36.0, height: 36.0)
let micButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 7.0 - micButtonSize.width, y: floor((panelHeight - micButtonSize.height) / 2.0)), size: micButtonSize)
transition.updateFrame(node: self.micButton, frame: micButtonFrame)
transition.updateFrame(node: self.micButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: micButtonFrame.size))
if let image = self.micButtonForegroundMutedNode.image {
transition.updateFrame(node: self.micButtonForegroundMutedNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - image.size.width) / 2.0), y: floor((micButtonFrame.height - image.size.height) / 2.0)), size: image.size))
}
if let image = self.micButtonForegroundUnmutedNode.image {
transition.updateFrame(node: self.micButtonForegroundUnmutedNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - image.size.width) / 2.0), y: floor((micButtonFrame.height - image.size.height) / 2.0)), size: image.size))
}
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
let textSize = self.textNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: 10.0), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: textSize))
if let image = self.muteIconNode.image {
transition.updateFrame(node: self.muteIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + 5.0), size: image.size))
}
self.muteIconNode.isHidden = self.currentData?.groupCall != nil
self.joinButton.isHidden = self.currentData?.groupCall != nil
self.micButton.isHidden = self.currentData?.groupCall == nil
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
}
func animateIn(_ transition: ContainedViewLayoutTransition) {
self.clipsToBounds = true
let contentPosition = self.contentNode.layer.position
transition.animatePosition(node: self.contentNode, from: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0), completion: { [weak self] _ in
self?.clipsToBounds = false
})
}
func animateOut(_ transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
self.clipsToBounds = true
let contentPosition = self.contentNode.layer.position
transition.animatePosition(node: self.contentNode, to: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0), removeOnCompletion: false, completion: { [weak self] _ in
self?.clipsToBounds = false
completion()
})
}
}

View File

@ -25,6 +25,34 @@ public enum LocationBroadcastPanelSource {
case peer(PeerId)
}
public enum GroupCallPanelSource {
case none
case all
case peer(PeerId)
}
final class GroupCallPanelData {
let peerId: PeerId
let info: GroupCallInfo
let topParticipants: [GroupCallParticipantsContext.Participant]
let participantCount: Int
let groupCall: PresentationGroupCall?
init(
peerId: PeerId,
info: GroupCallInfo,
topParticipants: [GroupCallParticipantsContext.Participant],
participantCount: Int,
groupCall: PresentationGroupCall?
) {
self.peerId = peerId
self.info = info
self.topParticipants = topParticipants
self.participantCount = participantCount
self.groupCall = groupCall
}
}
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
let presentImpl: (Message?) -> Void = { [weak controller] message in
if let message = message, let strongController = controller {
@ -64,9 +92,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
public let locationBroadcastPanelSource: LocationBroadcastPanelSource
public let groupCallPanelSource: GroupCallPanelSource
private var mediaStatusDisposable: Disposable?
private var locationBroadcastDisposable: Disposable?
private var currentGroupCallDisposable: Disposable?
public private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
private var playlistLocation: SharedMediaPlaylistLocation?
@ -81,6 +111,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
private var locationBroadcastMessages: [MessageId: Message]?
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
private var groupCallPanelData: GroupCallPanelData?
private var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
private var dismissingPanel: ASDisplayNode?
private var presentationData: PresentationData
@ -101,6 +134,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
public var additionalHeight: CGFloat {
var height: CGFloat = 0.0
if let _ = self.groupCallAccessoryPanel {
height += 50.0
}
if let _ = self.mediaAccessoryPanel {
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
}
@ -114,11 +150,12 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
return super.navigationHeight
}
public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?, mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility, locationBroadcastPanelSource: LocationBroadcastPanelSource) {
public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?, mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility, locationBroadcastPanelSource: LocationBroadcastPanelSource, groupCallPanelSource: GroupCallPanelSource) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.mediaAccessoryPanelVisibility = mediaAccessoryPanelVisibility
self.locationBroadcastPanelSource = locationBroadcastPanelSource
self.groupCallPanelSource = groupCallPanelSource
super.init(navigationBarPresentationData: navigationBarPresentationData)
@ -250,6 +287,93 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
}
}
if let callManager = context.sharedContext.callManager {
switch groupCallPanelSource {
case .none:
break
default:
let currentGroupCall: Signal<GroupCallPanelData?, NoError> = callManager.currentGroupCallSignal
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs?.internalId == rhs?.internalId
})
|> mapToSignal { call -> Signal<GroupCallPanelData?, NoError> in
guard let call = call else {
return .single(nil)
}
return call.summaryState
|> filter { $0 != nil }
|> take(1)
|> map { summary -> GroupCallPanelData? in
guard let summary = summary else {
return nil
}
return GroupCallPanelData(
peerId: call.peerId,
info: summary.info,
topParticipants: summary.topParticipants,
participantCount: summary.participantCount,
groupCall: call
)
}
}
let availableGroupCall: Signal<GroupCallPanelData?, NoError>
if case let .peer(peerId) = groupCallPanelSource {
availableGroupCall = context.account.viewTracker.peerView(peerId)
|> map { peerView -> CachedChannelData.ActiveCall? in
guard let cachedData = peerView.cachedData as? CachedChannelData else {
return nil
}
return cachedData.activeCall
}
|> distinctUntilChanged
|> mapToSignal { activeCall -> Signal<GroupCallPanelData?, NoError> in
guard let activeCall = activeCall else {
return .single(nil)
}
return getCurrentGroupCall(account: context.account, callId: activeCall.id, accessHash: activeCall.accessHash)
|> `catch` { _ -> Signal<GroupCallSummary?, NoError> in
return .single(nil)
}
|> map { summary -> GroupCallPanelData? in
guard let summary = summary else {
return nil
}
return GroupCallPanelData(
peerId: peerId,
info: summary.info,
topParticipants: summary.topParticipants,
participantCount: summary.info.participantCount,
groupCall: nil
)
}
}
} else {
availableGroupCall = .single(nil)
}
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(),
currentGroupCall,
availableGroupCall
).start(next: { [weak self] currentState, availableState in
guard let strongSelf = self else {
return
}
let panelData = currentState ?? availableState
let wasEmpty = strongSelf.groupCallPanelData == nil
strongSelf.groupCallPanelData = panelData
let isEmpty = strongSelf.groupCallPanelData == nil
if wasEmpty != isEmpty {
strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
} else if let groupCallPanelData = strongSelf.groupCallPanelData {
strongSelf.groupCallAccessoryPanel?.update(data: groupCallPanelData)
}
})
}
}
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
@ -269,6 +393,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
deinit {
self.mediaStatusDisposable?.dispose()
self.locationBroadcastDisposable?.dispose()
self.currentGroupCallDisposable?.dispose()
self.presentationDataDisposable?.dispose()
self.playlistPreloadDisposable?.dispose()
}
@ -287,6 +412,52 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
var additionalHeight: CGFloat = 0.0
if let groupCallPanelData = self.groupCallPanelData {
let panelHeight: CGFloat = 50.0
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
additionalHeight += panelHeight
let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel
if let current = self.groupCallAccessoryPanel {
groupCallAccessoryPanel = current
transition.updateFrame(node: groupCallAccessoryPanel, frame: panelFrame)
groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
} else {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
groupCallAccessoryPanel = GroupCallNavigationAccessoryPanel(context: self.context, presentationData: presentationData, tapAction: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.joinGroupCall(
peerId: groupCallPanelData.peerId,
info: groupCallPanelData.info
)
})
if let navigationBar = self.navigationBar {
self.displayNode.insertSubnode(groupCallAccessoryPanel, aboveSubnode: navigationBar)
} else {
self.displayNode.addSubnode(groupCallAccessoryPanel)
}
self.groupCallAccessoryPanel = groupCallAccessoryPanel
groupCallAccessoryPanel.frame = panelFrame
groupCallAccessoryPanel.update(data: groupCallPanelData)
groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
if transition.isAnimated {
groupCallAccessoryPanel.animateIn(transition)
}
}
} else if let groupCallAccessoryPanel = self.groupCallAccessoryPanel {
self.groupCallAccessoryPanel = nil
if transition.isAnimated {
groupCallAccessoryPanel.animateOut(transition, completion: { [weak groupCallAccessoryPanel] in
groupCallAccessoryPanel?.removeFromSupernode()
})
} else {
groupCallAccessoryPanel.removeFromSupernode()
}
}
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
@ -675,4 +846,36 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
}
})]
}
private func joinGroupCall(peerId: PeerId, info: GroupCallInfo) {
let callResult = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId {
self.context.sharedContext.navigateToCurrentCall()
} else {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peerId), currentPeerId.flatMap(transaction.getPeer))
}
|> deliverOnMainQueue).start(next: { [weak self] peer, current in
guard let strongSelf = self else {
return
}
guard let peer = peer else {
return
}
if let current = current {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: true)
}
})]), in: .window(.root))
} else {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}
})
}
}
}
}

View File

@ -592,17 +592,32 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
}
}
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult {
if let currentGroupCall = self.currentGroupCallValue {
return .alreadyInProgress(currentGroupCall.peerId)
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool) -> RequestOrJoinGroupCallResult {
let begin: () -> Void = { [weak self] in
let _ = self?.startGroupCall(accountContext: context, peerId: peerId, initialCall: initialCall).start()
}
if let currentGroupCall = self.currentGroupCallValue {
if endCurrentIfAny {
let endSignal = currentGroupCall.leave()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue
self.startCallDisposable.set(endSignal.start(next: { _ in
begin()
}))
} else {
return .alreadyInProgress(currentGroupCall.peerId)
}
} else {
begin()
}
let _ = self.startGroupCall(accountContext: context, peerId: peerId).start()
return .requested
}
private func startGroupCall(
accountContext: AccountContext,
peerId: PeerId,
initialCall: CachedChannelData.ActiveCall?,
internalId: CallSessionInternalId = CallSessionInternalId()
) -> Signal<Bool, NoError> {
let (presentationData, present, openSettings) = self.getDeviceAccessData()
@ -649,6 +664,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
audioSession: strongSelf.audioSession,
callKitIntegration: nil,
getDeviceAccessData: strongSelf.getDeviceAccessData,
initialCall: initialCall,
internalId: internalId,
peerId: peerId,
peer: nil

View File

@ -41,6 +41,29 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
private struct SummaryInfoState: Equatable {
public var info: GroupCallInfo
public init(
info: GroupCallInfo
) {
self.info = info
}
}
private struct SummaryParticipantsState: Equatable {
public var participantCount: Int
public var topParticipants: [GroupCallParticipantsContext.Participant]
public init(
participantCount: Int,
topParticipants: [GroupCallParticipantsContext.Participant]
) {
self.participantCount = participantCount
self.topParticipants = topParticipants
}
}
public let account: Account
public let accountContext: AccountContext
private let audioSession: ManagedAudioSession
@ -51,6 +74,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
private let initialCall: CachedChannelData.ActiveCall?
public let internalId: CallSessionInternalId
public let peerId: PeerId
public let peer: Peer?
@ -60,7 +84,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var callContext: OngoingGroupCallContext?
private var ssrcMapping: [UInt32: PeerId] = [:]
private var sessionStateDisposable: Disposable?
private var summaryInfoState = Promise<SummaryInfoState?>(nil)
private var summaryParticipantsState = Promise<SummaryParticipantsState?>(nil)
private let summaryStatePromise = Promise<PresentationGroupCallSummaryState?>(nil)
public var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> {
return self.summaryStatePromise.get()
}
private var summaryStateDisposable: Disposable?
private let isMutedPromise = ValuePromise<Bool>(true)
private var isMutedValue = true
@ -155,6 +186,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
audioSession: ManagedAudioSession,
callKitIntegration: CallKitIntegration?,
getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void),
initialCall: CachedChannelData.ActiveCall?,
internalId: CallSessionInternalId,
peerId: PeerId,
peer: Peer?
@ -165,6 +197,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.callKitIntegration = callKitIntegration
self.getDeviceAccessData = getDeviceAccessData
self.initialCall = initialCall
self.internalId = internalId
self.peerId = peerId
self.peer = peer
@ -269,13 +302,33 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
})
self.summaryStatePromise.set(combineLatest(queue: .mainQueue(),
self.summaryInfoState.get(),
self.summaryParticipantsState.get(),
self.statePromise.get()
)
|> map { infoState, participantsState, callState -> PresentationGroupCallSummaryState? in
guard let infoState = infoState else {
return nil
}
guard let participantsState = participantsState else {
return nil
}
return PresentationGroupCallSummaryState(
info: infoState.info,
participantCount: participantsState.participantCount,
callState: callState,
topParticipants: participantsState.topParticipants
)
})
self.requestCall()
}
deinit {
self.audioSessionShouldBeActiveDisposable?.dispose()
self.audioSessionActiveDisposable?.dispose()
self.sessionStateDisposable?.dispose()
self.summaryStateDisposable?.dispose()
self.audioSessionDisposable?.dispose()
self.requestDisposable.dispose()
self.groupCallParticipantUpdatesDisposable?.dispose()
@ -409,6 +462,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
break
default:
if case let .estabilished(callInfo, clientParams, _, initialState) = internalState {
self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo)))
self.ssrcMapping.removeAll()
var ssrcs: [UInt32] = []
for participant in initialState.participants {
@ -431,7 +486,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
var memberStates: [PeerId: PresentationGroupCallMemberState] = [:]
var topParticipants: [GroupCallParticipantsContext.Participant] = []
for participant in state.participants {
if topParticipants.count < 3 {
topParticipants.append(participant)
}
strongSelf.ssrcMapping[participant.ssrc] = participant.peer.id
memberStates[participant.peer.id] = PresentationGroupCallMemberState(
@ -440,6 +500,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
)
}
strongSelf.membersValue = memberStates
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
participantCount: state.totalCount,
topParticipants: topParticipants
)))
}))
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
@ -546,9 +611,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
let account = self.account
let peerId = self.peerId
let currentCall = getCurrentGroupCall(account: account, peerId: peerId)
|> mapError { _ -> CallError in
return .generic
let currentCall: Signal<GroupCallInfo?, CallError>
if let initialCall = self.initialCall {
currentCall = getCurrentGroupCall(account: account, callId: initialCall.id, accessHash: initialCall.accessHash)
|> mapError { _ -> CallError in
return .generic
}
|> map { summary -> GroupCallInfo? in
return summary?.info
}
} else {
currentCall = .single(nil)
}
let currentOrRequestedCall = currentCall

View File

@ -468,6 +468,7 @@ public final class VoiceChatController: ViewController {
guard let peer = view.peers[view.peerId] else {
return
}
//TODO:localize
var subtitle = "group"
if let cachedData = view.cachedData as? CachedChannelData {
if let memberCount = cachedData.participantsSummary.memberCount {
@ -476,7 +477,7 @@ public final class VoiceChatController: ViewController {
}
let titleView = VoiceChatControllerTitleView(theme: strongSelf.presentationData.theme)
titleView.set(title: peer.debugDisplayTitle, subtitle: subtitle)
titleView.set(title: "Voice Chat", subtitle: subtitle)
strongSelf.controller?.navigationItem.titleView = titleView
if !strongSelf.didSetDataReady {
@ -548,7 +549,7 @@ public final class VoiceChatController: ViewController {
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
/*items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
@ -558,7 +559,7 @@ public final class VoiceChatController: ViewController {
f(.dismissWithoutContent)
})))
items.append(.separator)
items.append(.separator)*/
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak self] _, f in

View File

@ -7,19 +7,19 @@ import SyncCore
public struct GroupCallInfo: Equatable {
public var id: Int64
public var accessHash: Int64
public var participantCount: Int
public var clientParams: String?
}
public struct GroupCallSummary: Equatable {
public var info: GroupCallInfo
public var topParticipants: [GroupCallParticipantsContext.Participant]
}
private extension GroupCallInfo {
init?(_ call: Api.GroupCall) {
switch call {
case let .groupCallPrivate(id, accessHash, _):
self.init(
id: id,
accessHash: accessHash,
clientParams: nil
)
case let .groupCall(_, id, accessHash, _, params, _):
case let .groupCall(_, id, accessHash, participantCount, params, _):
var clientParams: String?
if let params = params {
switch params {
@ -30,6 +30,7 @@ private extension GroupCallInfo {
self.init(
id: id,
accessHash: accessHash,
participantCount: Int(participantCount),
clientParams: clientParams
)
case .groupCallDiscarded:
@ -42,49 +43,66 @@ public enum GetCurrentGroupCallError {
case generic
}
public func getCurrentGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCallInfo?, GetCurrentGroupCallError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in
transaction.getPeer(peerId).flatMap(apiInputChannel)
public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|> mapError { _ -> GetCurrentGroupCallError in
return .generic
}
|> castError(GetCurrentGroupCallError.self)
|> mapToSignal { inputPeer -> Signal<Api.InputGroupCall?, GetCurrentGroupCallError> in
guard let inputPeer = inputPeer else {
return .fail(.generic)
}
return account.network.request(Api.functions.channels.getFullChannel(channel: inputPeer))
|> mapError { _ -> GetCurrentGroupCallError in
return .generic
}
|> mapToSignal { result -> Signal<Api.InputGroupCall?, GetCurrentGroupCallError> in
switch result {
case let .chatFull(fullChat, _, _):
switch fullChat {
case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, inputCall):
return .single(inputCall)
default:
return .single(nil)
|> mapToSignal { result -> Signal<GroupCallSummary?, GetCurrentGroupCallError> in
switch result {
case let .groupCall(call, _, participants, users):
return account.postbox.transaction { transaction -> GroupCallSummary? in
guard let info = GroupCallInfo(call) else {
return nil
}
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
if let presence = TelegramUserPresence(apiUser: user) {
peerPresences[telegramUser.id] = presence
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
var parsedParticipants: [GroupCallParticipantsContext.Participant] = []
loop: for participant in participants {
switch participant {
case let .groupCallParticipant(flags, userId, date, source):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else {
continue loop
}
var muteState: GroupCallParticipantsContext.Participant.MuteState?
if (flags & (1 << 0)) != 0 {
let canUnmute = (flags & (1 << 2)) != 0
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
}
parsedParticipants.append(GroupCallParticipantsContext.Participant(
peer: peer,
ssrc: ssrc,
joinTimestamp: date,
muteState: muteState
))
}
}
return GroupCallSummary(
info: info,
topParticipants: parsedParticipants
)
}
}
}
|> mapToSignal { inputCall -> Signal<GroupCallInfo?, GetCurrentGroupCallError> in
guard let inputCall = inputCall else {
return .single(nil)
}
return account.network.request(Api.functions.phone.getGroupCall(call: inputCall))
|> mapError { _ -> GetCurrentGroupCallError in
return .generic
}
|> mapToSignal { result -> Signal<GroupCallInfo?, GetCurrentGroupCallError> in
switch result {
case let .groupCall(call, _, _, _):
return account.postbox.transaction { transaction -> GroupCallInfo? in
return GroupCallInfo(call)
}
|> mapError { _ -> GetCurrentGroupCallError in
return .generic
}
|> mapError { _ -> GetCurrentGroupCallError in
return .generic
}
}
}

View File

@ -397,13 +397,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.peekData = peekData
var locationBroadcastPanelSource: LocationBroadcastPanelSource
var groupCallPanelSource: GroupCallPanelSource
switch chatLocation {
case let .peer(peerId):
locationBroadcastPanelSource = .peer(peerId)
groupCallPanelSource = .peer(peerId)
self.chatLocationInfoData = .peer(Promise())
case let .replyThread(replyThreadMessage):
locationBroadcastPanelSource = .none
groupCallPanelSource = .all
let promise = Promise<Message?>()
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
promise.set(context.account.postbox.combinedView(keys: [key])
@ -428,6 +431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
mediaAccessoryPanelVisibility = .specific(size: .compact)
} else {
locationBroadcastPanelSource = .none
groupCallPanelSource = .none
}
let navigationBarPresentationData: NavigationBarPresentationData?
switch mode {
@ -436,7 +440,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
}
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource, groupCallPanelSource: groupCallPanelSource)
self.automaticallyControlPresentationContextLayout = false
self.blocksBackgroundWhenInOverlay = true
@ -5982,7 +5986,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return
}
let callResult = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id)
let callResult = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peer.id {
strongSelf.context.sharedContext.navigateToCurrentCall()
@ -5990,12 +5994,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peer.id), currentPeerId.flatMap(transaction.getPeer))
} |> deliverOnMainQueue).start(next: { [weak self] peer, current in
} |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer {
if let strongSelf = self, let current = current {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self {
//let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true)
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: true)
}
})]), in: .window(.root))
} else {

View File

@ -64,16 +64,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
}
}
if chatPresentationInterfaceState.activeGroupCallInfo != nil {
if let currentPanel = currentPanel as? ChatCallTitlePanelNode {
return currentPanel
} else {
let panel = ChatCallTitlePanelNode(context: context)
panel.interfaceInteraction = interfaceInteraction
return panel
}
}
if let selectedContext = selectedContext {
switch selectedContext {
case .pinnedMessage:

View File

@ -36,7 +36,7 @@ final class ChatRecentActionsController: TelegramBaseController {
self.titleView = ChatRecentActionsTitleView(color: self.presentationData.theme.rootController.navigationBar.primaryTextColor)
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none)
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none)
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style

View File

@ -3166,7 +3166,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private func requestCall(isVideo: Bool) {
if let peer = self.data?.peer as? TelegramChannel {
self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id)
guard let cachedChannelData = self.data?.cachedData as? CachedChannelData else {
return
}
let _ = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id, initialCall: cachedChannelData.activeCall, endCurrentIfAny: false)
return
}

View File

@ -655,23 +655,26 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
})
self.callStateDisposable = (self.callState.get()
|> deliverOnMainQueue).start(next: { [weak self] state in
self.callStateDisposable = combineLatest(queue: .mainQueue(),
self.callState.get(),
callManager.currentGroupCallSignal
|> map { call -> Bool in
return call != nil
}
).start(next: { [weak self] state, hasGroupCall in
if let strongSelf = self {
let resolvedText: CallStatusText
if let state = state {
// if [.active, .paused].contains(state.videoState) || [.active, .paused].contains(state.remoteVideoState) {
// resolvedText = .none
// } else {
switch state.state {
case .connecting, .requesting, .terminating, .ringing, .waiting:
resolvedText = .inProgress(nil)
case .terminated:
resolvedText = .none
case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _):
resolvedText = .inProgress(timestamp)
}
// }
switch state.state {
case .connecting, .requesting, .terminating, .ringing, .waiting:
resolvedText = .inProgress(nil)
case .terminated:
resolvedText = .none
case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _):
resolvedText = .inProgress(timestamp)
}
} else if hasGroupCall {
resolvedText = .inProgress(nil)
} else {
resolvedText = .none
}
@ -705,7 +708,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
})
mainWindow.inCallNavigate = { [weak self] in
if let strongSelf = self, let callController = strongSelf.callController {
guard let strongSelf = self else {
return
}
if let callController = strongSelf.callController {
if callController.isNodeLoaded {
mainWindow.hostView.containerView.endEditing(true)
if callController.view.superview == nil {
@ -714,6 +720,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
callController.expandFromPipIfPossible()
}
}
} else if let groupCallController = strongSelf.groupCallController {
if groupCallController.isNodeLoaded {
mainWindow.hostView.containerView.endEditing(true)
if groupCallController.view.superview == nil {
mainWindow.present(groupCallController, on: .calls)
}
}
}
}
} else {