This commit is contained in:
Ali 2020-11-29 04:13:27 +04:00
parent 7d45ba44b9
commit 2889bae353
15 changed files with 299 additions and 120 deletions

View File

@ -12,8 +12,8 @@ public enum RequestCallResult {
case alreadyInProgress(PeerId?)
}
public enum RequestOrJoinGroupCallResult {
case requested
public enum JoinGroupCallManagerResult {
case joined
case alreadyInProgress(PeerId?)
}
@ -278,5 +278,5 @@ public protocol PresentationCallManager: class {
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get }
func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, 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 {
switch groupCallPanelSource {
case .none:
case .none, .all:
break
default:
case let .peer(peerId):
let currentGroupCall: Signal<GroupCallPanelData?, NoError> = callManager.currentGroupCallSignal
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs?.internalId == rhs?.internalId
})
|> mapToSignal { call -> Signal<GroupCallPanelData?, NoError> in
guard let call = call else {
guard let call = call, call.peerId == peerId else {
return .single(nil)
}
return call.summaryState
|> filter { $0 != nil }
|> take(1)
|> map { summary -> GroupCallPanelData? in
guard let summary = summary else {
return nil
@ -289,6 +288,13 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
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>
@ -861,7 +867,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
}
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 currentPeerId == peerId {
self.context.sharedContext.navigateToCurrentCall(sourcePanel: sourcePanel)
@ -880,7 +886,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
if let current = current {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: true, 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))
} 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 _ = self?.startGroupCall(accountContext: context, peerId: peerId, initialCall: initialCall, sourcePanel: sourcePanel).start()
}
@ -612,13 +612,13 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
} else {
begin()
}
return .requested
return .joined
}
private func startGroupCall(
accountContext: AccountContext,
peerId: PeerId,
initialCall: CachedChannelData.ActiveCall?,
initialCall: CachedChannelData.ActiveCall,
internalId: CallSessionInternalId = CallSessionInternalId(),
sourcePanel: ASDisplayNode?
) -> 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 initialCall: CachedChannelData.ActiveCall?
private var initialCall: CachedChannelData.ActiveCall?
public let internalId: CallSessionInternalId
public let peerId: PeerId
public let peer: Peer?
@ -374,12 +374,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
var removedSsrc: [UInt32] = []
for (callId, update) in updates {
if callId == callInfo.id {
for participantUpdate in update.participantUpdates {
if participantUpdate.isRemoved {
removedSsrc.append(participantUpdate.ssrc)
switch update {
case let .state(update):
for participantUpdate in update.participantUpdates {
if participantUpdate.isRemoved {
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 {
@ -553,7 +567,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if case let .estabilished(callInfo, clientParams, _, initialState) = internalState {
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()
var ssrcs: [UInt32] = []
@ -780,7 +794,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
let account = self.account
let peerId = self.peerId
let currentCall: Signal<GroupCallInfo?, CallError>
if let initialCall = self.initialCall {
@ -796,36 +809,27 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
let currentOrRequestedCall = currentCall
|> mapToSignal { callInfo -> Signal<GroupCallInfo, CallError> in
|> mapToSignal { callInfo -> Signal<GroupCallInfo?, CallError> in
if let callInfo = callInfo {
return .single(callInfo)
} else {
return createGroupCall(account: account, peerId: peerId)
|> mapError { _ -> CallError in
return .generic
}
return .single(nil)
}
}
/*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
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl)
if let value = value {
strongSelf.initialCall = CachedChannelData.ActiveCall(id: value.id, accessHash: value.accessHash)
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 {
strongSelf.isSendingTyping = 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()
} else {
strongSelf.typingDisposable.set(nil)

View File

@ -223,7 +223,7 @@ public final class VoiceChatController: ViewController {
} else {
microphoneColor = UIColor(rgb: 0x979797)
}
icon = .microphone(true, microphoneColor)
icon = .microphone(self.muteState != nil, microphoneColor)
case .speaking:
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
icon = .microphone(false, UIColor(rgb: 0x34c759))
@ -411,37 +411,38 @@ public final class VoiceChatController: ViewController {
}
}
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)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
guard let strongSelf = self else {
return
}
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme))
var items: [ActionSheetItem] = []
items.append(DeleteChatPeerActionSheetItem(context: strongSelf.context, peer: peer, chatPeer: peer, action: .removeFromGroup, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder))
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
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
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
}))
guard let strongSelf = self else {
return
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme))
var items: [ActionSheetItem] = []
items.append(DeleteChatPeerActionSheetItem(context: strongSelf.context, peer: peer, chatPeer: peer, action: .removeFromGroup, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder))
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
}))
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
])
strongSelf.controller?.present(actionSheet, in: .window(.root))
})))
strongSelf.controller?.present(actionSheet, in: .window(.root))
})))
}
}
}
@ -1139,11 +1140,13 @@ public final class VoiceChatController: ViewController {
var callMembers = callMembers
callMembers.sort()
for i in 0 ..< callMembers.count {
if callMembers[i].peer.id == self.context.account.peerId {
let member = callMembers[i]
callMembers.remove(at: i)
callMembers.insert(member, at: i)
callMembers.insert(member, at: 0)
break
}
}

View File

@ -111,6 +111,7 @@ enum AccountStateMutationOperation {
case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?)
case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?)
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
case UpdateGroupCall(call: Api.GroupCall)
}
struct HoleFromPreviousState {
@ -281,6 +282,10 @@ struct AccountMutableState {
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) {
self.addOperation(.ReadGroupFeedInbox(groupId, index))
}
@ -489,7 +494,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) {
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
case let .AddMessages(messages, location):
for message in messages {
@ -607,7 +612,7 @@ struct AccountReplayedFinalState {
let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall]
let addedCallSignalingData: [(Int64, Data)]
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)]
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)]
let updatedPeersNearby: [PeerNearby]?
let isContactUpdates: [(PeerId, Bool)]
let delayNotificatonsUntil: Int32?
@ -623,7 +628,7 @@ struct AccountFinalStateEvents {
let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall]
let addedCallSignalingData: [(Int64, Data)]
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)]
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)]
let updatedPeersNearby: [PeerNearby]?
let isContactUpdates: [(PeerId, 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
}
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.wasScheduledMessageIds = wasScheduledMessageIds
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)
case let .updateUserTyping(userId, type):
if let date = updatesDate, date + 60 > serverTime {
let activity = PeerInputActivity(apiType: type)
let activity = PeerInputActivity(apiType: type, timestamp: date)
var category: PeerActivitySpace.Category = .global
if case .speakingInGroupCall = activity {
category = .voiceChat
@ -1231,7 +1231,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
}
case let .updateChatUserTyping(chatId, userId, type):
if let date = updatesDate, date + 60 > serverTime {
let activity = PeerInputActivity(apiType: type)
let activity = PeerInputActivity(apiType: type, timestamp: date)
var category: PeerActivitySpace.Category = .global
if case .speakingInGroupCall = activity {
category = .voiceChat
@ -1244,7 +1244,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
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
if case .speakingInGroupCall = activity {
category = .voiceChat
@ -1332,6 +1332,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
case let .inputGroupCall(id, accessHash):
updatedState.updateGroupCallParticipants(id: id, accessHash: accessHash, participants: participants, version: version)
}
case let .updateGroupCall(call):
updatedState.updateGroupCall(call: call)
case let .updateLangPackTooLong(langCode):
updatedState.updateLangPack(langCode: langCode, difference: nil)
case let .updateLangPack(difference):
@ -2135,7 +2137,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
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 {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -2222,7 +2224,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:]
var updatedCalls: [Api.PhoneCall] = []
var addedCallSignalingData: [(Int64, Data)] = []
var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.StateUpdate)] = []
var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = []
var updatedPeersNearby: [PeerNearby]?
var isContactUpdates: [(PeerId, Bool)] = []
var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = []
@ -2955,8 +2957,18 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
case let .UpdateGroupCallParticipants(callId, _, participants, version):
updatedGroupCallParticipants.append((
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):
if let difference = difference {
if langPackDifferences[langCode] == nil {

View File

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

View File

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

View File

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

View File

@ -408,9 +408,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
//TODO:localize
let titleString: String
if let duration = duration {
titleString = "Group Call \(duration)s"
titleString = "Voice chat ended (\(duration)s)"
} else {
titleString = "Group Call"
titleString = "Voice chat started"
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
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 {
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 currentPeerId == peer.id {
strongSelf.context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
@ -5999,7 +5999,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self, let current = current {
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
if let strongSelf = self {
let _ = strongSelf.context.sharedContext.callManager?.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))
} else {

View File

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

View File

@ -2966,6 +2966,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
} 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) {
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_Stats, color: .accent, action: { [weak self] in
dismissAction()
@ -3169,7 +3179,41 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let cachedChannelData = self.data?.cachedData as? CachedChannelData else {
return
}
let _ = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id, initialCall: cachedChannelData.activeCall, endCurrentIfAny: false, 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
}

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