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

This commit is contained in:
Ilya Laktyushin 2020-11-29 15:47:54 +04:00
commit df5e9c7cd3
15 changed files with 299 additions and 120 deletions

View File

@ -12,8 +12,8 @@ public enum RequestCallResult {
case alreadyInProgress(PeerId?) case alreadyInProgress(PeerId?)
} }
public enum RequestOrJoinGroupCallResult { public enum JoinGroupCallManagerResult {
case requested case joined
case alreadyInProgress(PeerId?) case alreadyInProgress(PeerId?)
} }
@ -278,5 +278,5 @@ public protocol PresentationCallManager: class {
var currentGroupCallSignal: Signal<PresentationGroupCall?, 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, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool, sourcePanel: ASDisplayNode?) -> RequestOrJoinGroupCallResult func joinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall, endCurrentIfAny: Bool, sourcePanel: ASDisplayNode?) -> JoinGroupCallManagerResult
} }

View File

@ -262,20 +262,19 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
if let callManager = context.sharedContext.callManager { if let callManager = context.sharedContext.callManager {
switch groupCallPanelSource { switch groupCallPanelSource {
case .none: case .none, .all:
break break
default: case let .peer(peerId):
let currentGroupCall: Signal<GroupCallPanelData?, NoError> = callManager.currentGroupCallSignal let currentGroupCall: Signal<GroupCallPanelData?, NoError> = callManager.currentGroupCallSignal
|> distinctUntilChanged(isEqual: { lhs, rhs in |> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs?.internalId == rhs?.internalId return lhs?.internalId == rhs?.internalId
}) })
|> mapToSignal { call -> Signal<GroupCallPanelData?, NoError> in |> mapToSignal { call -> Signal<GroupCallPanelData?, NoError> in
guard let call = call else { guard let call = call, call.peerId == peerId else {
return .single(nil) return .single(nil)
} }
return call.summaryState return call.summaryState
|> filter { $0 != nil } |> filter { $0 != nil }
|> take(1)
|> map { summary -> GroupCallPanelData? in |> map { summary -> GroupCallPanelData? in
guard let summary = summary else { guard let summary = summary else {
return nil return nil
@ -289,6 +288,13 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
groupCall: call groupCall: call
) )
} }
|> take(until: { summary in
if summary != nil {
return SignalTakeAction(passthrough: true, complete: true)
} else {
return SignalTakeAction(passthrough: true, complete: false)
}
})
} }
let availableGroupCall: Signal<GroupCallPanelData?, NoError> let availableGroupCall: Signal<GroupCallPanelData?, NoError>
@ -861,7 +867,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
} }
private func joinGroupCall(peerId: PeerId, info: GroupCallInfo, sourcePanel: GroupCallNavigationAccessoryPanel?) { private func joinGroupCall(peerId: PeerId, info: GroupCallInfo, sourcePanel: GroupCallNavigationAccessoryPanel?) {
let callResult = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: false, sourcePanel: sourcePanel) let callResult = self.context.sharedContext.callManager?.joinGroupCall(context: self.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: false, sourcePanel: sourcePanel)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peerId { if currentPeerId == peerId {
self.context.sharedContext.navigateToCurrentCall(sourcePanel: sourcePanel) self.context.sharedContext.navigateToCurrentCall(sourcePanel: sourcePanel)
@ -880,7 +886,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
if let current = current { 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: { 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?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: true, sourcePanel: sourcePanel) let _ = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: true, sourcePanel: sourcePanel)
} }
})]), in: .window(.root)) })]), in: .window(.root))
} else { } else {

View File

@ -593,7 +593,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
} }
} }
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool, sourcePanel: ASDisplayNode?) -> RequestOrJoinGroupCallResult { public func joinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall, endCurrentIfAny: Bool, sourcePanel: ASDisplayNode?) -> JoinGroupCallManagerResult {
let begin: () -> Void = { [weak self] in let begin: () -> Void = { [weak self] in
let _ = self?.startGroupCall(accountContext: context, peerId: peerId, initialCall: initialCall, sourcePanel: sourcePanel).start() let _ = self?.startGroupCall(accountContext: context, peerId: peerId, initialCall: initialCall, sourcePanel: sourcePanel).start()
} }
@ -612,13 +612,13 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
} else { } else {
begin() begin()
} }
return .requested return .joined
} }
private func startGroupCall( private func startGroupCall(
accountContext: AccountContext, accountContext: AccountContext,
peerId: PeerId, peerId: PeerId,
initialCall: CachedChannelData.ActiveCall?, initialCall: CachedChannelData.ActiveCall,
internalId: CallSessionInternalId = CallSessionInternalId(), internalId: CallSessionInternalId = CallSessionInternalId(),
sourcePanel: ASDisplayNode? sourcePanel: ASDisplayNode?
) -> Signal<Bool, NoError> { ) -> Signal<Bool, NoError> {

View File

@ -142,7 +142,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? private var 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?
@ -374,12 +374,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
var removedSsrc: [UInt32] = [] var removedSsrc: [UInt32] = []
for (callId, update) in updates { for (callId, update) in updates {
if callId == callInfo.id { if callId == callInfo.id {
switch update {
case let .state(update):
for participantUpdate in update.participantUpdates { for participantUpdate in update.participantUpdates {
if participantUpdate.isRemoved { if participantUpdate.isRemoved {
removedSsrc.append(participantUpdate.ssrc) removedSsrc.append(participantUpdate.ssrc)
if participantUpdate.peerId == strongSelf.accountContext.account.peerId {
strongSelf._canBeRemoved.set(.single(true))
}
} else if participantUpdate.peerId == strongSelf.accountContext.account.peerId {
if case let .estabilished(_, _, ssrc, _) = strongSelf.internalState, ssrc != participantUpdate.ssrc {
strongSelf._canBeRemoved.set(.single(true))
}
}
}
case let .call(isTerminated):
if isTerminated {
strongSelf._canBeRemoved.set(.single(true))
} }
} }
strongSelf.participantsContext?.addUpdates(updates: [update])
} }
} }
if !removedSsrc.isEmpty { if !removedSsrc.isEmpty {
@ -553,7 +567,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if case let .estabilished(callInfo, clientParams, _, initialState) = internalState { if case let .estabilished(callInfo, clientParams, _, initialState) = internalState {
self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo))) self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo)))
self.stateValue.canManageCall = initialState.isCreator self.stateValue.canManageCall = initialState.isCreator || initialState.adminIds.contains(self.accountContext.account.peerId)
self.ssrcMapping.removeAll() self.ssrcMapping.removeAll()
var ssrcs: [UInt32] = [] var ssrcs: [UInt32] = []
@ -780,7 +794,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
let account = self.account let account = self.account
let peerId = self.peerId
let currentCall: Signal<GroupCallInfo?, CallError> let currentCall: Signal<GroupCallInfo?, CallError>
if let initialCall = self.initialCall { if let initialCall = self.initialCall {
@ -796,36 +809,27 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
let currentOrRequestedCall = currentCall let currentOrRequestedCall = currentCall
|> mapToSignal { callInfo -> Signal<GroupCallInfo, CallError> in |> mapToSignal { callInfo -> Signal<GroupCallInfo?, CallError> in
if let callInfo = callInfo { if let callInfo = callInfo {
return .single(callInfo) return .single(callInfo)
} else { } else {
return createGroupCall(account: account, peerId: peerId) return .single(nil)
|> mapError { _ -> CallError in
return .generic
} }
} }
}
/*let restartedCall = currentOrRequestedCall
|> mapToSignal { value -> Signal<GroupCallInfo, CallError> in
let stopped: Signal<GroupCallInfo, CallError> = stopGroupCall(account: account, callId: value.id, accessHash: value.accessHash)
|> mapError { _ -> CallError in
return .generic
}
|> map { _ -> GroupCallInfo in
}
return stopped
|> then(currentOrRequestedCall)
}*/
self.requestDisposable.set((currentOrRequestedCall self.requestDisposable.set((currentOrRequestedCall
|> deliverOnMainQueue).start(next: { [weak self] value in |> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if let value = value {
strongSelf.initialCall = CachedChannelData.ActiveCall(id: value.id, accessHash: value.accessHash)
strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl) strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl)
} else {
strongSelf._canBeRemoved.set(.single(true))
}
})) }))
} }
@ -868,7 +872,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if shouldBeSendingTyping != strongSelf.isSendingTyping { if shouldBeSendingTyping != strongSelf.isSendingTyping {
strongSelf.isSendingTyping = shouldBeSendingTyping strongSelf.isSendingTyping = shouldBeSendingTyping
if shouldBeSendingTyping { if shouldBeSendingTyping {
strongSelf.typingDisposable.set(strongSelf.accountContext.account.acquireLocalInputActivity(peerId: PeerActivitySpace(peerId: strongSelf.peerId, category: .voiceChat), activity: .speakingInGroupCall)) strongSelf.typingDisposable.set(strongSelf.accountContext.account.acquireLocalInputActivity(peerId: PeerActivitySpace(peerId: strongSelf.peerId, category: .voiceChat), activity: .speakingInGroupCall(timestamp: 0)))
strongSelf.restartMyAudioLevelTimer() strongSelf.restartMyAudioLevelTimer()
} else { } else {
strongSelf.typingDisposable.set(nil) strongSelf.typingDisposable.set(nil)

View File

@ -223,7 +223,7 @@ public final class VoiceChatController: ViewController {
} else { } else {
microphoneColor = UIColor(rgb: 0x979797) microphoneColor = UIColor(rgb: 0x979797)
} }
icon = .microphone(true, microphoneColor) icon = .microphone(self.muteState != nil, microphoneColor)
case .speaking: case .speaking:
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive) text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
icon = .microphone(false, UIColor(rgb: 0x34c759)) icon = .microphone(false, UIColor(rgb: 0x34c759))
@ -411,7 +411,7 @@ public final class VoiceChatController: ViewController {
} }
} }
if let callState = strongSelf.callState, (callState.canManageCall && !callState.adminIds.contains(strongSelf.context.account.peerId)) {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
@ -444,6 +444,7 @@ public final class VoiceChatController: ViewController {
}))) })))
} }
} }
}
guard !items.isEmpty else { guard !items.isEmpty else {
return return
@ -1139,11 +1140,13 @@ public final class VoiceChatController: ViewController {
var callMembers = callMembers var callMembers = callMembers
callMembers.sort()
for i in 0 ..< callMembers.count { for i in 0 ..< callMembers.count {
if callMembers[i].peer.id == self.context.account.peerId { if callMembers[i].peer.id == self.context.account.peerId {
let member = callMembers[i] let member = callMembers[i]
callMembers.remove(at: i) callMembers.remove(at: i)
callMembers.insert(member, at: i) callMembers.insert(member, at: 0)
break break
} }
} }

View File

@ -111,6 +111,7 @@ enum AccountStateMutationOperation {
case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?)
case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?)
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32) case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
case UpdateGroupCall(call: Api.GroupCall)
} }
struct HoleFromPreviousState { struct HoleFromPreviousState {
@ -281,6 +282,10 @@ struct AccountMutableState {
self.addOperation(.UpdateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version)) self.addOperation(.UpdateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version))
} }
mutating func updateGroupCall(call: Api.GroupCall) {
self.addOperation(.UpdateGroupCall(call: call))
}
mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) { mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) {
self.addOperation(.ReadGroupFeedInbox(groupId, index)) self.addOperation(.ReadGroupFeedInbox(groupId, index))
} }
@ -489,7 +494,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) { mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateMessagesPinned: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned:
break break
case let .AddMessages(messages, location): case let .AddMessages(messages, location):
for message in messages { for message in messages {
@ -607,7 +612,7 @@ struct AccountReplayedFinalState {
let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall] let updatedCalls: [Api.PhoneCall]
let addedCallSignalingData: [(Int64, Data)] let addedCallSignalingData: [(Int64, Data)]
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)]
let updatedPeersNearby: [PeerNearby]? let updatedPeersNearby: [PeerNearby]?
let isContactUpdates: [(PeerId, Bool)] let isContactUpdates: [(PeerId, Bool)]
let delayNotificatonsUntil: Int32? let delayNotificatonsUntil: Int32?
@ -623,7 +628,7 @@ struct AccountFinalStateEvents {
let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall] let updatedCalls: [Api.PhoneCall]
let addedCallSignalingData: [(Int64, Data)] let addedCallSignalingData: [(Int64, Data)]
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)]
let updatedPeersNearby: [PeerNearby]? let updatedPeersNearby: [PeerNearby]?
let isContactUpdates: [(PeerId, Bool)] let isContactUpdates: [(PeerId, Bool)]
let displayAlerts: [(text: String, isDropAuth: Bool)] let displayAlerts: [(text: String, isDropAuth: Bool)]
@ -639,7 +644,7 @@ struct AccountFinalStateEvents {
return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty
} }
init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) { init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) {
self.addedIncomingMessageIds = addedIncomingMessageIds self.addedIncomingMessageIds = addedIncomingMessageIds
self.wasScheduledMessageIds = wasScheduledMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds
self.deletedMessageIds = deletedMessageIds self.deletedMessageIds = deletedMessageIds

View File

@ -1221,7 +1221,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.readSecretOutbox(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), timestamp: maxDate, actionTimestamp: date) updatedState.readSecretOutbox(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), timestamp: maxDate, actionTimestamp: date)
case let .updateUserTyping(userId, type): case let .updateUserTyping(userId, type):
if let date = updatesDate, date + 60 > serverTime { if let date = updatesDate, date + 60 > serverTime {
let activity = PeerInputActivity(apiType: type) let activity = PeerInputActivity(apiType: type, timestamp: date)
var category: PeerActivitySpace.Category = .global var category: PeerActivitySpace.Category = .global
if case .speakingInGroupCall = activity { if case .speakingInGroupCall = activity {
category = .voiceChat category = .voiceChat
@ -1231,7 +1231,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
} }
case let .updateChatUserTyping(chatId, userId, type): case let .updateChatUserTyping(chatId, userId, type):
if let date = updatesDate, date + 60 > serverTime { if let date = updatesDate, date + 60 > serverTime {
let activity = PeerInputActivity(apiType: type) let activity = PeerInputActivity(apiType: type, timestamp: date)
var category: PeerActivitySpace.Category = .global var category: PeerActivitySpace.Category = .global
if case .speakingInGroupCall = activity { if case .speakingInGroupCall = activity {
category = .voiceChat category = .voiceChat
@ -1244,7 +1244,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
let threadId = topMsgId.flatMap { makeMessageThreadId(MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: $0)) } let threadId = topMsgId.flatMap { makeMessageThreadId(MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: $0)) }
let activity = PeerInputActivity(apiType: type) let activity = PeerInputActivity(apiType: type, timestamp: date)
var category: PeerActivitySpace.Category = .global var category: PeerActivitySpace.Category = .global
if case .speakingInGroupCall = activity { if case .speakingInGroupCall = activity {
category = .voiceChat category = .voiceChat
@ -1332,6 +1332,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
updatedState.updateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version) updatedState.updateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version)
} }
case let .updateGroupCall(call):
updatedState.updateGroupCall(call: call)
case let .updateLangPackTooLong(langCode): case let .updateLangPackTooLong(langCode):
updatedState.updateLangPack(langCode: langCode, difference: nil) updatedState.updateLangPack(langCode: langCode, difference: nil)
case let .updateLangPack(difference): case let .updateLangPack(difference):
@ -2135,7 +2137,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState? var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations { for operation in operations {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
} }
@ -2222,7 +2224,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:]
var updatedCalls: [Api.PhoneCall] = [] var updatedCalls: [Api.PhoneCall] = []
var addedCallSignalingData: [(Int64, Data)] = [] var addedCallSignalingData: [(Int64, Data)] = []
var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] = [] var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = []
var updatedPeersNearby: [PeerNearby]? var updatedPeersNearby: [PeerNearby]?
var isContactUpdates: [(PeerId, Bool)] = [] var isContactUpdates: [(PeerId, Bool)] = []
var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = []
@ -2955,8 +2957,18 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
case let .UpdateGroupCallParticipants(callId, _, participants, version): case let .UpdateGroupCallParticipants(callId, _, participants, version):
updatedGroupCallParticipants.append(( updatedGroupCallParticipants.append((
callId, callId,
GroupCallParticipantsContext.StateUpdate(participants: participants, version: version) .state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version))
)) ))
case let .UpdateGroupCall(call):
switch call {
case .groupCall:
break
case let .groupCallDiscarded(callId, _, _):
updatedGroupCallParticipants.append((
callId,
.call(isTerminated: true)
))
}
case let .UpdateLangPack(langCode, difference): case let .UpdateLangPack(langCode, difference):
if let difference = difference { if let difference = difference {
if langPackDifferences[langCode] == nil { if langPackDifferences[langCode] == nil {

View File

@ -148,8 +148,8 @@ public final class AccountStateManager {
return self.threadReadStateUpdatesPipe.signal() return self.threadReadStateUpdatesPipe.signal()
} }
private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, GroupCallParticipantsContext.StateUpdate)]>() private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, GroupCallParticipantsContext.Update)]>()
public var groupCallParticipantUpdates: Signal<[(Int64, GroupCallParticipantsContext.StateUpdate)], NoError> { public var groupCallParticipantUpdates: Signal<[(Int64, GroupCallParticipantsContext.Update)], NoError> {
return self.groupCallParticipantUpdatesPipe.signal() return self.groupCallParticipantUpdatesPipe.signal()
} }

