mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
WIP
This commit is contained in:
parent
e2bc141356
commit
a97cd76c5d
@ -51,7 +51,6 @@ BAZEL_OPTIONS=(\
|
|||||||
--spawn_strategy=standalone \
|
--spawn_strategy=standalone \
|
||||||
--strategy=SwiftCompile=standalone \
|
--strategy=SwiftCompile=standalone \
|
||||||
--features=swift.enable_batch_mode \
|
--features=swift.enable_batch_mode \
|
||||||
--apple_generate_dsym \
|
|
||||||
--swiftcopt=-j${CORE_COUNT_MINUS_ONE} \
|
--swiftcopt=-j${CORE_COUNT_MINUS_ONE} \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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 struct PresentationGroupCallMemberState: Equatable {
|
||||||
public var ssrc: UInt32
|
public var ssrc: UInt32
|
||||||
public var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
public var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||||
@ -197,9 +216,11 @@ public protocol PresentationGroupCall: class {
|
|||||||
|
|
||||||
var canBeRemoved: Signal<Bool, NoError> { get }
|
var canBeRemoved: Signal<Bool, NoError> { get }
|
||||||
var state: Signal<PresentationGroupCallState, NoError> { get }
|
var state: Signal<PresentationGroupCallState, NoError> { get }
|
||||||
|
var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> { get }
|
||||||
var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> { get }
|
var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> { get }
|
||||||
var audioLevels: Signal<[(PeerId, Float)], NoError> { get }
|
var audioLevels: Signal<[(PeerId, Float)], NoError> { get }
|
||||||
var myAudioLevel: Signal<Float, NoError> { get }
|
var myAudioLevel: Signal<Float, NoError> { get }
|
||||||
|
var isMuted: Signal<Bool, NoError> { get }
|
||||||
|
|
||||||
func leave() -> Signal<Bool, NoError>
|
func leave() -> Signal<Bool, NoError>
|
||||||
|
|
||||||
@ -211,7 +232,8 @@ public protocol PresentationGroupCall: class {
|
|||||||
|
|
||||||
public protocol PresentationCallManager: class {
|
public protocol PresentationCallManager: class {
|
||||||
var currentCallSignal: Signal<PresentationCall?, NoError> { get }
|
var currentCallSignal: Signal<PresentationCall?, NoError> { get }
|
||||||
|
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get }
|
||||||
|
|
||||||
func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
|
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
|
||||||
}
|
}
|
||||||
|
@ -146,9 +146,7 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
|||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, animated: Bool, synchronousLoad: Bool) -> CGSize {
|
public func update(context: AccountContext, content: AnimatedAvatarSetContext.Content, itemSize: CGSize = CGSize(width: 30.0, height: 30.0), animated: Bool, synchronousLoad: Bool) -> CGSize {
|
||||||
let itemSize = CGSize(width: 30.0, height: 30.0)
|
|
||||||
|
|
||||||
var contentWidth: CGFloat = 0.0
|
var contentWidth: CGFloat = 0.0
|
||||||
let contentHeight: CGFloat = itemSize.height
|
let contentHeight: CGFloat = itemSize.height
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
self.tabContainerNode = ChatListFilterTabContainerNode()
|
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
|
self.tabBarItemContextActionType = .always
|
||||||
|
|
||||||
|
@ -391,7 +391,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let inCallStatusBar = self.inCallStatusBar {
|
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 {
|
if inCallStatusBar.frame.isEmpty {
|
||||||
inCallStatusBar.frame = inCallStatusBarFrame
|
inCallStatusBar.frame = inCallStatusBarFrame
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,7 +33,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||||
|
|
||||||
|
@ -6,9 +6,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[571523412] = { return $0.readDouble() }
|
dict[571523412] = { return $0.readDouble() }
|
||||||
dict[-1255641564] = { return parseString($0) }
|
dict[-1255641564] = { return parseString($0) }
|
||||||
dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($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[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
|
||||||
|
dict[1435512961] = { return Api.GroupCall.parse_groupCall($0) }
|
||||||
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
||||||
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
||||||
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
|
||||||
|
@ -1910,19 +1910,18 @@ public struct messages {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
public enum GroupCall: TypeConstructorDescription {
|
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 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) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .groupCallPrivate(let id, let accessHash, let participantsCount):
|
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-1331534976)
|
buffer.appendInt32(2004925620)
|
||||||
}
|
}
|
||||||
serializeInt64(id, buffer: buffer, boxed: false)
|
serializeInt64(id, buffer: buffer, boxed: false)
|
||||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||||
serializeInt32(participantsCount, buffer: buffer, boxed: false)
|
serializeInt32(duration, buffer: buffer, boxed: false)
|
||||||
break
|
break
|
||||||
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
|
case .groupCall(let flags, let id, let accessHash, let participantsCount, let params, let version):
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -1935,29 +1934,19 @@ public extension Api {
|
|||||||
if Int(flags) & Int(1 << 0) != 0 {params!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {params!.serialize(buffer, true)}
|
||||||
serializeInt32(version, buffer: buffer, boxed: false)
|
serializeInt32(version, buffer: buffer, boxed: false)
|
||||||
break
|
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)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
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):
|
case .groupCallDiscarded(let id, let accessHash, let duration):
|
||||||
return ("groupCallDiscarded", [("id", id), ("accessHash", accessHash), ("duration", 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?
|
var _1: Int64?
|
||||||
_1 = reader.readInt64()
|
_1 = reader.readInt64()
|
||||||
var _2: Int64?
|
var _2: Int64?
|
||||||
@ -1968,7 +1957,7 @@ public extension Api {
|
|||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
if _c1 && _c2 && _c3 {
|
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 {
|
else {
|
||||||
return nil
|
return nil
|
||||||
@ -2002,23 +1991,6 @@ public extension Api {
|
|||||||
return nil
|
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 {
|
public enum InputGeoPoint: TypeConstructorDescription {
|
||||||
|
@ -20,6 +20,8 @@ swift_library(
|
|||||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
"//submodules/Markdown:Markdown",
|
"//submodules/Markdown:Markdown",
|
||||||
|
"//submodules/TelegramCallsUI:TelegramCallsUI",
|
||||||
|
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,34 @@ public enum LocationBroadcastPanelSource {
|
|||||||
case peer(PeerId)
|
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) {
|
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
||||||
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
||||||
if let message = message, let strongController = controller {
|
if let message = message, let strongController = controller {
|
||||||
@ -64,9 +92,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
|
|
||||||
public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
|
public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
|
||||||
public let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
public let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
||||||
|
public let groupCallPanelSource: GroupCallPanelSource
|
||||||
|
|
||||||
private var mediaStatusDisposable: Disposable?
|
private var mediaStatusDisposable: Disposable?
|
||||||
private var locationBroadcastDisposable: Disposable?
|
private var locationBroadcastDisposable: Disposable?
|
||||||
|
private var currentGroupCallDisposable: Disposable?
|
||||||
|
|
||||||
public private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
|
public private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
|
||||||
private var playlistLocation: SharedMediaPlaylistLocation?
|
private var playlistLocation: SharedMediaPlaylistLocation?
|
||||||
@ -81,6 +111,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
private var locationBroadcastMessages: [MessageId: Message]?
|
private var locationBroadcastMessages: [MessageId: Message]?
|
||||||
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
|
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
|
||||||
|
|
||||||
|
private var groupCallPanelData: GroupCallPanelData?
|
||||||
|
private var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
|
||||||
|
|
||||||
private var dismissingPanel: ASDisplayNode?
|
private var dismissingPanel: ASDisplayNode?
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -101,6 +134,9 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
|
|
||||||
public var additionalHeight: CGFloat {
|
public var additionalHeight: CGFloat {
|
||||||
var height: CGFloat = 0.0
|
var height: CGFloat = 0.0
|
||||||
|
if let _ = self.groupCallAccessoryPanel {
|
||||||
|
height += 50.0
|
||||||
|
}
|
||||||
if let _ = self.mediaAccessoryPanel {
|
if let _ = self.mediaAccessoryPanel {
|
||||||
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||||
}
|
}
|
||||||
@ -114,11 +150,12 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
return super.navigationHeight
|
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.context = context
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.mediaAccessoryPanelVisibility = mediaAccessoryPanelVisibility
|
self.mediaAccessoryPanelVisibility = mediaAccessoryPanelVisibility
|
||||||
self.locationBroadcastPanelSource = locationBroadcastPanelSource
|
self.locationBroadcastPanelSource = locationBroadcastPanelSource
|
||||||
|
self.groupCallPanelSource = groupCallPanelSource
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: navigationBarPresentationData)
|
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
|
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -269,6 +393,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
deinit {
|
deinit {
|
||||||
self.mediaStatusDisposable?.dispose()
|
self.mediaStatusDisposable?.dispose()
|
||||||
self.locationBroadcastDisposable?.dispose()
|
self.locationBroadcastDisposable?.dispose()
|
||||||
|
self.currentGroupCallDisposable?.dispose()
|
||||||
self.presentationDataDisposable?.dispose()
|
self.presentationDataDisposable?.dispose()
|
||||||
self.playlistPreloadDisposable?.dispose()
|
self.playlistPreloadDisposable?.dispose()
|
||||||
}
|
}
|
||||||
@ -287,6 +412,52 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
|
|
||||||
var additionalHeight: CGFloat = 0.0
|
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 {
|
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
|
||||||
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
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))
|
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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -592,17 +592,32 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult {
|
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool) -> RequestOrJoinGroupCallResult {
|
||||||
if let currentGroupCall = self.currentGroupCallValue {
|
let begin: () -> Void = { [weak self] in
|
||||||
return .alreadyInProgress(currentGroupCall.peerId)
|
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
|
return .requested
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startGroupCall(
|
private func startGroupCall(
|
||||||
accountContext: AccountContext,
|
accountContext: AccountContext,
|
||||||
peerId: PeerId,
|
peerId: PeerId,
|
||||||
|
initialCall: CachedChannelData.ActiveCall?,
|
||||||
internalId: CallSessionInternalId = CallSessionInternalId()
|
internalId: CallSessionInternalId = CallSessionInternalId()
|
||||||
) -> Signal<Bool, NoError> {
|
) -> Signal<Bool, NoError> {
|
||||||
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
||||||
@ -649,6 +664,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
audioSession: strongSelf.audioSession,
|
audioSession: strongSelf.audioSession,
|
||||||
callKitIntegration: nil,
|
callKitIntegration: nil,
|
||||||
getDeviceAccessData: strongSelf.getDeviceAccessData,
|
getDeviceAccessData: strongSelf.getDeviceAccessData,
|
||||||
|
initialCall: initialCall,
|
||||||
internalId: internalId,
|
internalId: internalId,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
peer: nil
|
peer: nil
|
||||||
|
@ -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 account: Account
|
||||||
public let accountContext: AccountContext
|
public let accountContext: AccountContext
|
||||||
private let audioSession: ManagedAudioSession
|
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 getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
||||||
|
|
||||||
|
private let initialCall: CachedChannelData.ActiveCall?
|
||||||
public let internalId: CallSessionInternalId
|
public let internalId: CallSessionInternalId
|
||||||
public let peerId: PeerId
|
public let peerId: PeerId
|
||||||
public let peer: Peer?
|
public let peer: Peer?
|
||||||
@ -60,7 +84,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
private var callContext: OngoingGroupCallContext?
|
private var callContext: OngoingGroupCallContext?
|
||||||
private var ssrcMapping: [UInt32: PeerId] = [:]
|
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 let isMutedPromise = ValuePromise<Bool>(true)
|
||||||
private var isMutedValue = true
|
private var isMutedValue = true
|
||||||
@ -143,6 +174,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
audioSession: ManagedAudioSession,
|
audioSession: ManagedAudioSession,
|
||||||
callKitIntegration: CallKitIntegration?,
|
callKitIntegration: CallKitIntegration?,
|
||||||
getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void),
|
getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void),
|
||||||
|
initialCall: CachedChannelData.ActiveCall?,
|
||||||
internalId: CallSessionInternalId,
|
internalId: CallSessionInternalId,
|
||||||
peerId: PeerId,
|
peerId: PeerId,
|
||||||
peer: Peer?
|
peer: Peer?
|
||||||
@ -153,6 +185,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.callKitIntegration = callKitIntegration
|
self.callKitIntegration = callKitIntegration
|
||||||
self.getDeviceAccessData = getDeviceAccessData
|
self.getDeviceAccessData = getDeviceAccessData
|
||||||
|
|
||||||
|
self.initialCall = initialCall
|
||||||
self.internalId = internalId
|
self.internalId = internalId
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
@ -257,13 +290,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()
|
self.requestCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.audioSessionShouldBeActiveDisposable?.dispose()
|
self.audioSessionShouldBeActiveDisposable?.dispose()
|
||||||
self.audioSessionActiveDisposable?.dispose()
|
self.audioSessionActiveDisposable?.dispose()
|
||||||
self.sessionStateDisposable?.dispose()
|
self.summaryStateDisposable?.dispose()
|
||||||
self.audioSessionDisposable?.dispose()
|
self.audioSessionDisposable?.dispose()
|
||||||
self.requestDisposable.dispose()
|
self.requestDisposable.dispose()
|
||||||
self.groupCallParticipantUpdatesDisposable?.dispose()
|
self.groupCallParticipantUpdatesDisposable?.dispose()
|
||||||
@ -397,6 +450,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if case let .estabilished(callInfo, clientParams, _, initialState) = internalState {
|
if case let .estabilished(callInfo, clientParams, _, initialState) = internalState {
|
||||||
|
self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo)))
|
||||||
|
|
||||||
self.ssrcMapping.removeAll()
|
self.ssrcMapping.removeAll()
|
||||||
var ssrcs: [UInt32] = []
|
var ssrcs: [UInt32] = []
|
||||||
for participant in initialState.participants {
|
for participant in initialState.participants {
|
||||||
@ -419,7 +474,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var memberStates: [PeerId: PresentationGroupCallMemberState] = [:]
|
var memberStates: [PeerId: PresentationGroupCallMemberState] = [:]
|
||||||
|
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||||
for participant in state.participants {
|
for participant in state.participants {
|
||||||
|
if topParticipants.count < 3 {
|
||||||
|
topParticipants.append(participant)
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.ssrcMapping[participant.ssrc] = participant.peer.id
|
strongSelf.ssrcMapping[participant.ssrc] = participant.peer.id
|
||||||
|
|
||||||
memberStates[participant.peer.id] = PresentationGroupCallMemberState(
|
memberStates[participant.peer.id] = PresentationGroupCallMemberState(
|
||||||
@ -428,6 +488,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
strongSelf.membersValue = memberStates
|
strongSelf.membersValue = memberStates
|
||||||
|
|
||||||
|
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
|
participantCount: state.totalCount,
|
||||||
|
topParticipants: topParticipants
|
||||||
|
)))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
|
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
|
||||||
@ -530,9 +595,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
let account = self.account
|
let account = self.account
|
||||||
let peerId = self.peerId
|
let peerId = self.peerId
|
||||||
|
|
||||||
let currentCall = getCurrentGroupCall(account: account, peerId: peerId)
|
let currentCall: Signal<GroupCallInfo?, CallError>
|
||||||
|> mapError { _ -> CallError in
|
if let initialCall = self.initialCall {
|
||||||
return .generic
|
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
|
let currentOrRequestedCall = currentCall
|
||||||
|
@ -442,6 +442,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
guard let peer = view.peers[view.peerId] else {
|
guard let peer = view.peers[view.peerId] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//TODO:localize
|
||||||
var subtitle = "group"
|
var subtitle = "group"
|
||||||
if let cachedData = view.cachedData as? CachedChannelData {
|
if let cachedData = view.cachedData as? CachedChannelData {
|
||||||
if let memberCount = cachedData.participantsSummary.memberCount {
|
if let memberCount = cachedData.participantsSummary.memberCount {
|
||||||
@ -450,7 +451,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let titleView = VoiceChatControllerTitleView(theme: strongSelf.presentationData.theme)
|
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
|
strongSelf.controller?.navigationItem.titleView = titleView
|
||||||
|
|
||||||
if !strongSelf.didSetDataReady {
|
if !strongSelf.didSetDataReady {
|
||||||
@ -522,7 +523,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
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)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
@ -532,7 +533,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
})))
|
})))
|
||||||
items.append(.separator)
|
items.append(.separator)*/
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in
|
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)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { [weak self] _, f in
|
}, action: { [weak self] _, f in
|
||||||
|
@ -7,19 +7,19 @@ import SyncCore
|
|||||||
public struct GroupCallInfo: Equatable {
|
public struct GroupCallInfo: Equatable {
|
||||||
public var id: Int64
|
public var id: Int64
|
||||||
public var accessHash: Int64
|
public var accessHash: Int64
|
||||||
|
public var participantCount: Int
|
||||||
public var clientParams: String?
|
public var clientParams: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct GroupCallSummary: Equatable {
|
||||||
|
public var info: GroupCallInfo
|
||||||
|
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||||
|
}
|
||||||
|
|
||||||
private extension GroupCallInfo {
|
private extension GroupCallInfo {
|
||||||
init?(_ call: Api.GroupCall) {
|
init?(_ call: Api.GroupCall) {
|
||||||
switch call {
|
switch call {
|
||||||
case let .groupCallPrivate(id, accessHash, _):
|
case let .groupCall(_, id, accessHash, participantCount, params, _):
|
||||||
self.init(
|
|
||||||
id: id,
|
|
||||||
accessHash: accessHash,
|
|
||||||
clientParams: nil
|
|
||||||
)
|
|
||||||
case let .groupCall(_, id, accessHash, _, params, _):
|
|
||||||
var clientParams: String?
|
var clientParams: String?
|
||||||
if let params = params {
|
if let params = params {
|
||||||
switch params {
|
switch params {
|
||||||
@ -30,6 +30,7 @@ private extension GroupCallInfo {
|
|||||||
self.init(
|
self.init(
|
||||||
id: id,
|
id: id,
|
||||||
accessHash: accessHash,
|
accessHash: accessHash,
|
||||||
|
participantCount: Int(participantCount),
|
||||||
clientParams: clientParams
|
clientParams: clientParams
|
||||||
)
|
)
|
||||||
case .groupCallDiscarded:
|
case .groupCallDiscarded:
|
||||||
@ -42,49 +43,66 @@ public enum GetCurrentGroupCallError {
|
|||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getCurrentGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCallInfo?, GetCurrentGroupCallError> {
|
public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal<GroupCallSummary?, GetCurrentGroupCallError> {
|
||||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|
||||||
transaction.getPeer(peerId).flatMap(apiInputChannel)
|
|> mapError { _ -> GetCurrentGroupCallError in
|
||||||
|
return .generic
|
||||||
}
|
}
|
||||||
|> castError(GetCurrentGroupCallError.self)
|
|> mapToSignal { result -> Signal<GroupCallSummary?, GetCurrentGroupCallError> in
|
||||||
|> mapToSignal { inputPeer -> Signal<Api.InputGroupCall?, GetCurrentGroupCallError> in
|
switch result {
|
||||||
guard let inputPeer = inputPeer else {
|
case let .groupCall(call, _, participants, users):
|
||||||
return .fail(.generic)
|
return account.postbox.transaction { transaction -> GroupCallSummary? in
|
||||||
}
|
guard let info = GroupCallInfo(call) else {
|
||||||
return account.network.request(Api.functions.channels.getFullChannel(channel: inputPeer))
|
return nil
|
||||||
|> 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|> mapError { _ -> GetCurrentGroupCallError in
|
||||||
}
|
return .generic
|
||||||
|> 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,13 +397,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.peekData = peekData
|
self.peekData = peekData
|
||||||
|
|
||||||
var locationBroadcastPanelSource: LocationBroadcastPanelSource
|
var locationBroadcastPanelSource: LocationBroadcastPanelSource
|
||||||
|
var groupCallPanelSource: GroupCallPanelSource
|
||||||
|
|
||||||
switch chatLocation {
|
switch chatLocation {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
locationBroadcastPanelSource = .peer(peerId)
|
locationBroadcastPanelSource = .peer(peerId)
|
||||||
|
groupCallPanelSource = .peer(peerId)
|
||||||
self.chatLocationInfoData = .peer(Promise())
|
self.chatLocationInfoData = .peer(Promise())
|
||||||
case let .replyThread(replyThreadMessage):
|
case let .replyThread(replyThreadMessage):
|
||||||
locationBroadcastPanelSource = .none
|
locationBroadcastPanelSource = .none
|
||||||
|
groupCallPanelSource = .all
|
||||||
let promise = Promise<Message?>()
|
let promise = Promise<Message?>()
|
||||||
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
|
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
|
||||||
promise.set(context.account.postbox.combinedView(keys: [key])
|
promise.set(context.account.postbox.combinedView(keys: [key])
|
||||||
@ -428,6 +431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
mediaAccessoryPanelVisibility = .specific(size: .compact)
|
mediaAccessoryPanelVisibility = .specific(size: .compact)
|
||||||
} else {
|
} else {
|
||||||
locationBroadcastPanelSource = .none
|
locationBroadcastPanelSource = .none
|
||||||
|
groupCallPanelSource = .none
|
||||||
}
|
}
|
||||||
let navigationBarPresentationData: NavigationBarPresentationData?
|
let navigationBarPresentationData: NavigationBarPresentationData?
|
||||||
switch mode {
|
switch mode {
|
||||||
@ -436,7 +440,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
default:
|
default:
|
||||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false)
|
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.automaticallyControlPresentationContextLayout = false
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
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 {
|
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
return
|
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 let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||||
if currentPeerId == peer.id {
|
if currentPeerId == peer.id {
|
||||||
strongSelf.context.sharedContext.navigateToCurrentCall()
|
strongSelf.context.sharedContext.navigateToCurrentCall()
|
||||||
@ -5990,12 +5994,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||||
return (transaction.getPeer(peer.id), currentPeerId.flatMap(transaction.getPeer))
|
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 peer = peer {
|
||||||
if let strongSelf = self, let current = current {
|
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: {
|
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 {
|
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))
|
})]), in: .window(.root))
|
||||||
} else {
|
} else {
|
||||||
|
@ -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 {
|
if let selectedContext = selectedContext {
|
||||||
switch selectedContext {
|
switch selectedContext {
|
||||||
case .pinnedMessage:
|
case .pinnedMessage:
|
||||||
|
@ -36,7 +36,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
|||||||
|
|
||||||
self.titleView = ChatRecentActionsTitleView(color: self.presentationData.theme.rootController.navigationBar.primaryTextColor)
|
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
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||||
|
|
||||||
|
@ -3166,7 +3166,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
|
|
||||||
private func requestCall(isVideo: Bool) {
|
private func requestCall(isVideo: Bool) {
|
||||||
if let peer = self.data?.peer as? TelegramChannel {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
@ -655,23 +655,26 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.callStateDisposable = (self.callState.get()
|
self.callStateDisposable = combineLatest(queue: .mainQueue(),
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
self.callState.get(),
|
||||||
|
callManager.currentGroupCallSignal
|
||||||
|
|> map { call -> Bool in
|
||||||
|
return call != nil
|
||||||
|
}
|
||||||
|
).start(next: { [weak self] state, hasGroupCall in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let resolvedText: CallStatusText
|
let resolvedText: CallStatusText
|
||||||
if let state = state {
|
if let state = state {
|
||||||
// if [.active, .paused].contains(state.videoState) || [.active, .paused].contains(state.remoteVideoState) {
|
switch state.state {
|
||||||
// resolvedText = .none
|
case .connecting, .requesting, .terminating, .ringing, .waiting:
|
||||||
// } else {
|
resolvedText = .inProgress(nil)
|
||||||
switch state.state {
|
case .terminated:
|
||||||
case .connecting, .requesting, .terminating, .ringing, .waiting:
|
resolvedText = .none
|
||||||
resolvedText = .inProgress(nil)
|
case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _):
|
||||||
case .terminated:
|
resolvedText = .inProgress(timestamp)
|
||||||
resolvedText = .none
|
}
|
||||||
case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _):
|
} else if hasGroupCall {
|
||||||
resolvedText = .inProgress(timestamp)
|
resolvedText = .inProgress(nil)
|
||||||
}
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
resolvedText = .none
|
resolvedText = .none
|
||||||
}
|
}
|
||||||
@ -705,7 +708,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.inCallNavigate = { [weak self] in
|
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 {
|
if callController.isNodeLoaded {
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
mainWindow.hostView.containerView.endEditing(true)
|
||||||
if callController.view.superview == nil {
|
if callController.view.superview == nil {
|
||||||
@ -714,6 +720,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
callController.expandFromPipIfPossible()
|
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 {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user