mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add voice chat invite links support
This commit is contained in:
commit
259491941f
@ -5768,6 +5768,7 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.EndConfirmationEnd" = "End";
|
||||
|
||||
"VoiceChat.InviteMemberToGroupFirstText" = "%1$@ isn't a member of \"%2$@\" yet. Add them to the group?";
|
||||
"VoiceChat.InviteMemberToChannelFirstText" = "%1$@ isn't a member of \"%2$@\" yet. Add them to the channel?";
|
||||
"VoiceChat.InviteMemberToGroupFirstAdd" = "Add";
|
||||
|
||||
"VoiceChat.CreateNewVoiceChatText" = "Voice chat ended. Start a new one?";
|
||||
|
@ -328,6 +328,8 @@ public protocol PresentationGroupCall: class {
|
||||
func removedPeer(_ peerId: PeerId)
|
||||
var invitedPeers: Signal<[PeerId], NoError> { get }
|
||||
|
||||
var inviteLinks: Signal<GroupCallInviteLinks?, NoError> { get }
|
||||
|
||||
var incomingVideoSources: Signal<[PeerId: UInt32], NoError> { get }
|
||||
|
||||
func makeIncomingVideoView(source: UInt32, completion: @escaping (PresentationCallVideoView?) -> Void)
|
||||
|
@ -12,6 +12,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import TelegramNotices
|
||||
import ChatListSearchItemHeader
|
||||
|
||||
private struct CallListNodeListViewTransition {
|
||||
let callListView: CallListNodeView
|
||||
@ -567,6 +568,12 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
self.updateState {
|
||||
return $0.withUpdatedPresentationData(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: presentationData.disableAnimations)
|
||||
}
|
||||
|
||||
self.listNode.forEachItemHeaderNode({ itemHeaderNode in
|
||||
if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode {
|
||||
itemHeaderNode.updateTheme(theme: presentationData.theme)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,8 +61,6 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
|
||||
}
|
||||
return self.delayedFrames.remove(at: minFrameIndex)
|
||||
}
|
||||
} else {
|
||||
assert(true)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -865,7 +865,7 @@ private final class ChatListViewSpaceState {
|
||||
|
||||
let loadedEntries = postbox.chatListTable.entries(groupId: .root, from: (allEntries[0].index.predecessor, true), to: (allEntries[allEntries.count - 1].index.successor, true), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: 1000, predicate: nil).map(mapEntry)
|
||||
|
||||
assert(loadedEntries.map({ $0.index }) == allEntries.map({ $0.index }))
|
||||
//assert(loadedEntries.map({ $0.index }) == allEntries.map({ $0.index }))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -357,7 +357,14 @@ public final class ShareController: ViewController {
|
||||
switch subject {
|
||||
case let .url(text):
|
||||
self.defaultAction = ShareControllerAction(title: forcedActionTitle ?? self.presentationData.strings.ShareMenu_CopyShareLink, action: { [weak self] in
|
||||
UIPasteboard.general.string = text
|
||||
if let strongSelf = self, let segmentedValues = segmentedValues {
|
||||
let selectedValue = segmentedValues[strongSelf.controllerNode.selectedSegmentedIndex]
|
||||
if case let .url(text) = selectedValue.subject {
|
||||
UIPasteboard.general.string = text
|
||||
}
|
||||
} else {
|
||||
UIPasteboard.general.string = text
|
||||
}
|
||||
self?.controllerNode.cancel?()
|
||||
|
||||
self?.actionCompleted?()
|
||||
@ -499,7 +506,13 @@ public final class ShareController: ViewController {
|
||||
}
|
||||
|
||||
var shareSignals: [Signal<[MessageId?], NoError>] = []
|
||||
switch strongSelf.subject {
|
||||
var subject = strongSelf.subject
|
||||
if let segmentedValues = strongSelf.segmentedValues {
|
||||
let selectedValue = segmentedValues[strongSelf.controllerNode.selectedSegmentedIndex]
|
||||
subject = selectedValue.subject
|
||||
}
|
||||
|
||||
switch subject {
|
||||
case let .url(url):
|
||||
for peerId in peerIds {
|
||||
var messages: [EnqueueMessage] = []
|
||||
@ -635,7 +648,12 @@ public final class ShareController: ViewController {
|
||||
self.controllerNode.shareExternal = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
var collectableItems: [CollectableExternalShareItem] = []
|
||||
switch strongSelf.subject {
|
||||
var subject = strongSelf.subject
|
||||
if let segmentedValues = strongSelf.segmentedValues {
|
||||
let selectedValue = segmentedValues[strongSelf.controllerNode.selectedSegmentedIndex]
|
||||
subject = selectedValue.subject
|
||||
}
|
||||
switch subject {
|
||||
case let .url(text):
|
||||
collectableItems.append(CollectableExternalShareItem(url: explicitUrl(text), text: "", author: nil, timestamp: nil, mediaReference: nil))
|
||||
case let .text(string):
|
||||
|
@ -31,7 +31,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
private var immediatePeerId: PeerId?
|
||||
private let fromForeignApp: Bool
|
||||
private let segmentedValues: [ShareControllerSegmentedValue]?
|
||||
private var selectedSegmentedIndex: Int = 0
|
||||
var selectedSegmentedIndex: Int = 0
|
||||
|
||||
private let defaultAction: ShareControllerAction?
|
||||
private let requestLayout: (ContainedViewLayoutTransition) -> Void
|
||||
@ -237,8 +237,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||
self.contentContainerNode.addSubnode(self.actionSeparatorNode)
|
||||
self.contentContainerNode.addSubnode(self.actionsBackgroundNode)
|
||||
self.contentContainerNode.addSubnode(self.actionButtonNode)
|
||||
self.contentContainerNode.addSubnode(self.inputFieldNode)
|
||||
self.contentContainerNode.addSubnode(self.actionButtonNode)
|
||||
|
||||
self.inputFieldNode.updateHeight = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
@ -314,7 +314,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
let previousAlpha = node.alpha
|
||||
node.alpha = alpha
|
||||
if animated {
|
||||
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: alpha.isZero ? 0.18 : 0.32)
|
||||
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: alpha.isZero ? 0.18 : 0.32, timingFunction: alpha.isZero ? CAMediaTimingFunctionName.easeOut.rawValue : CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
}
|
||||
|
||||
if let inputNode = node as? ShareInputFieldNode, alpha.isZero {
|
||||
@ -460,13 +460,13 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||
|
||||
transition.updateFrame(node: self.actionsBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - bottomGridInset), size: CGSize(width: contentContainerFrame.size.width, height: bottomGridInset)))
|
||||
transition.updateFrame(node: self.actionsBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - bottomGridInset), size: CGSize(width: contentContainerFrame.size.width, height: bottomGridInset)), beginWithCurrentState: true)
|
||||
|
||||
transition.updateFrame(node: self.actionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - actionButtonHeight), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight)))
|
||||
|
||||
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - bottomGridInset), size: CGSize(width: contentContainerFrame.size.width, height: inputHeight)))
|
||||
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - bottomGridInset), size: CGSize(width: contentContainerFrame.size.width, height: inputHeight)), beginWithCurrentState: true)
|
||||
|
||||
transition.updateFrame(node: self.actionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - bottomGridInset - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.actionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - bottomGridInset - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)), beginWithCurrentState: true)
|
||||
|
||||
let gridSize = CGSize(width: contentFrame.size.width, height: max(32.0, contentFrame.size.height - titleAreaHeight))
|
||||
|
||||
|
@ -672,6 +672,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) }
|
||||
dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) }
|
||||
dict[506920429] = { return Api.InputPhoneCall.parse_inputPhoneCall($0) }
|
||||
dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) }
|
||||
dict[-1551583367] = { return Api.ReceivedNotifyMessage.parse_receivedNotifyMessage($0) }
|
||||
dict[-57668565] = { return Api.ChatParticipants.parse_chatParticipantsForbidden($0) }
|
||||
dict[1061556205] = { return Api.ChatParticipants.parse_chatParticipants($0) }
|
||||
@ -1413,6 +1414,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputPhoneCall:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.phone.ExportedGroupCallInvite:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ReceivedNotifyMessage:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChatParticipants:
|
||||
|
@ -1811,6 +1811,40 @@ public struct phone {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ExportedGroupCallInvite: TypeConstructorDescription {
|
||||
case exportedGroupCallInvite(link: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .exportedGroupCallInvite(let link):
|
||||
if boxed {
|
||||
buffer.appendInt32(541839704)
|
||||
}
|
||||
serializeString(link, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .exportedGroupCallInvite(let link):
|
||||
return ("exportedGroupCallInvite", [("link", link)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PhoneCall: TypeConstructorDescription {
|
||||
case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User])
|
||||
@ -7703,17 +7737,16 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func inviteToGroupCall(flags: Int32, call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
public static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-919505530)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(2067345760)
|
||||
call.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
return (FunctionDescription(name: "phone.inviteToGroupCall", parameters: [("flags", flags), ("call", call), ("users", users)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
return (FunctionDescription(name: "phone.inviteToGroupCall", parameters: [("call", call), ("users", users)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -7870,6 +7903,21 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func exportGroupCallInvite(flags: Int32, call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.ExportedGroupCallInvite>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-425040769)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
call.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "phone.exportGroupCallInvite", parameters: [("flags", flags), ("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.ExportedGroupCallInvite? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.phone.ExportedGroupCallInvite?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.phone.ExportedGroupCallInvite
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -842,14 +842,7 @@ public final class ManagedAudioSession {
|
||||
|
||||
private func updateOutputMode(_ outputMode: AudioSessionOutputMode) {
|
||||
if let (type, currentOutputMode) = self.currentTypeAndOutputMode, currentOutputMode != outputMode {
|
||||
//self.currentTypeAndOutputMode = (type, outputMode)
|
||||
do {
|
||||
try self.setup(type: type, outputMode: outputMode, activateNow: true)
|
||||
//try self.setupOutputMode(outputMode, type: type)
|
||||
//try self.activate()
|
||||
} catch let error {
|
||||
print("ManagedAudioSession overrideOutputAudioPort error \(error)")
|
||||
}
|
||||
self.setup(type: type, outputMode: outputMode, activateNow: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -711,7 +711,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
initialCall: initialCall,
|
||||
internalId: internalId,
|
||||
peerId: peerId,
|
||||
peer: nil,
|
||||
joinAsPeerId: joinAsPeerId
|
||||
)
|
||||
strongSelf.updateCurrentGroupCall(call)
|
||||
|
@ -195,7 +195,7 @@ public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCach
|
||||
}
|
||||
|
||||
private extension PresentationGroupCallState {
|
||||
static func initialValue(myPeerId: PeerId) -> PresentationGroupCallState {
|
||||
static func initialValue(myPeerId: PeerId, title: String?) -> PresentationGroupCallState {
|
||||
return PresentationGroupCallState(
|
||||
myPeerId: myPeerId,
|
||||
networkState: .connecting,
|
||||
@ -204,7 +204,7 @@ private extension PresentationGroupCallState {
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
defaultParticipantMuteState: nil,
|
||||
recordingStartTimestamp: nil,
|
||||
title: nil,
|
||||
title: title,
|
||||
raisedHand: false
|
||||
)
|
||||
}
|
||||
@ -345,15 +345,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
public let internalId: CallSessionInternalId
|
||||
public let peerId: PeerId
|
||||
private var joinAsPeerId: PeerId
|
||||
public let peer: Peer?
|
||||
|
||||
public private(set) var isVideo: Bool
|
||||
|
||||
private let temporaryJoinTimestamp: Int32
|
||||
|
||||
private var internalState: InternalState = .requesting
|
||||
private let internalStatePromise = Promise<InternalState>(.requesting)
|
||||
|
||||
private var callContext: OngoingGroupCallContext?
|
||||
private var currentConnectionMode: OngoingGroupCallContext.ConnectionMode = .none
|
||||
private var ssrcMapping: [UInt32: PeerId] = [:]
|
||||
|
||||
private var requestedSsrcs = Set<UInt32>()
|
||||
@ -428,6 +429,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
public var canBeRemoved: Signal<Bool, NoError> {
|
||||
return self._canBeRemoved.get()
|
||||
}
|
||||
private var markedAsCanBeRemoved = false
|
||||
|
||||
private let wasRemoved = Promise<Bool>(false)
|
||||
|
||||
@ -481,6 +483,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private let isMutedDisposable = MetaDisposable()
|
||||
private let memberStatesDisposable = MetaDisposable()
|
||||
private let leaveDisposable = MetaDisposable()
|
||||
|
||||
private var isReconnectingAsSpeaker = false {
|
||||
didSet {
|
||||
if self.isReconnectingAsSpeaker != oldValue {
|
||||
self.isReconnectingAsSpeakerPromise.set(self.isReconnectingAsSpeaker)
|
||||
}
|
||||
}
|
||||
}
|
||||
private let isReconnectingAsSpeakerPromise = ValuePromise<Bool>(false)
|
||||
|
||||
private var checkCallDisposable: Disposable?
|
||||
private var isCurrentlyConnecting: Bool?
|
||||
@ -515,7 +526,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
initialCall: CachedChannelData.ActiveCall?,
|
||||
internalId: CallSessionInternalId,
|
||||
peerId: PeerId,
|
||||
peer: Peer?,
|
||||
joinAsPeerId: PeerId?
|
||||
) {
|
||||
self.account = accountContext.account
|
||||
@ -527,10 +537,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.initialCall = initialCall
|
||||
self.internalId = internalId
|
||||
self.peerId = peerId
|
||||
self.peer = peer
|
||||
self.joinAsPeerId = joinAsPeerId ?? accountContext.account.peerId
|
||||
|
||||
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId)
|
||||
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title)
|
||||
self.statePromise = ValuePromise(self.stateValue)
|
||||
|
||||
self.temporaryJoinTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
@ -654,13 +663,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
if participantUpdate.peerId == strongSelf.joinAsPeerId {
|
||||
if case let .established(_, _, _, ssrc, _) = strongSelf.internalState, ssrc == participantUpdate.ssrc {
|
||||
strongSelf._canBeRemoved.set(.single(true))
|
||||
strongSelf.markAsCanBeRemoved()
|
||||
}
|
||||
}
|
||||
} else if participantUpdate.peerId == strongSelf.joinAsPeerId {
|
||||
if case let .established(_, connectionMode, _, ssrc, _) = strongSelf.internalState {
|
||||
if ssrc != participantUpdate.ssrc {
|
||||
strongSelf._canBeRemoved.set(.single(true))
|
||||
strongSelf.markAsCanBeRemoved()
|
||||
} else if case .broadcast = connectionMode {
|
||||
let canUnmute: Bool
|
||||
if let muteState = participantUpdate.muteState {
|
||||
@ -670,7 +679,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
if canUnmute {
|
||||
strongSelf.requestCall()
|
||||
strongSelf.requestCall(movingFromBroadcastToRtc: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -684,7 +693,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
case let .call(isTerminated, _, _, _):
|
||||
if isTerminated {
|
||||
strongSelf._canBeRemoved.set(.single(true))
|
||||
strongSelf.markAsCanBeRemoved()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -734,7 +743,31 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
})
|
||||
|
||||
self.requestCall()
|
||||
let _ = (self.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
var canManageCall = false
|
||||
if let peer = peer as? TelegramGroup {
|
||||
if case .creator = peer.role {
|
||||
canManageCall = true
|
||||
} else if case let .admin(rights, _) = peer.role, rights.rights.contains(.canManageCalls) {
|
||||
canManageCall = true
|
||||
}
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
if peer.flags.contains(.isCreator) {
|
||||
canManageCall = true
|
||||
} else if (peer.adminRights?.rights.contains(.canManageCalls) == true) {
|
||||
canManageCall = true
|
||||
}
|
||||
}
|
||||
if let strongSelf = self {
|
||||
var updatedValue = strongSelf.stateValue
|
||||
updatedValue.canManageCall = canManageCall
|
||||
strongSelf.stateValue = updatedValue
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
self.requestCall(movingFromBroadcastToRtc: false)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -828,6 +861,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
jsonParams: nil,
|
||||
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
||||
raiseHandRating: nil,
|
||||
hasRaiseHand: false,
|
||||
activityTimestamp: nil,
|
||||
activityRank: nil,
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
@ -880,6 +914,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
let previousInternalState = self.internalState
|
||||
self.internalState = internalState
|
||||
self.internalStatePromise.set(.single(internalState))
|
||||
|
||||
if let audioSessionControl = audioSessionControl, previousControl == nil {
|
||||
switch self.currentSelectedAudioOutputValue {
|
||||
@ -924,7 +959,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
return
|
||||
}
|
||||
if case .established = strongSelf.internalState {
|
||||
strongSelf.requestCall()
|
||||
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -983,10 +1018,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
switch joinCallResult.connectionMode {
|
||||
case .rtc:
|
||||
strongSelf.callContext?.setConnectionMode(.rtc)
|
||||
strongSelf.currentConnectionMode = .rtc
|
||||
strongSelf.callContext?.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false)
|
||||
strongSelf.callContext?.setJoinResponse(payload: clientParams, participants: addedParticipants)
|
||||
case .broadcast:
|
||||
strongSelf.callContext?.setConnectionMode(.broadcast)
|
||||
strongSelf.currentConnectionMode = .broadcast
|
||||
strongSelf.callContext?.setConnectionMode(.broadcast, keepBroadcastConnectedIfWasEnabled: false)
|
||||
}
|
||||
|
||||
strongSelf.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
|
||||
@ -1006,7 +1043,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
|
||||
]), on: .root, blockInteraction: false, completion: {})
|
||||
}
|
||||
strongSelf._canBeRemoved.set(.single(true))
|
||||
strongSelf.markAsCanBeRemoved()
|
||||
}))
|
||||
}))
|
||||
|
||||
@ -1016,12 +1053,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
return
|
||||
}
|
||||
let mappedState: PresentationGroupCallState.NetworkState
|
||||
switch state {
|
||||
case .connecting:
|
||||
mappedState = .connecting
|
||||
case .connected:
|
||||
if state.isConnected {
|
||||
mappedState = .connected
|
||||
} else {
|
||||
mappedState = .connecting
|
||||
}
|
||||
|
||||
let wasConnecting = strongSelf.stateValue.networkState == .connecting
|
||||
if strongSelf.stateValue.networkState != mappedState {
|
||||
strongSelf.stateValue.networkState = mappedState
|
||||
@ -1037,8 +1074,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.checkCallDisposable = nil
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.isReconnectingAsSpeaker = state.isTransitioningFromBroadcastToRtc
|
||||
|
||||
if (wasConnecting != isConnecting && strongSelf.didConnectOnce) { //|| !strongSelf.didStartConnectingOnce {
|
||||
if (wasConnecting != isConnecting && strongSelf.didConnectOnce) {
|
||||
if isConnecting {
|
||||
let toneRenderer = PresentationCallToneRenderer(tone: .groupConnecting)
|
||||
strongSelf.toneRenderer = toneRenderer
|
||||
@ -1052,7 +1091,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.didStartConnectingOnce = true
|
||||
}
|
||||
|
||||
if case .connected = state {
|
||||
if state.isConnected {
|
||||
if !strongSelf.didConnectOnce {
|
||||
strongSelf.didConnectOnce = true
|
||||
|
||||
@ -1179,8 +1218,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.speakingParticipantsContext.get(),
|
||||
adminIds,
|
||||
myPeer,
|
||||
accountContext.account.postbox.peerView(id: peerId)
|
||||
).start(next: { [weak self] state, activeSpeakers, speakingParticipants, adminIds, myPeer, view in
|
||||
accountContext.account.postbox.peerView(id: peerId),
|
||||
self.isReconnectingAsSpeakerPromise.get()
|
||||
).start(next: { [weak self] state, activeSpeakers, speakingParticipants, adminIds, myPeer, view, isReconnectingAsSpeaker in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1227,6 +1267,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
jsonParams: nil,
|
||||
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
||||
raiseHandRating: nil,
|
||||
hasRaiseHand: false,
|
||||
activityTimestamp: nil,
|
||||
activityRank: nil,
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
@ -1291,7 +1332,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
})
|
||||
}
|
||||
|
||||
if let muteState = participant.muteState {
|
||||
var filteredMuteState = participant.muteState
|
||||
if isReconnectingAsSpeaker || strongSelf.currentConnectionMode != .rtc {
|
||||
filteredMuteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: false)
|
||||
}
|
||||
|
||||
if let muteState = filteredMuteState {
|
||||
if muteState.canUnmute {
|
||||
switch strongSelf.isMutedValue {
|
||||
case let .muted(isPushToTalkActive):
|
||||
@ -1341,7 +1387,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
strongSelf.stateValue.recordingStartTimestamp = state.recordingStartTimestamp
|
||||
strongSelf.stateValue.title = state.title
|
||||
|
||||
|
||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
participantCount: state.totalCount,
|
||||
topParticipants: topParticipants,
|
||||
@ -1473,7 +1519,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
return
|
||||
}
|
||||
strongSelf.checkCallDisposable = nil
|
||||
strongSelf.requestCall()
|
||||
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1486,6 +1532,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
private func markAsCanBeRemoved() {
|
||||
if self.markedAsCanBeRemoved {
|
||||
return
|
||||
}
|
||||
self.markedAsCanBeRemoved = true
|
||||
|
||||
self.callContext?.stop()
|
||||
self._canBeRemoved.set(.single(true))
|
||||
|
||||
@ -1535,7 +1586,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.stateValue.myPeerId = peerId
|
||||
}
|
||||
|
||||
strongSelf.requestCall()
|
||||
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1784,14 +1835,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.participantsContext?.updateShouldBeRecording(shouldBeRecording, title: title)
|
||||
}
|
||||
|
||||
private func requestCall() {
|
||||
self.callContext?.setConnectionMode(.none)
|
||||
private func requestCall(movingFromBroadcastToRtc: Bool) {
|
||||
self.currentConnectionMode = .none
|
||||
self.callContext?.setConnectionMode(.none, keepBroadcastConnectedIfWasEnabled: movingFromBroadcastToRtc)
|
||||
|
||||
self.missingSsrcsDisposable.set(nil)
|
||||
self.missingSsrcs.removeAll()
|
||||
self.processedMissingSsrcs.removeAll()
|
||||
|
||||
self.internalState = .requesting
|
||||
self.internalStatePromise.set(.single(.requesting))
|
||||
self.isCurrentlyConnecting = nil
|
||||
|
||||
enum CallError {
|
||||
@ -1829,7 +1882,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.checkCallDisposable = nil
|
||||
|
||||
self.stateValue.networkState = .connecting
|
||||
self.stateValue.title = initialCall?.title
|
||||
|
||||
if !movingFromBroadcastToRtc {
|
||||
self.stateValue.networkState = .connecting
|
||||
}
|
||||
|
||||
self.requestDisposable.set((currentOrRequestedCall
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
@ -1842,7 +1898,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl)
|
||||
} else {
|
||||
strongSelf._canBeRemoved.set(.single(true))
|
||||
strongSelf.markAsCanBeRemoved()
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -1856,7 +1912,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
updatedInvitedPeers.insert(peerId, at: 0)
|
||||
self.invitedPeersValue = updatedInvitedPeers
|
||||
|
||||
let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId, canUnmute: false).start()
|
||||
let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
||||
|
||||
return true
|
||||
}
|
||||
@ -1875,6 +1931,31 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let _ = editGroupCallTitle(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, title: title).start()
|
||||
}
|
||||
|
||||
public var inviteLinks: Signal<GroupCallInviteLinks?, NoError> {
|
||||
return self.state
|
||||
|> map { state -> PeerId in
|
||||
return state.myPeerId
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { _ -> Signal<GroupCallInviteLinks?, NoError> in
|
||||
return self.internalStatePromise.get()
|
||||
|> filter { state -> Bool in
|
||||
if case .requesting = state {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|> mapToSignal { state in
|
||||
if let callInfo = state.callInfo {
|
||||
return groupCallInviteLinks(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var currentMyAudioLevel: Float = 0.0
|
||||
private var currentMyAudioLevelTimestamp: Double = 0.0
|
||||
private var isSendingTyping: Bool = false
|
||||
|
@ -727,10 +727,10 @@ public final class VoiceChatController: ViewController {
|
||||
self.topPanelEdgeNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
|
||||
self.optionsButton = VoiceChatHeaderButton()
|
||||
self.optionsButton.setImage(optionsButtonImage(dark: false))
|
||||
self.closeButton = VoiceChatHeaderButton()
|
||||
self.closeButton.setImage(closeButtonImage(dark: false))
|
||||
self.optionsButton = VoiceChatHeaderButton(context: context)
|
||||
self.optionsButton.setContent(.image(optionsButtonImage(dark: false)))
|
||||
self.closeButton = VoiceChatHeaderButton(context: context)
|
||||
self.closeButton.setContent(.image(closeButtonImage(dark: false)))
|
||||
|
||||
self.titleNode = VoiceChatControllerTitleNode(theme: self.presentationData.theme)
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
@ -782,7 +782,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let displayAsPeers: Signal<[FoundPeer], NoError> = currentAccountPeer
|
||||
|> then(
|
||||
combineLatest(currentAccountPeer, groupCallDisplayAsAvailablePeers(network: context.account.network, postbox: context.account.postbox, peerId: call.peerId))
|
||||
combineLatest(currentAccountPeer, cachedGroupCallDisplayAsAvailablePeers(account: context.account, peerId: call.peerId))
|
||||
|> map { currentAccountPeer, availablePeers -> [FoundPeer] in
|
||||
var result = currentAccountPeer
|
||||
result.append(contentsOf: availablePeers)
|
||||
@ -874,7 +874,14 @@ public final class VoiceChatController: ViewController {
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: participant.peer, text: strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false })
|
||||
}
|
||||
} else {
|
||||
strongSelf.controller?.present(textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: strongSelf.presentationData.strings.VoiceChat_InviteMemberToGroupFirstText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), groupPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.VoiceChat_InviteMemberToGroupFirstAdd, action: {
|
||||
let text: String
|
||||
if let peer = groupPeer as? TelegramChannel, case .broadcast = peer.info {
|
||||
text = strongSelf.presentationData.strings.VoiceChat_InviteMemberToChannelFirstText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), groupPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.VoiceChat_InviteMemberToGroupFirstText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), groupPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.VoiceChat_InviteMemberToGroupFirstAdd, action: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1438,6 +1445,10 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.cameraButtonNode.addTarget(self, action: #selector(self.cameraPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
let inviteLinksPromise = Promise<GroupCallInviteLinks?>(nil)
|
||||
inviteLinksPromise.set(.single(nil)
|
||||
|> then(call.inviteLinks))
|
||||
|
||||
let avatarSize = CGSize(width: 28.0, height: 28.0)
|
||||
self.optionsButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||
@ -1496,9 +1507,11 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.call.reconnect(as: peer.peer.id)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer.peer, text: strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false })
|
||||
if peer.peer.id != myPeerId {
|
||||
strongSelf.call.reconnect(as: peer.peer.id)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer.peer, text: strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false })
|
||||
}
|
||||
})))
|
||||
|
||||
if peer.peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
@ -1563,9 +1576,9 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
mainItemsImpl = {
|
||||
return combineLatest(displayAsPeersPromise.get(), context.account.postbox.loadedPeerWithId(call.peerId))
|
||||
return combineLatest(displayAsPeersPromise.get(), context.account.postbox.loadedPeerWithId(call.peerId), inviteLinksPromise.get())
|
||||
|> take(1)
|
||||
|> map { peers, chatPeer -> [ContextMenuItem] in
|
||||
|> map { peers, chatPeer, inviteLinks -> [ContextMenuItem] in
|
||||
let presentationData = strongSelf.presentationData
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
@ -1610,52 +1623,46 @@ public final class VoiceChatController: ViewController {
|
||||
})))
|
||||
}
|
||||
|
||||
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
|
||||
f(.default)
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> String? in
|
||||
if let peer = transaction.getPeer(call.peerId), let addressName = peer.addressName, !addressName.isEmpty {
|
||||
return "https://t.me/\(addressName)"
|
||||
} else if let cachedData = transaction.getPeerCachedData(peerId: call.peerId) {
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.exportedInvitation?.link
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
return cachedData.exportedInvitation?.link
|
||||
}
|
||||
if let inviteLinks = inviteLinks {
|
||||
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
|
||||
f(.default)
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
} |> deliverOnMainQueue).start(next: { link in
|
||||
if let link = link {
|
||||
if let strongSelf = self {
|
||||
let formatSendTitle: (String) -> String = { string in
|
||||
var string = string
|
||||
if string.contains("[") && string.contains("]") {
|
||||
if let startIndex = string.firstIndex(of: "["), let endIndex = string.firstIndex(of: "]") {
|
||||
string.removeSubrange(startIndex ... endIndex)
|
||||
}
|
||||
} else {
|
||||
string = string.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
|
||||
}
|
||||
return string
|
||||
|
||||
let formatSendTitle: (String) -> String = { string in
|
||||
var string = string
|
||||
if string.contains("[") && string.contains("]") {
|
||||
if let startIndex = string.firstIndex(of: "["), let endIndex = string.firstIndex(of: "]") {
|
||||
string.removeSubrange(startIndex ... endIndex)
|
||||
}
|
||||
|
||||
let segmentedValues = [ShareControllerSegmentedValue(title: strongSelf.presentationData.strings.VoiceChat_InviteLink_Speaker, subject: .url(link), actionTitle: strongSelf.presentationData.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(strongSelf.presentationData.strings.VoiceChat_InviteLink_InviteSpeakers(Int32(count)))
|
||||
}), ShareControllerSegmentedValue(title: strongSelf.presentationData.strings.VoiceChat_InviteLink_Listener, subject: .url(link), actionTitle: strongSelf.presentationData.strings.VoiceChat_InviteLink_CopyListenerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(strongSelf.presentationData.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
|
||||
})]
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .url(link), segmentedValues: segmentedValues, forcedTheme: strongSelf.darkTheme, forcedActionTitle: strongSelf.presentationData.strings.VoiceChat_CopyInviteLink)
|
||||
strongSelf.controller?.present(shareController, in: .window(.root))
|
||||
} else {
|
||||
string = string.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
var segmentedValues: [ShareControllerSegmentedValue]?
|
||||
if let speakerLink = inviteLinks.speakerLink {
|
||||
segmentedValues = [ShareControllerSegmentedValue(title: strongSelf.presentationData.strings.VoiceChat_InviteLink_Speaker, subject: .url(speakerLink), actionTitle: strongSelf.presentationData.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(strongSelf.presentationData.strings.VoiceChat_InviteLink_InviteSpeakers(Int32(count)))
|
||||
}), ShareControllerSegmentedValue(title: strongSelf.presentationData.strings.VoiceChat_InviteLink_Listener, subject: .url(inviteLinks.listenerLink), actionTitle: strongSelf.presentationData.strings.VoiceChat_InviteLink_CopyListenerLink, formatSendTitle: { count in
|
||||
return formatSendTitle(strongSelf.presentationData.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
|
||||
})]
|
||||
}
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forcedTheme: strongSelf.darkTheme, forcedActionTitle: strongSelf.presentationData.strings.VoiceChat_InviteLink_CopyListenerLink)
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
}
|
||||
})
|
||||
})))
|
||||
strongSelf.controller?.present(shareController, in: .window(.root))
|
||||
})))
|
||||
}
|
||||
|
||||
if let recordingStartTimestamp = strongSelf.callState?.recordingStartTimestamp {
|
||||
items.append(.custom(VoiceChatRecordingContextItem(timestamp: recordingStartTimestamp, action: { _, f in
|
||||
@ -2259,8 +2266,8 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: isFullscreen)
|
||||
|
||||
self.optionsButton.setImage(optionsButtonImage(dark: isFullscreen), animated: transition.isAnimated)
|
||||
self.closeButton.setImage(closeButtonImage(dark: isFullscreen), animated: transition.isAnimated)
|
||||
self.optionsButton.setContent(.image(optionsButtonImage(dark: isFullscreen)), animated: transition.isAnimated)
|
||||
self.closeButton.setContent(.image(closeButtonImage(dark: isFullscreen)), animated: transition.isAnimated)
|
||||
|
||||
self.updateTitle(transition: transition)
|
||||
}
|
||||
@ -2737,10 +2744,12 @@ public final class VoiceChatController: ViewController {
|
||||
var processedPeerIds = Set<PeerId>()
|
||||
|
||||
var canInvite = true
|
||||
if let peer = self.peer as? TelegramChannel, peer.flags.contains(.isGigagroup) {
|
||||
if peer.flags.contains(.isCreator) || peer.adminRights != nil {
|
||||
} else {
|
||||
canInvite = false
|
||||
if let peer = self.peer as? TelegramChannel {
|
||||
if peer.flags.contains(.isGigagroup) || (peer.addressName?.isEmpty ?? true) {
|
||||
if peer.flags.contains(.isCreator) || peer.adminRights != nil {
|
||||
} else {
|
||||
canInvite = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if canInvite {
|
||||
@ -2755,7 +2764,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let memberState: PeerEntry.State
|
||||
var memberMuteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
if member.raiseHandRating != nil {
|
||||
if member.raiseHandRating != nil || member.hasRaiseHand {
|
||||
memberState = .raisedHand
|
||||
memberMuteState = member.muteState
|
||||
} else if member.peer.id == self.callState?.myPeerId {
|
||||
|
@ -2,6 +2,10 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import AvatarNode
|
||||
|
||||
func optionsBackgroundImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||
@ -45,13 +49,25 @@ func closeButtonImage(dark: Bool) -> UIImage? {
|
||||
}
|
||||
|
||||
final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
enum Content {
|
||||
case image(UIImage?)
|
||||
case avatar(Peer)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private var theme: PresentationTheme
|
||||
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let iconNode: ASImageNode
|
||||
private let avatarNode: AvatarNode
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
init() {
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.theme = context.sharedContext.currentPresentationData.with { $0 }.theme
|
||||
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
@ -60,10 +76,14 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.contentMode = .scaleToFill
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 17.0))
|
||||
self.avatarNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.addSubnode(self.iconNode)
|
||||
self.referenceNode.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
@ -84,20 +104,46 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
self.iconNode.frame = self.containerNode.bounds
|
||||
self.avatarNode.frame = self.containerNode.bounds
|
||||
}
|
||||
|
||||
func setImage(_ image: UIImage?, animated: Bool = false) {
|
||||
if animated, let snapshotView = self.iconNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.iconNode.frame
|
||||
self.view.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
private var content: Content?
|
||||
func setContent(_ content: Content, animated: Bool = false) {
|
||||
if animated {
|
||||
switch content {
|
||||
case let .image(image):
|
||||
if let snapshotView = self.referenceNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.referenceNode.frame
|
||||
self.view.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.avatarNode.isHidden = true
|
||||
case let .avatar(peer):
|
||||
self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: peer)
|
||||
self.iconNode.isHidden = true
|
||||
self.avatarNode.isHidden = false
|
||||
|
||||
}
|
||||
} else {
|
||||
self.content = content
|
||||
switch content {
|
||||
case let .image(image):
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.avatarNode.isHidden = true
|
||||
case let .avatar(peer):
|
||||
self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: peer)
|
||||
self.iconNode.isHidden = true
|
||||
self.avatarNode.isHidden = false
|
||||
}
|
||||
}
|
||||
self.iconNode.image = image
|
||||
}
|
||||
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.view.isOpaque = false
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import SyncCore
|
||||
import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
@ -32,6 +33,8 @@ private func createChannel(account: Account, title: String, description: String?
|
||||
address = location.address
|
||||
}
|
||||
|
||||
transaction.clearItemCacheCollection(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers)
|
||||
|
||||
return account.network.request(Api.functions.channels.createChannel(flags: flags, title: title, about: description ?? "", geoPoint: geoPoint, address: address), automaticFloodWait: false)
|
||||
|> mapError { error -> CreateChannelError in
|
||||
if error.errorCode == 406 {
|
||||
|
@ -40,7 +40,7 @@ public struct GroupCallSummary: Equatable {
|
||||
extension GroupCallInfo {
|
||||
init?(_ call: Api.GroupCall) {
|
||||
switch call {
|
||||
case let .groupCall(_, id, accessHash, participantCount, params, title, streamDcId, recordStartDate, _):
|
||||
case let .groupCall(flags, id, accessHash, participantCount, params, title, streamDcId, recordStartDate, _):
|
||||
var clientParams: String?
|
||||
if let params = params {
|
||||
switch params {
|
||||
@ -143,6 +143,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
|
||||
jsonParams: jsonParams,
|
||||
joinTimestamp: date,
|
||||
raiseHandRating: raiseHandRating,
|
||||
hasRaiseHand: raiseHandRating != nil,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
activityRank: nil,
|
||||
muteState: muteState,
|
||||
@ -309,6 +310,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
||||
jsonParams: jsonParams,
|
||||
joinTimestamp: date,
|
||||
raiseHandRating: raiseHandRating,
|
||||
hasRaiseHand: raiseHandRating != nil,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
activityRank: nil,
|
||||
muteState: muteState,
|
||||
@ -337,29 +339,6 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
||||
}
|
||||
}
|
||||
|
||||
public func inviteToGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, users: Set<PeerId>, canUnmute: Bool) -> Signal<Void, NoError> {
|
||||
return .complete()
|
||||
// return account.postbox.transaction { transaction -> [Api.InputUser] in
|
||||
// var inputUsers: [Api.InputUser] = []
|
||||
// for user in users {
|
||||
// if let peer = transaction.getPeer(user), let inputUser = apiInputUser(peer) {
|
||||
// inputUsers.append(inputUser)
|
||||
// }
|
||||
// }
|
||||
// return inputUsers
|
||||
// }
|
||||
// |> mapToSignal { users -> Signal<Void, NoError> in
|
||||
// return account.network.request(Api.functions.phone.inviteToGroupCall(flags: 0, call: .inputGroupCall(id: callId, accessHash: accessHash), users: users))
|
||||
// |> `catch` { _ -> Signal<Void, NoError> in
|
||||
// return .single(Void())
|
||||
// } |> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
// account.stateManager.addUpdates(updates)
|
||||
//
|
||||
// return .single(Void())
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public enum JoinGroupCallError {
|
||||
case generic
|
||||
case anonymousNotAllowed
|
||||
@ -714,6 +693,7 @@ public final class GroupCallParticipantsContext {
|
||||
public var jsonParams: String?
|
||||
public var joinTimestamp: Int32
|
||||
public var raiseHandRating: Int64?
|
||||
public var hasRaiseHand: Bool
|
||||
public var activityTimestamp: Double?
|
||||
public var activityRank: Int?
|
||||
public var muteState: MuteState?
|
||||
@ -726,6 +706,7 @@ public final class GroupCallParticipantsContext {
|
||||
jsonParams: String?,
|
||||
joinTimestamp: Int32,
|
||||
raiseHandRating: Int64?,
|
||||
hasRaiseHand: Bool,
|
||||
activityTimestamp: Double?,
|
||||
activityRank: Int?,
|
||||
muteState: MuteState?,
|
||||
@ -737,6 +718,7 @@ public final class GroupCallParticipantsContext {
|
||||
self.jsonParams = jsonParams
|
||||
self.joinTimestamp = joinTimestamp
|
||||
self.raiseHandRating = raiseHandRating
|
||||
self.hasRaiseHand = hasRaiseHand
|
||||
self.activityTimestamp = activityTimestamp
|
||||
self.activityRank = activityRank
|
||||
self.muteState = muteState
|
||||
@ -762,6 +744,9 @@ public final class GroupCallParticipantsContext {
|
||||
if lhs.raiseHandRating != rhs.raiseHandRating {
|
||||
return false
|
||||
}
|
||||
if lhs.hasRaiseHand != rhs.hasRaiseHand {
|
||||
return false
|
||||
}
|
||||
if lhs.activityTimestamp != rhs.activityTimestamp {
|
||||
return false
|
||||
}
|
||||
@ -973,17 +958,27 @@ public final class GroupCallParticipantsContext {
|
||||
public var immediateState: State?
|
||||
|
||||
public var state: Signal<State, NoError> {
|
||||
let accountPeerId = self.account.peerId
|
||||
return self.statePromise.get()
|
||||
|> map { state -> State in
|
||||
if state.overlayState.isEmpty {
|
||||
return state.state
|
||||
}
|
||||
var publicState = state.state
|
||||
var sortAgain = false
|
||||
let canSeeHands = state.state.isCreator || state.state.adminIds.contains(accountPeerId)
|
||||
for i in 0 ..< publicState.participants.count {
|
||||
if let pendingMuteState = state.overlayState.pendingMuteStateChanges[publicState.participants[i].peer.id] {
|
||||
publicState.participants[i].muteState = pendingMuteState.state
|
||||
publicState.participants[i].volume = pendingMuteState.volume
|
||||
}
|
||||
if !canSeeHands && publicState.participants[i].raiseHandRating != nil {
|
||||
publicState.participants[i].raiseHandRating = nil
|
||||
sortAgain = true
|
||||
}
|
||||
}
|
||||
if sortAgain {
|
||||
publicState.participants.sort()
|
||||
}
|
||||
return publicState
|
||||
}
|
||||
@ -1389,6 +1384,7 @@ public final class GroupCallParticipantsContext {
|
||||
jsonParams: participantUpdate.jsonParams,
|
||||
joinTimestamp: participantUpdate.joinTimestamp,
|
||||
raiseHandRating: participantUpdate.raiseHandRating,
|
||||
hasRaiseHand: participantUpdate.raiseHandRating != nil,
|
||||
activityTimestamp: activityTimestamp,
|
||||
activityRank: previousActivityRank,
|
||||
muteState: participantUpdate.muteState,
|
||||
@ -1755,7 +1751,7 @@ public enum InviteToGroupCallError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func inviteToGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId, canUnmute: Bool) -> Signal<Never, InviteToGroupCallError> {
|
||||
public func inviteToGroupCall(account: Account, callId: Int64, accessHash: Int64, peerId: PeerId) -> Signal<Never, InviteToGroupCallError> {
|
||||
return account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
@ -1767,12 +1763,8 @@ public func inviteToGroupCall(account: Account, callId: Int64, accessHash: Int64
|
||||
guard let apiUser = apiInputUser(user) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
var flags: Int32 = 0
|
||||
if canUnmute {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.phone.inviteToGroupCall(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), users: [apiUser]))
|
||||
return account.network.request(Api.functions.phone.inviteToGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), users: [apiUser]))
|
||||
|> mapError { _ -> InviteToGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
@ -1784,6 +1776,47 @@ public func inviteToGroupCall(account: Account, callId: Int64, accessHash: Int64
|
||||
}
|
||||
}
|
||||
|
||||
public struct GroupCallInviteLinks {
|
||||
public let listenerLink: String
|
||||
public let speakerLink: String?
|
||||
}
|
||||
|
||||
public func groupCallInviteLinks(account: Account, callId: Int64, accessHash: Int64) -> Signal<GroupCallInviteLinks?, NoError> {
|
||||
let call = Api.InputGroupCall.inputGroupCall(id: callId, accessHash: accessHash)
|
||||
let listenerInvite: Signal<String?, NoError> = account.network.request(Api.functions.phone.exportGroupCallInvite(flags: 0, call: call))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.phone.ExportedGroupCallInvite?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<String?, NoError> in
|
||||
if let result = result, case let .exportedGroupCallInvite(link) = result {
|
||||
return .single(link)
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
let speakerInvite: Signal<String?, NoError> = account.network.request(Api.functions.phone.exportGroupCallInvite(flags: 1 << 0, call: call))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.phone.ExportedGroupCallInvite?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<String?, NoError> in
|
||||
if let result = result, case let .exportedGroupCallInvite(link) = result {
|
||||
return .single(link)
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
return combineLatest(listenerInvite, speakerInvite)
|
||||
|> map { listenerLink, speakerLink in
|
||||
if let listenerLink = listenerLink {
|
||||
return GroupCallInviteLinks(listenerLink: listenerLink, speakerLink: speakerLink)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum EditGroupCallTitleError {
|
||||
case generic
|
||||
}
|
||||
@ -1861,6 +1894,7 @@ public final class CachedDisplayAsPeers: PostboxCoding {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: PeerId) -> Signal<[FoundPeer], NoError> {
|
||||
let key = ValueBoxKey(length: 8)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
@ -1884,14 +1918,14 @@ public func cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: Pee
|
||||
}
|
||||
|> mapToSignal { cachedPeersAndTimestamp -> Signal<[FoundPeer], NoError> in
|
||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if let (cachedPeers, timestamp) = cachedPeersAndTimestamp, currentTimestamp - timestamp < 60 * 5 && !cachedPeers.isEmpty {
|
||||
if let (cachedPeers, timestamp) = cachedPeersAndTimestamp, currentTimestamp - timestamp < 60 * 3 && !cachedPeers.isEmpty {
|
||||
return .single(cachedPeers)
|
||||
} else {
|
||||
return groupCallDisplayAsAvailablePeers(network: account.network, postbox: account.postbox, peerId: peerId)
|
||||
|> mapToSignal { peers -> Signal<[FoundPeer], NoError> in
|
||||
return account.postbox.transaction { transaction -> [FoundPeer] in
|
||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers, key: key), entry: CachedDisplayAsPeers(peerIds: peers.map { $0.peer.id }, timestamp: currentTimestamp), collectionSpec: ItemCacheCollectionSpec(lowWaterItemCount: 1, highWaterItemCount: 1))
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers, key: key), entry: CachedDisplayAsPeers(peerIds: peers.map { $0.peer.id }, timestamp: currentTimestamp), collectionSpec: ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20))
|
||||
return peers
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -11865,7 +11865,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return [FoundPeer(peer: peer, subscribers: nil)]
|
||||
}
|
||||
|
||||
let _ = (combineLatest(currentAccountPeer, groupCallDisplayAsAvailablePeers(network: context.account.network, postbox: context.account.postbox, peerId: peerId))
|
||||
let _ = (combineLatest(currentAccountPeer, cachedGroupCallDisplayAsAvailablePeers(account: context.account, peerId: peerId))
|
||||
|> map { currentAccountPeer, availablePeers -> [FoundPeer] in
|
||||
var result = currentAccountPeer
|
||||
result.append(contentsOf: availablePeers)
|
||||
|
@ -279,6 +279,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewGroup
|
||||
case "telegram_message":
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewMessage
|
||||
case "telegram_voicechat":
|
||||
actionTitle = item.presentationData.strings.Conversation_JoinVoiceChat
|
||||
case "telegram_background":
|
||||
title = item.presentationData.strings.Conversation_ChatBackground
|
||||
subtitle = nil
|
||||
|
@ -1742,7 +1742,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
viewControllers.remove(atOffsets: IndexSet(indexesToRemove))
|
||||
for i in indexesToRemove.sorted().reversed() {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}))
|
||||
}
|
||||
@ -1882,7 +1884,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
viewControllers.remove(atOffsets: IndexSet(indexesToRemove))
|
||||
for i in indexesToRemove.sorted().reversed() {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}))
|
||||
}
|
||||
@ -2780,7 +2784,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) {
|
||||
self.displayAsPeersPromise.set(groupCallDisplayAsAvailablePeers(network: context.account.network, postbox: context.account.postbox, peerId: peerId))
|
||||
self.displayAsPeersPromise.set(cachedGroupCallDisplayAsAvailablePeers(account: context.account, peerId: peerId))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,9 +134,9 @@ public final class OngoingGroupCallContext {
|
||||
case broadcast
|
||||
}
|
||||
|
||||
public enum NetworkState {
|
||||
case connecting
|
||||
case connected
|
||||
public struct NetworkState: Equatable {
|
||||
public var isConnected: Bool
|
||||
public var isTransitioningFromBroadcastToRtc: Bool
|
||||
}
|
||||
|
||||
public enum AudioLevelKey: Hashable {
|
||||
@ -151,7 +151,7 @@ public final class OngoingGroupCallContext {
|
||||
let sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max))
|
||||
|
||||
let joinPayload = Promise<(String, UInt32)>()
|
||||
let networkState = ValuePromise<NetworkState>(.connecting, ignoreRepeated: true)
|
||||
let networkState = ValuePromise<NetworkState>(NetworkState(isConnected: false, isTransitioningFromBroadcastToRtc: false), ignoreRepeated: true)
|
||||
let isMuted = ValuePromise<Bool>(true, ignoreRepeated: true)
|
||||
let audioLevels = ValuePipe<[(AudioLevelKey, Float, Bool)]>()
|
||||
|
||||
@ -210,16 +210,7 @@ public final class OngoingGroupCallContext {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let mappedState: NetworkState
|
||||
switch state {
|
||||
case .connecting:
|
||||
mappedState = .connecting
|
||||
case .connected:
|
||||
mappedState = .connected
|
||||
@unknown default:
|
||||
mappedState = .connecting
|
||||
}
|
||||
strongSelf.networkState.set(mappedState)
|
||||
strongSelf.networkState.set(NetworkState(isConnected: state.isConnected, isTransitioningFromBroadcastToRtc: state.isTransitioningFromBroadcastToRtc))
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +286,7 @@ public final class OngoingGroupCallContext {
|
||||
self.context.stop()
|
||||
}
|
||||
|
||||
func setConnectionMode(_ connectionMode: ConnectionMode) {
|
||||
func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool) {
|
||||
let mappedConnectionMode: OngoingCallConnectionMode
|
||||
switch connectionMode {
|
||||
case .none:
|
||||
@ -305,7 +296,7 @@ public final class OngoingGroupCallContext {
|
||||
case .broadcast:
|
||||
mappedConnectionMode = .broadcast
|
||||
}
|
||||
self.context.setConnectionMode(mappedConnectionMode)
|
||||
self.context.setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled)
|
||||
|
||||
if (mappedConnectionMode != .rtc) {
|
||||
self.joinPayload.set(.never())
|
||||
@ -504,9 +495,9 @@ public final class OngoingGroupCallContext {
|
||||
})
|
||||
}
|
||||
|
||||
public func setConnectionMode(_ connectionMode: ConnectionMode) {
|
||||
public func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool) {
|
||||
self.impl.with { impl in
|
||||
impl.setConnectionMode(connectionMode)
|
||||
impl.setConnectionMode(connectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,10 +151,10 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
- (void)switchAudioInput:(NSString * _Nonnull)deviceId;
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(int32_t, GroupCallNetworkState) {
|
||||
GroupCallNetworkStateConnecting,
|
||||
GroupCallNetworkStateConnected
|
||||
};
|
||||
typedef struct {
|
||||
bool isConnected;
|
||||
bool isTransitioningFromBroadcastToRtc;
|
||||
} GroupCallNetworkState;
|
||||
|
||||
@interface OngoingGroupCallParticipantDescription : NSObject
|
||||
|
||||
@ -200,7 +200,7 @@ typedef NS_ENUM(int32_t, OngoingGroupCallBroadcastPartStatus) {
|
||||
|
||||
- (void)stop;
|
||||
|
||||
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode;
|
||||
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled;
|
||||
|
||||
- (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;
|
||||
- (void)setJoinResponsePayload:(NSString * _Nonnull)payload participants:(NSArray<OngoingGroupCallParticipantDescription *> * _Nonnull)participants;
|
||||
|
@ -863,13 +863,16 @@ private:
|
||||
|
||||
__weak GroupCallThreadLocalContext *weakSelf = self;
|
||||
_instance.reset(new tgcalls::GroupInstanceCustomImpl((tgcalls::GroupInstanceDescriptor){
|
||||
.networkStateUpdated = [weakSelf, queue, networkStateUpdated](bool isConnected) {
|
||||
.networkStateUpdated = [weakSelf, queue, networkStateUpdated](tgcalls::GroupNetworkState networkState) {
|
||||
[queue dispatch:^{
|
||||
__strong GroupCallThreadLocalContext *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
networkStateUpdated(isConnected ? GroupCallNetworkStateConnected : GroupCallNetworkStateConnecting);
|
||||
GroupCallNetworkState mappedState;
|
||||
mappedState.isConnected = networkState.isConnected;
|
||||
mappedState.isTransitioningFromBroadcastToRtc = networkState.isTransitioningFromBroadcastToRtc;
|
||||
networkStateUpdated(mappedState);
|
||||
}];
|
||||
},
|
||||
.audioLevelsUpdated = [audioLevelsUpdated](tgcalls::GroupLevelsUpdate const &levels) {
|
||||
@ -1041,7 +1044,7 @@ static void processJoinPayload(tgcalls::GroupJoinPayload &payload, void (^ _Nonn
|
||||
completion(string, payload.ssrc);
|
||||
}
|
||||
|
||||
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode {
|
||||
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled {
|
||||
if (_instance) {
|
||||
tgcalls::GroupConnectionMode mappedConnectionMode;
|
||||
switch (connectionMode) {
|
||||
@ -1062,7 +1065,7 @@ static void processJoinPayload(tgcalls::GroupJoinPayload &payload, void (^ _Nonn
|
||||
break;
|
||||
}
|
||||
}
|
||||
_instance->setConnectionMode(mappedConnectionMode);
|
||||
_instance->setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 2c55abafe21cf67128dc3733844e37276fbcabb3
|
||||
Subproject commit d19c74b1474e4aab01d797373f1d62e4da5f87a5
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "7.5.1",
|
||||
"app": "7.6",
|
||||
"bazel": "4.0.0",
|
||||
"xcode": "12.4"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user