View File

@ -467,7 +467,7 @@ private func binaryInsertionIndex(_ inputArr: [GroupCallParticipantsContext.Part
} }
public final class GroupCallParticipantsContext { public final class GroupCallParticipantsContext {
public struct Participant: Equatable { public struct Participant: Equatable, Comparable {
public struct MuteState: Equatable { public struct MuteState: Equatable {
public var canUnmute: Bool public var canUnmute: Bool
@ -500,6 +500,24 @@ public final class GroupCallParticipantsContext {
} }
return true return true
} }
public static func <(lhs: Participant, rhs: Participant) -> Bool {
if let lhsActivityTimestamp = lhs.activityTimestamp, let rhsActivityTimestamp = rhs.activityTimestamp {
if lhsActivityTimestamp != rhsActivityTimestamp {
return lhsActivityTimestamp > rhsActivityTimestamp
}
} else if lhs.activityTimestamp != nil {
return true
} else if rhs.activityTimestamp != nil {
return false
}
if lhs.joinTimestamp != rhs.joinTimestamp {
return lhs.joinTimestamp > rhs.joinTimestamp
}
return lhs.peer.id < rhs.peer.id
}
} }
public struct State: Equatable { public struct State: Equatable {
@ -542,6 +560,7 @@ public final class GroupCallParticipantsContext {
var overlayState: OverlayState var overlayState: OverlayState
} }
public enum Update {
public struct StateUpdate { public struct StateUpdate {
public struct ParticipantUpdate { public struct ParticipantUpdate {
public var peerId: PeerId public var peerId: PeerId
@ -558,6 +577,10 @@ public final class GroupCallParticipantsContext {
public var removePendingMuteStates: Set<PeerId> public var removePendingMuteStates: Set<PeerId>
} }
case state(update: StateUpdate)
case call(isTerminated: Bool)
}
private let account: Account private let account: Account
private let id: Int64 private let id: Int64
private let accessHash: Int64 private let accessHash: Int64
@ -597,7 +620,7 @@ public final class GroupCallParticipantsContext {
return self.numberOfActiveSpeakersPromise.get() return self.numberOfActiveSpeakersPromise.get()
} }
private var updateQueue: [StateUpdate] = [] private var updateQueue: [Update.StateUpdate] = []
private var isProcessingUpdate: Bool = false private var isProcessingUpdate: Bool = false
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
@ -616,7 +639,7 @@ public final class GroupCallParticipantsContext {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
var filteredUpdates: [StateUpdate] = [] var filteredUpdates: [Update] = []
for (callId, update) in updates { for (callId, update) in updates {
if callId == id { if callId == id {
filteredUpdates.append(update) filteredUpdates.append(update)
@ -635,6 +658,53 @@ public final class GroupCallParticipantsContext {
} }
strongSelf.numberOfActiveSpeakersValue = activities.count strongSelf.numberOfActiveSpeakersValue = activities.count
var updatedParticipants = strongSelf.stateValue.state.participants
var indexMap: [PeerId: Int] = [:]
for i in 0 ..< updatedParticipants.count {
indexMap[updatedParticipants[i].peer.id] = i
}
var updated = false
for (activityPeerId, activity) in activities {
if case let .speakingInGroupCall(timestamp) = activity {
if let index = indexMap[activityPeerId] {
if let activityTimestamp = updatedParticipants[index].activityTimestamp {
if activityTimestamp < timestamp {
updatedParticipants[index].activityTimestamp = timestamp
updated = true
}
} else {
updatedParticipants[index].activityTimestamp = timestamp
updated = true
}
}
}
}
if updated {
updatedParticipants.sort()
for i in 0 ..< updatedParticipants.count {
if updatedParticipants[i].peer.id == strongSelf.account.peerId {
let member = updatedParticipants[i]
updatedParticipants.remove(at: i)
updatedParticipants.insert(member, at: 0)
break
}
}
strongSelf.stateValue = InternalState(
state: State(
participants: updatedParticipants,
nextParticipantsFetchOffset: strongSelf.stateValue.state.nextParticipantsFetchOffset,
adminIds: strongSelf.stateValue.state.adminIds,
isCreator: strongSelf.stateValue.state.isCreator,
totalCount: strongSelf.stateValue.state.totalCount,
version: strongSelf.stateValue.state.version
),
overlayState: strongSelf.stateValue.overlayState
)
}
}) })
} }
@ -644,10 +714,19 @@ public final class GroupCallParticipantsContext {
self.activitiesDisposable?.dispose() self.activitiesDisposable?.dispose()
} }
public func addUpdates(updates: [StateUpdate]) { public func addUpdates(updates: [Update]) {
self.updateQueue.append(contentsOf: updates) var stateUpdates: [Update.StateUpdate] = []
for update in updates {
if case let .state(update) = update {
stateUpdates.append(update)
}
}
if !stateUpdates.isEmpty {
self.updateQueue.append(contentsOf: stateUpdates)
self.beginProcessingUpdatesIfNeeded() self.beginProcessingUpdatesIfNeeded()
} }
}
private func beginProcessingUpdatesIfNeeded() { private func beginProcessingUpdatesIfNeeded() {
if self.isProcessingUpdate { if self.isProcessingUpdate {
@ -667,7 +746,7 @@ public final class GroupCallParticipantsContext {
self.beginProcessingUpdatesIfNeeded() self.beginProcessingUpdatesIfNeeded()
} }
private func processUpdate(update: StateUpdate) { private func processUpdate(update: Update.StateUpdate) {
if update.version < self.stateValue.state.version { if update.version < self.stateValue.state.version {
for peerId in update.removePendingMuteStates { for peerId in update.removePendingMuteStates {
self.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId) self.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId)
@ -702,7 +781,7 @@ public final class GroupCallParticipantsContext {
return return
} }
var updatedParticipants = Array(strongSelf.stateValue.state.participants.reversed()) var updatedParticipants = strongSelf.stateValue.state.participants
var updatedTotalCount = strongSelf.stateValue.state.totalCount var updatedTotalCount = strongSelf.stateValue.state.totalCount
for participantUpdate in update.participantUpdates { for participantUpdate in update.participantUpdates {
@ -718,20 +797,29 @@ public final class GroupCallParticipantsContext {
assertionFailure() assertionFailure()
continue continue
} }
var previousActivityTimestamp: Int32?
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) { if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
previousActivityTimestamp = updatedParticipants[index].activityTimestamp
updatedParticipants.remove(at: index) updatedParticipants.remove(at: index)
} else { } else {
updatedTotalCount += 1 updatedTotalCount += 1
} }
var activityTimestamp: Int32?
if let previousActivityTimestamp = previousActivityTimestamp, let updatedActivityTimestamp = participantUpdate.activityTimestamp {
activityTimestamp = max(updatedActivityTimestamp, previousActivityTimestamp)
} else {
activityTimestamp = participantUpdate.activityTimestamp ?? previousActivityTimestamp
}
let participant = Participant( let participant = Participant(
peer: peer, peer: peer,
ssrc: participantUpdate.ssrc, ssrc: participantUpdate.ssrc,
joinTimestamp: participantUpdate.joinTimestamp, joinTimestamp: participantUpdate.joinTimestamp,
activityTimestamp: activityTimestamp,
muteState: participantUpdate.muteState muteState: participantUpdate.muteState
) )
let index = binaryInsertionIndex(updatedParticipants, searchItem: participant.joinTimestamp) updatedParticipants.append(participant)
updatedParticipants.insert(participant, at: index)
} }
} }
@ -744,9 +832,19 @@ public final class GroupCallParticipantsContext {
let adminIds = strongSelf.stateValue.state.adminIds let adminIds = strongSelf.stateValue.state.adminIds
let isCreator = strongSelf.stateValue.state.isCreator let isCreator = strongSelf.stateValue.state.isCreator
updatedParticipants.sort()
for i in 0 ..< updatedParticipants.count {
if updatedParticipants[i].peer.id == strongSelf.account.peerId {
let member = updatedParticipants[i]
updatedParticipants.remove(at: i)
updatedParticipants.insert(member, at: 0)
break
}
}
strongSelf.stateValue = InternalState( strongSelf.stateValue = InternalState(
state: State( state: State(
participants: Array(updatedParticipants.reversed()), participants: updatedParticipants,
nextParticipantsFetchOffset: nextParticipantsFetchOffset, nextParticipantsFetchOffset: nextParticipantsFetchOffset,
adminIds: adminIds, adminIds: adminIds,
isCreator: isCreator, isCreator: isCreator,
@ -782,6 +880,14 @@ public final class GroupCallParticipantsContext {
self.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId) self.stateValue.overlayState.pendingMuteStateChanges.removeValue(forKey: peerId)
} }
for participant in self.stateValue.state.participants {
if participant.peer.id == peerId {
if participant.muteState == muteState {
return
}
}
}
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.stateValue.overlayState.pendingMuteStateChanges[peerId] = OverlayState.MuteStateChange( self.stateValue.overlayState.pendingMuteStateChanges[peerId] = OverlayState.MuteStateChange(
state: muteState, state: muteState,
@ -800,7 +906,7 @@ public final class GroupCallParticipantsContext {
return .single(nil) return .single(nil)
} }
var flags: Int32 = 0 var flags: Int32 = 0
if let muteState = muteState, !muteState.canUnmute { if let muteState = muteState, (!muteState.canUnmute || peerId == account.peerId) {
flags |= 1 << 0 flags |= 1 << 0
} }
@ -818,7 +924,7 @@ public final class GroupCallParticipantsContext {
} }
if let updates = updates { if let updates = updates {
var stateUpdates: [GroupCallParticipantsContext.StateUpdate] = [] var stateUpdates: [GroupCallParticipantsContext.Update] = []
loop: for update in updates.allUpdates { loop: for update in updates.allUpdates {
switch update { switch update {
@ -829,7 +935,7 @@ public final class GroupCallParticipantsContext {
continue loop continue loop
} }
} }
stateUpdates.append(GroupCallParticipantsContext.StateUpdate(participants: participants, version: version, removePendingMuteStates: [peerId])) stateUpdates.append(.state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version, removePendingMuteStates: [peerId])))
default: default:
break break
} }
@ -845,9 +951,9 @@ public final class GroupCallParticipantsContext {
} }
} }
extension GroupCallParticipantsContext.StateUpdate { extension GroupCallParticipantsContext.Update.StateUpdate {
init(participants: [Api.GroupCallParticipant], version: Int32, removePendingMuteStates: Set<PeerId> = Set()) { init(participants: [Api.GroupCallParticipant], version: Int32, removePendingMuteStates: Set<PeerId> = Set()) {
var participantUpdates: [GroupCallParticipantsContext.StateUpdate.ParticipantUpdate] = [] var participantUpdates: [GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate] = []
for participant in participants { for participant in participants {
switch participant { switch participant {
case let .groupCallParticipant(flags, userId, date, activeDate, source): case let .groupCallParticipant(flags, userId, date, activeDate, source):
@ -859,7 +965,7 @@ extension GroupCallParticipantsContext.StateUpdate {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute) muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
} }
let isRemoved = (flags & (1 << 1)) != 0 let isRemoved = (flags & (1 << 1)) != 0
participantUpdates.append(GroupCallParticipantsContext.StateUpdate.ParticipantUpdate( participantUpdates.append(GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(
peerId: peerId, peerId: peerId,
ssrc: ssrc, ssrc: ssrc,
joinTimestamp: date, joinTimestamp: date,

View File

@ -10,7 +10,7 @@ public enum PeerInputActivity: Comparable {
case playingGame case playingGame
case recordingInstantVideo case recordingInstantVideo
case uploadingInstantVideo(progress: Int32) case uploadingInstantVideo(progress: Int32)
case speakingInGroupCall case speakingInGroupCall(timestamp: Int32)
public var key: Int32 { public var key: Int32 {
switch self { switch self {
@ -41,7 +41,7 @@ public enum PeerInputActivity: Comparable {
} }
extension PeerInputActivity { extension PeerInputActivity {
init?(apiType: Api.SendMessageAction) { init?(apiType: Api.SendMessageAction, timestamp: Int32) {
switch apiType { switch apiType {
case .sendMessageCancelAction, .sendMessageChooseContactAction, .sendMessageGeoLocationAction, .sendMessageRecordVideoAction: case .sendMessageCancelAction, .sendMessageChooseContactAction, .sendMessageGeoLocationAction, .sendMessageRecordVideoAction:
return nil return nil
@ -62,7 +62,7 @@ extension PeerInputActivity {
case let .sendMessageUploadRoundAction(progress): case let .sendMessageUploadRoundAction(progress):
self = .uploadingInstantVideo(progress: progress) self = .uploadingInstantVideo(progress: progress)
case .speakingInGroupCallAction: case .speakingInGroupCallAction:
self = .speakingInGroupCall self = .speakingInGroupCall(timestamp: timestamp)
} }
} }
} }

View File

@ -408,9 +408,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
//TODO:localize //TODO:localize
let titleString: String let titleString: String
if let duration = duration { if let duration = duration {
titleString = "Group Call \(duration)s" titleString = "Voice chat ended (\(duration)s)"
} else { } else {
titleString = "Group Call" titleString = "Voice chat started"
} }
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case let .customText(text, entities): case let .customText(text, entities):

View File

@ -5986,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, initialCall: activeCall, endCurrentIfAny: false, sourcePanel: nil) let callResult = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: false, sourcePanel: nil)
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(sourcePanel: nil) strongSelf.context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
@ -5999,7 +5999,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
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?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: true, sourcePanel: nil) let _ = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: true, sourcePanel: nil)
} }
})]), in: .window(.root)) })]), in: .window(.root))
} else { } else {

View File

@ -949,7 +949,6 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
displayLeave = false displayLeave = false
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) { if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
result.append(.addMember) result.append(.addMember)
result.append(.call)
} }
} }
switch channel.participationStatus { switch channel.participationStatus {

View File

@ -2966,6 +2966,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
} }
} else if let channel = peer as? TelegramChannel { } else if let channel = peer as? TelegramChannel {
if case .group = channel.info, !channel.flags.contains(.hasVoiceChat) {
if channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls) {
//TODO:localize
items.append(ActionSheetButtonItem(title: "Create Voice Chat", color: .accent, action: { [weak self] in
dismissAction()
self?.requestCall(isVideo: false)
}))
}
}
if let cachedData = self.data?.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) { if let cachedData = self.data?.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) {
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_Stats, color: .accent, action: { [weak self] in items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_Stats, color: .accent, action: { [weak self] in
dismissAction() dismissAction()
@ -3169,7 +3179,41 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let cachedChannelData = self.data?.cachedData as? CachedChannelData else { guard let cachedChannelData = self.data?.cachedData as? CachedChannelData else {
return return
} }
let _ = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id, initialCall: cachedChannelData.activeCall, endCurrentIfAny: false, sourcePanel: nil)
if let activeCall = cachedChannelData.activeCall {
let _ = self.context.sharedContext.callManager?.joinGroupCall(context: self.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: false, sourcePanel: nil)
} else {
var dismissStatus: (() -> Void)?
let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: {
dismissStatus?()
}))
dismissStatus = { [weak self, weak statusController] in
self?.activeActionDisposable.set(nil)
statusController?.dismiss()
}
self.controller?.present(statusController, in: .window(.root))
self.activeActionDisposable.set((createGroupCall(account: self.context.account, peerId: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] info in
guard let strongSelf = self else {
return
}
let _ = strongSelf.context.sharedContext.callManager?.joinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: false, sourcePanel: nil)
}, error: { [weak self] _ in
dismissStatus?()
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}, completed: { [weak self] in
dismissStatus?()
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}))
}
return return
} }

@ -1 +1 @@
Subproject commit 353184e3e5c5e560a59cb836ca8de496c0cc95bb Subproject commit 7f2022780754c0b3a23f8ce8a940042101120077