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

This commit is contained in:
Ilya Laktyushin 2020-12-08 21:07:03 +04:00
commit b1ca1470ed
27 changed files with 4889 additions and 10046 deletions

View File

@ -23,6 +23,7 @@ internal:
environment:
name: internal
artifacts:
when: always
paths:
- build/artifacts
expire_in: 1 week

View File

@ -185,8 +185,8 @@
"PUSH_CHAT_MESSAGES_1" = "%2$@|%1$@ sent a message";
"PUSH_CHAT_MESSAGES_any" = "%2$@|%1$@ sent %3$d messages";
"PUSH_CHAT_ALBUM" = "%2$@|%1$@ sent an album";
"PUSH_CHAT_MESSAGE_DOCS_1" = "%2$@|%1$@ sent a file";
"PUSH_CHAT_MESSAGE_DOCS_any" = "%2$@|%1$@ sent %3$d files";
"PUSH_CHAT_MESSAGE_DOCS_FIX1_1" = "%2$@|%1$@ sent a file";
"PUSH_CHAT_MESSAGE_DOCS_FIX1_any" = "%2$@|%1$@ sent %3$d files";
"PUSH_PINNED_TEXT" = "%1$@|pinned \"%2$@\" ";
"PUSH_PINNED_NOTEXT" = "%1$@|pinned a message";
@ -6011,3 +6011,10 @@ Sorry for the inconvenience.";
"VoiceOver.Chat.MessagesSelected_3_10" = "%@ messages selected";
"VoiceOver.Chat.MessagesSelected_many" = "%@ messages selected";
"VoiceOver.Chat.MessagesSelected_any" = "%@ messages selected";
"Channel.AdminLog.StartedVoiceChat" = "%1$@ started voice chat";
"Channel.AdminLog.EndedVoiceChat" = "%1$@ ended voice chat";
"Channel.AdminLog.MutedParticipant" = "%1$@ muted %2$@";
"Channel.AdminLog.UnmutedMutedParticipant" = "%1$@ unmuted %2$@";
"Channel.AdminLog.AllowedNewMembersToSpeak" = "%1$@ allowed new members to speak";
"Channel.AdminLog.MutedNewMembers" = "%1$@ muted new members";

File diff suppressed because it is too large Load Diff

View File

@ -154,10 +154,10 @@ private final class ContentNode: ASDisplayNode {
playbackMaskLayer.frame = maskRect
playbackMaskLayer.fillRule = .evenOdd
let maskPath = UIBezierPath()
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22))
maskPath.append(UIBezierPath(roundedRect: self.unclippedNode.bounds.offsetBy(dx: 8, dy: 8), cornerRadius: maskRect.width / 2.0))
maskPath.append(UIBezierPath(rect: maskRect))
playbackMaskLayer.path = maskPath.cgPath
audioLevelView.layer.mask = playbackMaskLayer
//audioLevelView.layer.mask = playbackMaskLayer
audioLevelView.setColor(color)
self.audioLevelView = audioLevelView
@ -169,16 +169,20 @@ private final class ContentNode: ASDisplayNode {
audioLevelView.updateLevel(CGFloat(value) * 2.0)
let avatarScale: CGFloat
let audioLevelScale: CGFloat
if value > 0.0 {
audioLevelView.startAnimating()
avatarScale = 1.03 + level * 0.07
audioLevelScale = 1.0
} else {
audioLevelView.stopAnimating(duration: 0.5)
avatarScale = 1.0
audioLevelScale = 0.01
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
transition.updateSublayerTransformScale(node: self, scale: CGPoint(x: avatarScale, y: avatarScale), beginWithCurrentState: true)
transition.updateSublayerTransformScale(layer: audioLevelView.layer, scale: CGPoint(x: audioLevelScale, y: audioLevelScale), beginWithCurrentState: true)
}
}
}

View File

@ -220,13 +220,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
let incoming = message.flags.contains(.Incoming)
if let discardReason = discardReason {
switch discardReason {
case .busy, .disconnect:
case .disconnect:
if isVideo {
messageText = strings.Notification_VideoCallCanceled
} else {
messageText = strings.Notification_CallCanceled
}
case .missed:
case .missed, .busy:
if incoming {
if isVideo {
messageText = strings.Notification_VideoCallMissed

View File

@ -843,44 +843,10 @@ public extension ContainedViewLayoutTransition {
completion?(true)
return
}
let t = node.layer.sublayerTransform
let currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23))
if t.m22 < 0.0 {
currentScaleY = -currentScaleY
}
if CGPoint(x: currentScaleX, y: currentScaleY) == scale {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let initialTransform: CATransform3D
if beginWithCurrentState, node.isNodeLoaded {
initialTransform = node.layer.presentation()?.sublayerTransform ?? t
} else {
initialTransform = t
}
node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
node.layer.animate(from: NSValue(caTransform3D: initialTransform), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
self.updateSublayerTransformScale(layer: node.layer, scale: scale, beginWithCurrentState: beginWithCurrentState, completion: completion)
}
func updateSublayerTransformScale(layer: CALayer, scale: CGPoint, completion: ((Bool) -> Void)? = nil) {
func updateSublayerTransformScale(layer: CALayer, scale: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = layer.sublayerTransform
let currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23))
@ -901,8 +867,15 @@ public extension ContainedViewLayoutTransition {
completion(true)
}
case let .animated(duration, curve):
let initialTransform: CATransform3D
if beginWithCurrentState {
initialTransform = layer.presentation()?.sublayerTransform ?? t
} else {
initialTransform = t
}
layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
layer.animate(from: NSValue(caTransform3D: initialTransform), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)

View File

@ -311,6 +311,11 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
private let presentationDataPromise: Promise<PresentationData>
private var _hasDim: Bool = false
override public var hasDim: Bool {
return _hasDim
}
public init(context: AccountContext, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
self.context = context
self.openPeer = openPeer
@ -333,7 +338,17 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.listNode.isHidden = true
self.addSubnode(self.emptyQueryListNode)
if !filters.contains(where: { filter in
if case .excludeBots = filter {
return true
} else {
return false
}
}) {
self.addSubnode(self.emptyQueryListNode)
} else {
self._hasDim = true
}
self.addSubnode(self.listNode)
let statePromise = ValuePromise(ChannelMembersSearchContainerState(), ignoreRepeated: true)
@ -1360,4 +1375,14 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
self.cancel?()
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let result = self.view.hitTest(point, with: event) else {
return nil
}
if result === self.view {
return nil
}
return result
}
}

View File

@ -34,11 +34,13 @@ private final class ChannelMembersSearchInteraction {
private enum ChannelMembersSearchEntryId: Hashable {
case copyInviteLink
case peer(PeerId)
case contact(PeerId)
}
private enum ChannelMembersSearchEntry: Comparable, Identifiable {
case copyInviteLink
case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool)
case contact(Int, Peer, TelegramUserPresence?)
var stableId: ChannelMembersSearchEntryId {
switch self {
@ -46,6 +48,8 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
return .copyInviteLink
case let .peer(_, participant, _, _, _):
return .peer(participant.peer.id)
case let .contact(_, peer, _):
return .contact(peer.id)
}
}
@ -63,6 +67,21 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
} else {
return false
}
case let .contact(lhsIndex, lhsPeer, lhsPresence):
if case let .contact(rhsIndex, rhsPeer, rhsPresence) = rhs {
if lhsIndex != rhsIndex {
return false
}
if !lhsPeer.isEqual(rhsPeer) {
return false
}
if lhsPresence != rhsPresence {
return false
}
return true
} else {
return false
}
}
}
@ -79,6 +98,18 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
return false
} else if case let .peer(rhsIndex, _, _, _, _) = rhs {
return lhsIndex < rhsIndex
} else if case .contact = rhs {
return true
} else {
return false
}
case let .contact(lhsIndex, _, _):
if case .copyInviteLink = rhs {
return false
} else if case .peer = rhs {
return false
} else if case let .contact(rhsIndex, _, _) = rhs {
return lhsIndex < rhsIndex
} else {
return false
}
@ -101,12 +132,27 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
let status: ContactsPeerItemStatus
if let label = label {
status = .custom(string: label, multiline: false)
} else if participant.peer.id != context.account.peerId {
let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0)
status = .presence(presence, presentationData.dateTimeFormat)
} else {
status = .none
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: .members, theme: presentationData.theme, strings: presentationData.strings), action: { _ in
interaction.openPeer(participant.peer, participant)
})
case let .contact(_, peer, presence):
let status: ContactsPeerItemStatus
if peer.id != context.account.peerId, let presence = presence {
status = .presence(presence, presentationData.dateTimeFormat)
} else {
status = .none
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: nil), status: status, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: presentationData.theme, strings: presentationData.strings), action: { _ in
interaction.openPeer(peer, nil)
})
}
}
}
@ -190,6 +236,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
let previousEntries = Atomic<[ChannelMembersSearchEntry]?>(value: nil)
let disposableAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
let additionalDisposable = MetaDisposable()
if peerId.namespace == Namespaces.Peer.CloudGroup {
let disposable = (context.account.postbox.peerView(id: peerId)
@ -325,7 +372,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
})
disposableAndLoadMoreControl = (disposable, nil)
} else {
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { [weak self] state in
let membersState = Promise<ChannelMemberListState>()
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
membersState.set(.single(state))
})
additionalDisposable.set((combineLatest(queue: .mainQueue(),
membersState.get(),
context.account.postbox.contactPeersView(accountPeerId: context.account.peerId, includePresences: true)
).start(next: { [weak self] state, contactsView in
guard let strongSelf = self else {
return
}
@ -428,12 +484,24 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
index += 1
}
if case .inviteToCall = mode {
for peer in contactsView.peers {
entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.peerPresences[peer.id] as? TelegramUserPresence))
index += 1
}
}
let previous = previousEntries.swap(entries)
strongSelf.enqueueTransition(preparedTransition(from: previous, to: entries, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, interaction: interaction))
})
})))
}
self.disposable = disposableAndLoadMoreControl.0
let combinedDisposable = DisposableSet()
combinedDisposable.add(disposableAndLoadMoreControl.0)
combinedDisposable.add(additionalDisposable)
self.disposable = combinedDisposable
self.listControl = disposableAndLoadMoreControl.1
if peerId.namespace == Namespaces.Peer.CloudChannel {

View File

@ -72,13 +72,13 @@ private func stringForCallType(message: Message, strings: PresentationStrings) -
let incoming = message.flags.contains(.Incoming)
if let discardReason = discardReason {
switch discardReason {
case .busy, .disconnect:
case .disconnect:
if isVideo {
string = strings.Notification_VideoCallCanceled
} else {
string = strings.Notification_CallCanceled
}
case .missed:
case .missed, .busy:
if incoming {
if isVideo {
string = strings.Notification_VideoCallMissed

View File

@ -3,9 +3,21 @@ import Display
import AlertUI
import AccountContext
import SwiftSignalKit
import TelegramPresentationData
public func textAlertController(context: AccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController {
return textAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: context.sharedContext.currentPresentationData.with { $0 }), themeSignal: context.sharedContext.presentationData |> map { presentationData in AlertControllerTheme(presentationData: presentationData) }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap)
public func textAlertController(context: AccountContext, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController {
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
if let forceTheme = forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
return textAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: presentationData), themeSignal: context.sharedContext.presentationData |> map {
presentationData in
var presentationData = presentationData
if let forceTheme = forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
return AlertControllerTheme(presentationData: presentationData)
}), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap)
}
public func textAlertController(sharedContext: SharedAccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController {

View File

@ -12,9 +12,22 @@ public enum SearchDisplayControllerMode {
}
public final class SearchDisplayController {
private final class BackgroundNode: ASDisplayNode {
var isTransparent: Bool = false
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = self.view.hitTest(point, with: event)
if self.isTransparent, result === self.view {
return nil
} else {
return result
}
}
}
private let searchBar: SearchBarNode
private let mode: SearchDisplayControllerMode
private let backgroundNode: ASDisplayNode
private let backgroundNode: BackgroundNode
public let contentNode: SearchDisplayControllerContentNode
private var hasSeparator: Bool
@ -26,7 +39,7 @@ public final class SearchDisplayController {
public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, hasSeparator: Bool = false, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) {
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: hasSeparator), strings: presentationData.strings, fieldStyle: .modern, forceSeparator: hasSeparator)
self.backgroundNode = ASDisplayNode()
self.backgroundNode = BackgroundNode()
self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor
self.backgroundNode.allowsGroupOpacity = true
@ -93,8 +106,10 @@ public final class SearchDisplayController {
if self.contentNode.hasDim {
self.backgroundNode.backgroundColor = .clear
self.backgroundNode.isTransparent = true
} else {
self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor
self.backgroundNode.isTransparent = false
}
}
@ -148,8 +163,10 @@ public final class SearchDisplayController {
if self.contentNode.hasDim {
self.backgroundNode.backgroundColor = .clear
self.backgroundNode.isTransparent = true
} else {
self.backgroundNode.alpha = 0.0
self.backgroundNode.isTransparent = false
}
var size = layout.size

View File

@ -74,6 +74,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1516793212] = { return Api.ChatInvite.parse_chatInviteAlready($0) }
dict[-540871282] = { return Api.ChatInvite.parse_chatInvite($0) }
dict[1634294960] = { return Api.ChatInvite.parse_chatInvitePeek($0) }
dict[813821341] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypeSameBotPM($0) }
dict[-2093215828] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypePM($0) }
dict[-681130742] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypeChat($0) }
dict[1589952067] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypeMegagroup($0) }
dict[1664413338] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypeBroadcast($0) }
dict[-532532493] = { return Api.AutoDownloadSettings.parse_autoDownloadSettings($0) }
dict[1678812626] = { return Api.StickerSetCovered.parse_stickerSetCovered($0) }
dict[872932635] = { return Api.StickerSetCovered.parse_stickerSetMultiCovered($0) }
@ -210,7 +215,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) }
dict[1135492588] = { return Api.Update.parse_updateStickerSets($0) }
dict[-1821035490] = { return Api.Update.parse_updateSavedGifs($0) }
dict[1417832080] = { return Api.Update.parse_updateBotInlineQuery($0) }
dict[239663460] = { return Api.Update.parse_updateBotInlineSend($0) }
dict[457133559] = { return Api.Update.parse_updateEditChannelMessage($0) }
dict[-415938591] = { return Api.Update.parse_updateBotCallbackQuery($0) }
@ -264,6 +268,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-2054649973] = { return Api.Update.parse_updatePinnedChannelMessages($0) }
dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) }
dict[1462009966] = { return Api.Update.parse_updateGroupCall($0) }
dict[1059076315] = { return Api.Update.parse_updateBotInlineQuery($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
@ -432,6 +437,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1569748965] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLinkedChat($0) }
dict[241923758] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeLocation($0) }
dict[1401984889] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleSlowMode($0) }
dict[589338437] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStartGroupCall($0) }
dict[-610299584] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionDiscardGroupCall($0) }
dict[-115071790] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantMute($0) }
dict[-431740480] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantUnmute($0) }
dict[1456906823] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleGroupCallSetting($0) }
dict[-543777747] = { return Api.auth.ExportedAuthorization.parse_exportedAuthorization($0) }
dict[2103482845] = { return Api.SecurePlainData.parse_securePlainPhone($0) }
dict[569137759] = { return Api.SecurePlainData.parse_securePlainEmail($0) }
@ -959,6 +969,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.ChatInvite:
_1.serialize(buffer, boxed)
case let _1 as Api.InlineQueryPeerType:
_1.serialize(buffer, boxed)
case let _1 as Api.AutoDownloadSettings:
_1.serialize(buffer, boxed)
case let _1 as Api.StickerSetCovered:

View File

@ -3978,6 +3978,80 @@ public extension Api {
}
}
}
public enum InlineQueryPeerType: TypeConstructorDescription {
case inlineQueryPeerTypeSameBotPM
case inlineQueryPeerTypePM
case inlineQueryPeerTypeChat
case inlineQueryPeerTypeMegagroup
case inlineQueryPeerTypeBroadcast
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inlineQueryPeerTypeSameBotPM:
if boxed {
buffer.appendInt32(813821341)
}
break
case .inlineQueryPeerTypePM:
if boxed {
buffer.appendInt32(-2093215828)
}
break
case .inlineQueryPeerTypeChat:
if boxed {
buffer.appendInt32(-681130742)
}
break
case .inlineQueryPeerTypeMegagroup:
if boxed {
buffer.appendInt32(1589952067)
}
break
case .inlineQueryPeerTypeBroadcast:
if boxed {
buffer.appendInt32(1664413338)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inlineQueryPeerTypeSameBotPM:
return ("inlineQueryPeerTypeSameBotPM", [])
case .inlineQueryPeerTypePM:
return ("inlineQueryPeerTypePM", [])
case .inlineQueryPeerTypeChat:
return ("inlineQueryPeerTypeChat", [])
case .inlineQueryPeerTypeMegagroup:
return ("inlineQueryPeerTypeMegagroup", [])
case .inlineQueryPeerTypeBroadcast:
return ("inlineQueryPeerTypeBroadcast", [])
}
}
public static func parse_inlineQueryPeerTypeSameBotPM(_ reader: BufferReader) -> InlineQueryPeerType? {
return Api.InlineQueryPeerType.inlineQueryPeerTypeSameBotPM
}
public static func parse_inlineQueryPeerTypePM(_ reader: BufferReader) -> InlineQueryPeerType? {
return Api.InlineQueryPeerType.inlineQueryPeerTypePM
}
public static func parse_inlineQueryPeerTypeChat(_ reader: BufferReader) -> InlineQueryPeerType? {
return Api.InlineQueryPeerType.inlineQueryPeerTypeChat
}
public static func parse_inlineQueryPeerTypeMegagroup(_ reader: BufferReader) -> InlineQueryPeerType? {
return Api.InlineQueryPeerType.inlineQueryPeerTypeMegagroup
}
public static func parse_inlineQueryPeerTypeBroadcast(_ reader: BufferReader) -> InlineQueryPeerType? {
return Api.InlineQueryPeerType.inlineQueryPeerTypeBroadcast
}
}
public enum AutoDownloadSettings: TypeConstructorDescription {
case autoDownloadSettings(flags: Int32, photoSizeMax: Int32, videoSizeMax: Int32, fileSizeMax: Int32, videoUploadMaxbitrate: Int32)
@ -6293,7 +6367,6 @@ public extension Api {
case updateStickerSetsOrder(flags: Int32, order: [Int64])
case updateStickerSets
case updateSavedGifs
case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int32, query: String, geo: Api.GeoPoint?, offset: String)
case updateBotInlineSend(flags: Int32, userId: Int32, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?)
case updateEditChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32)
case updateBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int32, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?)
@ -6347,6 +6420,7 @@ public extension Api {
case updatePinnedChannelMessages(flags: Int32, channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32)
case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32)
case updateGroupCall(channelId: Int32, call: Api.GroupCall)
case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int32, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -6628,17 +6702,6 @@ public extension Api {
buffer.appendInt32(-1821035490)
}
break
case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let offset):
if boxed {
buffer.appendInt32(1417832080)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(queryId, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false)
serializeString(query, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)}
serializeString(offset, buffer: buffer, boxed: false)
break
case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId):
if boxed {
@ -7101,6 +7164,18 @@ public extension Api {
serializeInt32(channelId, buffer: buffer, boxed: false)
call.serialize(buffer, true)
break
case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset):
if boxed {
buffer.appendInt32(1059076315)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(queryId, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false)
serializeString(query, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {geo!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {peerType!.serialize(buffer, true)}
serializeString(offset, buffer: buffer, boxed: false)
break
}
}
@ -7172,8 +7247,6 @@ public extension Api {
return ("updateStickerSets", [])
case .updateSavedGifs:
return ("updateSavedGifs", [])
case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let offset):
return ("updateBotInlineQuery", [("flags", flags), ("queryId", queryId), ("userId", userId), ("query", query), ("geo", geo), ("offset", offset)])
case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId):
return ("updateBotInlineSend", [("flags", flags), ("userId", userId), ("query", query), ("geo", geo), ("id", id), ("msgId", msgId)])
case .updateEditChannelMessage(let message, let pts, let ptsCount):
@ -7280,6 +7353,8 @@ public extension Api {
return ("updateGroupCallParticipants", [("call", call), ("participants", participants), ("version", version)])
case .updateGroupCall(let channelId, let call):
return ("updateGroupCall", [("channelId", channelId), ("call", call)])
case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset):
return ("updateBotInlineQuery", [("flags", flags), ("queryId", queryId), ("userId", userId), ("query", query), ("geo", geo), ("peerType", peerType), ("offset", offset)])
}
}
@ -7839,34 +7914,6 @@ public extension Api {
public static func parse_updateSavedGifs(_ reader: BufferReader) -> Update? {
return Api.Update.updateSavedGifs
}
public static func parse_updateBotInlineQuery(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: String?
_4 = parseString(reader)
var _5: Api.GeoPoint?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.GeoPoint
} }
var _6: String?
_6 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.Update.updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, offset: _6!)
}
else {
return nil
}
}
public static func parse_updateBotInlineSend(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
@ -8789,6 +8836,39 @@ public extension Api {
return nil
}
}
public static func parse_updateBotInlineQuery(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: String?
_4 = parseString(reader)
var _5: Api.GeoPoint?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.GeoPoint
} }
var _6: Api.InlineQueryPeerType?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.InlineQueryPeerType
} }
var _7: String?
_7 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.Update.updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, peerType: _6, offset: _7!)
}
else {
return nil
}
}
}
public enum PopularContact: TypeConstructorDescription {
@ -12607,6 +12687,11 @@ public extension Api {
case channelAdminLogEventActionChangeLinkedChat(prevValue: Int32, newValue: Int32)
case channelAdminLogEventActionChangeLocation(prevValue: Api.ChannelLocation, newValue: Api.ChannelLocation)
case channelAdminLogEventActionToggleSlowMode(prevValue: Int32, newValue: Int32)
case channelAdminLogEventActionStartGroupCall(call: Api.InputGroupCall)
case channelAdminLogEventActionDiscardGroupCall(call: Api.InputGroupCall)
case channelAdminLogEventActionParticipantMute(participant: Api.GroupCallParticipant)
case channelAdminLogEventActionParticipantUnmute(participant: Api.GroupCallParticipant)
case channelAdminLogEventActionToggleGroupCallSetting(joinMuted: Api.Bool)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -12748,6 +12833,36 @@ public extension Api {
serializeInt32(prevValue, buffer: buffer, boxed: false)
serializeInt32(newValue, buffer: buffer, boxed: false)
break
case .channelAdminLogEventActionStartGroupCall(let call):
if boxed {
buffer.appendInt32(589338437)
}
call.serialize(buffer, true)
break
case .channelAdminLogEventActionDiscardGroupCall(let call):
if boxed {
buffer.appendInt32(-610299584)
}
call.serialize(buffer, true)
break
case .channelAdminLogEventActionParticipantMute(let participant):
if boxed {
buffer.appendInt32(-115071790)
}
participant.serialize(buffer, true)
break
case .channelAdminLogEventActionParticipantUnmute(let participant):
if boxed {
buffer.appendInt32(-431740480)
}
participant.serialize(buffer, true)
break
case .channelAdminLogEventActionToggleGroupCallSetting(let joinMuted):
if boxed {
buffer.appendInt32(1456906823)
}
joinMuted.serialize(buffer, true)
break
}
}
@ -12795,6 +12910,16 @@ public extension Api {
return ("channelAdminLogEventActionChangeLocation", [("prevValue", prevValue), ("newValue", newValue)])
case .channelAdminLogEventActionToggleSlowMode(let prevValue, let newValue):
return ("channelAdminLogEventActionToggleSlowMode", [("prevValue", prevValue), ("newValue", newValue)])
case .channelAdminLogEventActionStartGroupCall(let call):
return ("channelAdminLogEventActionStartGroupCall", [("call", call)])
case .channelAdminLogEventActionDiscardGroupCall(let call):
return ("channelAdminLogEventActionDiscardGroupCall", [("call", call)])
case .channelAdminLogEventActionParticipantMute(let participant):
return ("channelAdminLogEventActionParticipantMute", [("participant", participant)])
case .channelAdminLogEventActionParticipantUnmute(let participant):
return ("channelAdminLogEventActionParticipantUnmute", [("participant", participant)])
case .channelAdminLogEventActionToggleGroupCallSetting(let joinMuted):
return ("channelAdminLogEventActionToggleGroupCallSetting", [("joinMuted", joinMuted)])
}
}
@ -13091,6 +13216,71 @@ public extension Api {
return nil
}
}
public static func parse_channelAdminLogEventActionStartGroupCall(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
var _1: Api.InputGroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
}
let _c1 = _1 != nil
if _c1 {
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionStartGroupCall(call: _1!)
}
else {
return nil
}
}
public static func parse_channelAdminLogEventActionDiscardGroupCall(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
var _1: Api.InputGroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
}
let _c1 = _1 != nil
if _c1 {
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDiscardGroupCall(call: _1!)
}
else {
return nil
}
}
public static func parse_channelAdminLogEventActionParticipantMute(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
var _1: Api.GroupCallParticipant?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipant
}
let _c1 = _1 != nil
if _c1 {
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantMute(participant: _1!)
}
else {
return nil
}
}
public static func parse_channelAdminLogEventActionParticipantUnmute(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
var _1: Api.GroupCallParticipant?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipant
}
let _c1 = _1 != nil
if _c1 {
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantUnmute(participant: _1!)
}
else {
return nil
}
}
public static func parse_channelAdminLogEventActionToggleGroupCallSetting(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
var _1: Api.Bool?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Bool
}
let _c1 = _1 != nil
if _c1 {
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleGroupCallSetting(joinMuted: _1!)
}
else {
return nil
}
}
}
public enum SecurePlainData: TypeConstructorDescription {

View File

@ -174,7 +174,19 @@ public final class ManagedAudioSession {
private var currentTypeAndOutputMode: (ManagedAudioSessionType, AudioSessionOutputMode)?
private var deactivateTimer: SwiftSignalKit.Timer?
private var isHeadsetPluggedInValue = false
private let isHeadsetPluggedInSync = Atomic<Bool>(value: false)
private var isHeadsetPluggedInValue = false {
didSet {
if self.isHeadsetPluggedInValue != oldValue {
let _ = self.isHeadsetPluggedInSync.swap(self.isHeadsetPluggedInValue)
}
}
}
public func getIsHeadsetPluggedIn() -> Bool {
return self.isHeadsetPluggedInSync.with { $0 }
}
private let outputsToHeadphonesSubscribers = Bag<(Bool) -> Void>()
private var availableOutputsValue: [AudioSessionOutput] = []
@ -770,14 +782,17 @@ public final class ManagedAudioSession {
if let routes = AVAudioSession.sharedInstance().availableInputs {
var alreadySet = false
if self.isHeadsetPluggedInValue {
loop: for route in routes {
switch route.portType {
case .headphones, .bluetoothA2DP, .bluetoothHFP:
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
alreadySet = true
break loop
default:
break
if case .voiceCall = updatedType, case .custom(.builtin) = outputMode {
} else {
loop: for route in routes {
switch route.portType {
case .headphones, .bluetoothA2DP, .bluetoothHFP:
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
alreadySet = true
break loop
default:
break
}
}
}
}

View File

@ -275,7 +275,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
private func ringingStatesUpdated(_ ringingStates: [(Account, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) {
if let firstState = ringingStates.first {
if self.currentCall == nil {
if self.currentCall == nil && self.currentGroupCall == nil {
self.currentCallDisposable.set((combineLatest(firstState.0.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1))
|> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in
guard let strongSelf = self else {

View File

@ -324,7 +324,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
private var summaryStateDisposable: Disposable?
private var isMutedValue: PresentationGroupCallMuteAction = .muted(isPushToTalkActive: false)
private var isMutedValue: PresentationGroupCallMuteAction = .muted(isPushToTalkActive: false) {
didSet {
if self.isMutedValue != oldValue {
self.updateProximityMonitoring()
}
}
}
private let isMutedPromise = ValuePromise<PresentationGroupCallMuteAction>(.muted(isPushToTalkActive: false))
public var isMuted: Signal<Bool, NoError> {
return self.isMutedPromise.get()
@ -339,8 +345,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil))
private var audioOutputStateDisposable: Disposable?
private var actualAudioOutputState: ([AudioSessionOutput], AudioSessionOutput?)?
private var audioOutputStateValue: ([AudioSessionOutput], AudioSessionOutput?) = ([], nil)
private var currentAudioOutputValue: AudioSessionOutput = .builtin
private var currentSelectedAudioOutputValue: AudioSessionOutput = .builtin
public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
return self.audioOutputStatePromise.get()
}
@ -428,6 +436,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var proximityManagerIndex: Int?
private var removedChannelMembersDisposable: Disposable?
init(
accountContext: AccountContext,
audioSession: ManagedAudioSession,
@ -451,6 +461,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
var didReceiveAudioOutputs = false
if !audioSession.getIsHeadsetPluggedIn() {
self.currentSelectedAudioOutputValue = .speaker
}
self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, manualActivate: { [weak self] control in
Queue.mainQueue().async {
if let strongSelf = self {
@ -527,6 +541,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
})
self.audioOutputStateDisposable = (self.audioOutputStatePromise.get()
|> deliverOnMainQueue).start(next: { [weak self] availableOutputs, currentOutput in
guard let strongSelf = self else {
return
}
strongSelf.updateAudioOutputs(availableOutputs: availableOutputs, currentOutput: currentOutput)
})
self.groupCallParticipantUpdatesDisposable = (self.account.stateManager.groupCallParticipantUpdates
|> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else {
@ -604,12 +626,21 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
totalCount: 0,
loadMoreToken: nil
)
var updatedInvitedPeers = strongSelf.invitedPeersValue
var didUpdateInvitedPeers = false
for participant in state.participants {
members.participants.append(participant)
if topParticipants.count < 3 {
topParticipants.append(participant)
}
if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) {
updatedInvitedPeers.remove(at: index)
didUpdateInvitedPeers = true
}
}
members.totalCount = state.totalCount
@ -624,10 +655,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
topParticipants: topParticipants,
activeSpeakers: activeSpeakers
)))
if didUpdateInvitedPeers {
strongSelf.invitedPeersValue = updatedInvitedPeers
}
}))
}
}
self.removedChannelMembersDisposable = (accountContext.peerChannelMemberCategoriesContextsManager.removedChannelMembers
|> deliverOnMainQueue).start(next: { [weak self] pairs in
guard let strongSelf = self else {
return
}
for (channelId, memberId) in pairs {
if channelId == strongSelf.peerId {
strongSelf.removedPeer(memberId)
}
}
})
self.requestCall()
}
@ -653,6 +700,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if let proximityManagerIndex = self.proximityManagerIndex {
DeviceProximityManager.shared().remove(proximityManagerIndex)
}
self.audioOutputStateDisposable?.dispose()
self.removedChannelMembersDisposable?.dispose()
}
private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) {
@ -663,10 +714,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.internalState = internalState
if let audioSessionControl = audioSessionControl, previousControl == nil {
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
switch self.currentSelectedAudioOutputValue {
case .speaker:
audioSessionControl.setOutputMode(.custom(self.currentSelectedAudioOutputValue))
default:
break
}
audioSessionControl.setup(synchronous: true)
self.setCurrentAudioOutput(.speaker)
}
self.audioSessionShouldBeActive.set(true)
@ -811,6 +865,33 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
self.callContext?.setJoinResponse(payload: clientParams, ssrcs: ssrcs)
let accountContext = self.accountContext
let peerId = self.peerId
let rawAdminIds = Signal<Set<PeerId>, NoError> { subscriber in
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
subscriber.putNext(Set(list.list.map { $0.peer.id }))
})
return disposable
}
|> runOn(.mainQueue())
let adminIds = combineLatest(queue: .mainQueue(),
rawAdminIds,
accountContext.account.postbox.combinedView(keys: [.basicPeer(peerId)])
)
|> map { rawAdminIds, view -> Set<PeerId> in
var rawAdminIds = rawAdminIds
if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer as? TelegramChannel {
if peer.hasPermission(.manageCalls) {
rawAdminIds.insert(accountContext.account.peerId)
} else {
rawAdminIds.remove(accountContext.account.peerId)
}
}
return rawAdminIds
}
|> distinctUntilChanged
let participantsContext = GroupCallParticipantsContext(
account: self.accountContext.account,
peerId: self.peerId,
@ -821,9 +902,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.participantsContext = participantsContext
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
participantsContext.state,
participantsContext.activeSpeakers |> deliverOnMainQueue,
self.speakingParticipantsContext.get() |> deliverOnMainQueue
).start(next: { [weak self] state, activeSpeakers, speakingParticipants in
participantsContext.activeSpeakers,
self.speakingParticipantsContext.get(),
adminIds
).start(next: { [weak self] state, activeSpeakers, speakingParticipants, adminIds in
guard let strongSelf = self else {
return
}
@ -857,6 +939,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
totalCount: 0,
loadMoreToken: nil
)
var updatedInvitedPeers = strongSelf.invitedPeersValue
var didUpdateInvitedPeers = false
for participant in state.participants {
members.participants.append(participant)
@ -889,6 +975,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
strongSelf.callContext?.setIsMuted(true)
}
}
if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) {
updatedInvitedPeers.remove(at: index)
didUpdateInvitedPeers = true
}
}
members.totalCount = state.totalCount
@ -896,9 +987,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
strongSelf.membersValue = members
strongSelf.stateValue.adminIds = state.adminIds
strongSelf.stateValue.adminIds = adminIds
if (state.isCreator || state.adminIds.contains(strongSelf.accountContext.account.peerId)) && state.defaultParticipantsAreMuted.canChange {
if (state.isCreator || strongSelf.stateValue.adminIds.contains(strongSelf.accountContext.account.peerId)) && state.defaultParticipantsAreMuted.canChange {
strongSelf.stateValue.defaultParticipantMuteState = state.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted
}
@ -907,6 +998,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
topParticipants: topParticipants,
activeSpeakers: activeSpeakers
)))
if didUpdateInvitedPeers {
strongSelf.invitedPeersValue = updatedInvitedPeers
}
}))
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
@ -1032,18 +1127,35 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
guard self.currentAudioOutputValue != output else {
guard self.currentSelectedAudioOutputValue != output else {
return
}
self.currentAudioOutputValue = output
self.currentSelectedAudioOutputValue = output
self.updateProximityMonitoring()
self.audioOutputStatePromise.set(.single((self.audioOutputStateValue.0, output))
|> then(
.single(self.audioOutputStateValue)
|> delay(1.0, queue: Queue.mainQueue())
))
if let audioSessionControl = self.audioSessionControl {
audioSessionControl.setOutputMode(.custom(output))
}
}
private func updateProximityMonitoring() {
var shouldMonitorProximity = false
switch output {
switch self.currentSelectedAudioOutputValue {
case .builtin:
shouldMonitorProximity = true
default:
break
}
if case .muted(isPushToTalkActive: true) = self.isMutedValue {
shouldMonitorProximity = false
}
if shouldMonitorProximity {
if self.proximityManagerIndex == nil {
@ -1056,15 +1168,29 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
DeviceProximityManager.shared().remove(proximityManagerIndex)
}
}
self.audioOutputStatePromise.set(.single((self.audioOutputStateValue.0, output))
|> then(
.single(self.audioOutputStateValue)
|> delay(1.0, queue: Queue.mainQueue())
))
if let audioSessionControl = self.audioSessionControl {
audioSessionControl.setOutputMode(.custom(output))
}
private func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) {
if self.actualAudioOutputState?.0 != availableOutputs || self.actualAudioOutputState?.1 != currentOutput {
self.actualAudioOutputState = (availableOutputs, currentOutput)
self.setupAudioOutputs()
}
}
private func setupAudioOutputs() {
if let actualAudioOutputState = self.actualAudioOutputState, let currentOutput = actualAudioOutputState.1 {
self.currentSelectedAudioOutputValue = currentOutput
switch currentOutput {
case .headphones, .speaker:
break
case let .port(port) where port.type == .bluetooth:
break
default:
//self.setCurrentAudioOutput(.speaker)
break
}
}
}

View File

@ -579,7 +579,7 @@ public final class VoiceChatController: ViewController {
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: participant.peer, text: strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), elevatedLayout: false, action: { _ in return false }), in: .current)
} else {
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.VoiceChat_InviteMemberToGroupFirstText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), groupPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.VoiceChat_InviteMemberToGroupFirstAdd, action: {
strongSelf.controller?.present(textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: strongSelf.presentationData.strings.VoiceChat_InviteMemberToGroupFirstText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), groupPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.VoiceChat_InviteMemberToGroupFirstAdd, action: {
guard let strongSelf = self else {
return
}
@ -639,7 +639,7 @@ public final class VoiceChatController: ViewController {
case .bot:
text = presentationData.strings.Login_UnknownError
}
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.controller?.present(textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: {
guard let strongSelf = self else {
dismissController?()
@ -884,15 +884,22 @@ public final class VoiceChatController: ViewController {
if addressName.isEmpty {
let _ = ensuredExistingPeerExportedInvitation(account: strongSelf.context.account, peerId: call.peerId).start()
}
} else {
strongSelf.optionsButton.isUserInteractionEnabled = false
strongSelf.optionsButton.alpha = 0.0
}
}
strongSelf.didSetDataReady = true
strongSelf.controller?.dataReady.set(true)
}
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
if channel.hasPermission(.manageCalls) {
strongSelf.optionsButton.isUserInteractionEnabled = true
strongSelf.optionsButton.alpha = 1.0
} else {
strongSelf.optionsButton.isUserInteractionEnabled = false
strongSelf.optionsButton.alpha = 0.0
}
}
})
self.audioOutputStateDisposable = (call.audioOutputState
@ -1033,7 +1040,7 @@ public final class VoiceChatController: ViewController {
})
}
let alert = textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.VoiceChat_EndConfirmationTitle, text: strongSelf.presentationData.strings.VoiceChat_EndConfirmationText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.VoiceChat_EndConfirmationEnd, action: {
let alert = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: strongSelf.presentationData.strings.VoiceChat_EndConfirmationTitle, text: strongSelf.presentationData.strings.VoiceChat_EndConfirmationText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.VoiceChat_EndConfirmationEnd, action: {
action()
})])
strongSelf.controller?.present(alert, in: .window(.root))

View File

@ -147,6 +147,16 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
} else {
let _ = channelFlags.remove(.isVerified)
}
if (flags & Int32(1 << 23)) != 0 {
channelFlags.insert(.hasVoiceChat)
} else {
let _ = channelFlags.remove(.hasVoiceChat)
}
if (flags & Int32(1 << 24)) != 0 {
channelFlags.insert(.hasActiveVoiceChat)
} else {
let _ = channelFlags.remove(.hasActiveVoiceChat)
}
var info = lhs.info
switch info {
case .broadcast:
@ -178,6 +188,16 @@ func mergeChannel(lhs: TelegramChannel?, rhs: TelegramChannel) -> TelegramChanne
} else {
let _ = channelFlags.remove(.isVerified)
}
if rhs.flags.contains(.hasVoiceChat) {
channelFlags.insert(.hasVoiceChat)
} else {
let _ = channelFlags.remove(.hasVoiceChat)
}
if rhs.flags.contains(.hasActiveVoiceChat) {
channelFlags.insert(.hasActiveVoiceChat)
} else {
let _ = channelFlags.remove(.hasActiveVoiceChat)
}
var info = lhs.info
switch info {
case .broadcast:

View File

@ -54,6 +54,10 @@ public enum AdminLogEventAction {
case linkedPeerUpdated(previous: Peer?, updated: Peer?)
case changeGeoLocation(previous: PeerGeoLocation?, updated: PeerGeoLocation?)
case updateSlowmode(previous: Int32?, updated: Int32?)
case startGroupCall
case endGroupCall
case groupCallUpdateParticipantMuteStatus(peerId: PeerId, isMuted: Bool)
case updateGroupCallSettings(joinMuted: Bool)
}
public enum ChannelAdminLogEventError {
@ -84,12 +88,13 @@ public struct AdminLogEventsFlags: OptionSet {
public static let pinnedMessages = AdminLogEventsFlags(rawValue: 1 << 11)
public static let editMessages = AdminLogEventsFlags(rawValue: 1 << 12)
public static let deleteMessages = AdminLogEventsFlags(rawValue: 1 << 13)
public static let calls = AdminLogEventsFlags(rawValue: 1 << 14)
public static var all: AdminLogEventsFlags {
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .pinnedMessages, .editMessages, .deleteMessages]
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .pinnedMessages, .editMessages, .deleteMessages, .calls]
}
public static var flags: AdminLogEventsFlags {
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .pinnedMessages, .editMessages, .deleteMessages]
return [.join, .leave, .invite, .ban, .unban, .kick, .unkick, .promote, .demote, .info, .settings, .pinnedMessages, .editMessages, .deleteMessages, .calls]
}
}
@ -213,6 +218,18 @@ public func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: Pe
action = .changeGeoLocation(previous: PeerGeoLocation(apiLocation: prevValue), updated: PeerGeoLocation(apiLocation: newValue))
case let .channelAdminLogEventActionToggleSlowMode(prevValue, newValue):
action = .updateSlowmode(previous: prevValue == 0 ? nil : prevValue, updated: newValue == 0 ? nil : newValue)
case .channelAdminLogEventActionStartGroupCall:
action = .startGroupCall
case .channelAdminLogEventActionDiscardGroupCall:
action = .endGroupCall
case let .channelAdminLogEventActionParticipantMute(participant):
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: parsedParticipant.muteState != nil)
case let .channelAdminLogEventActionParticipantUnmute(participant):
let parsedParticipant = GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(participant)
action = .groupCallUpdateParticipantMuteStatus(peerId: parsedParticipant.peerId, isMuted: parsedParticipant.muteState != nil)
case let .channelAdminLogEventActionToggleGroupCallSetting(joinMuted):
action = .updateGroupCallSettings(joinMuted: joinMuted == .boolTrue)
}
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
if let action = action {

View File

@ -274,7 +274,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
}
return account.network.request(Api.functions.phone.joinGroupCall(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), params: .dataJSON(data: joinPayload)))
|> mapError { error -> JoinGroupCallError in
if error.errorDescription == "GROUP_CALL_ANONYMOUS_FORBIDDEN" {
if error.errorDescription == "GROUPCALL_ANONYMOUS_FORBIDDEN" {
return .anonymousNotAllowed
} else if error.errorDescription == "GROUPCALL_PARTICIPANTS_TOO_MUCH" {
return .tooManyParticipants
@ -1080,6 +1080,30 @@ public final class GroupCallParticipantsContext {
}
}
extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
init(_ apiParticipant: Api.GroupCallParticipant) {
switch apiParticipant {
case let .groupCallParticipant(flags, userId, date, activeDate, source):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
var muteState: GroupCallParticipantsContext.Participant.MuteState?
if (flags & (1 << 0)) != 0 {
let canUnmute = (flags & (1 << 2)) != 0
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
}
let isRemoved = (flags & (1 << 1)) != 0
self.init(
peerId: peerId,
ssrc: ssrc,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState,
isRemoved: isRemoved
)
}
}
}
extension GroupCallParticipantsContext.Update.StateUpdate {
init(participants: [Api.GroupCallParticipant], version: Int32, removePendingMuteStates: Set<PeerId> = Set()) {
var participantUpdates: [GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate] = []

View File

@ -395,9 +395,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
if let discardReason = discardReason {
switch discardReason {
case .busy, .disconnect:
case .disconnect:
titleString = strings.Notification_CallCanceled
case .missed:
case .missed, .busy:
titleString = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
case .hangup:
break

View File

@ -83,14 +83,14 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
callDuration = duration
if let discardReason = discardReason {
switch discardReason {
case .busy, .disconnect:
case .disconnect:
callSuccessful = false
if isVideo {
titleString = item.presentationData.strings.Notification_VideoCallCanceled
} else {
titleString = item.presentationData.strings.Notification_CallCanceled
}
case .missed:
case .missed, .busy:
callSuccessful = false
if incoming {
if isVideo {

View File

@ -300,7 +300,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
messageText = rawText
}
case .file:
let rawText = presentationData.strings.PUSH_CHAT_MESSAGE_DOCS(Int32(item.messages.count), author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
let rawText = presentationData.strings.PUSH_CHAT_MESSAGE_DOCS_FIX1(Int32(item.messages.count), author.compactDisplayTitle, peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder), Int32(item.messages.count))
if let index = rawText.firstIndex(of: "|") {
title = String(rawText[rawText.startIndex ..< index])
messageText = String(rawText[rawText.index(after: index)...])

View File

@ -1077,6 +1077,98 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
case .startGroupCall, .endGroupCall:
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let peer = self.entry.peers[self.entry.event.peerId] {
author = peer
peers[peer.id] = peer
}
var text: String = ""
var entities: [MessageTextEntity] = []
let rawText: (String, [(Int, NSRange)])
if case .startGroupCall = self.entry.event.action {
rawText = self.presentationData.strings.Channel_AdminLog_StartedVoiceChat(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
} else {
rawText = self.presentationData.strings.Channel_AdminLog_EndedVoiceChat(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
}
appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
case let .groupCallUpdateParticipantMuteStatus(participantId, isMuted):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
var participant: Peer?
if let peer = self.entry.peers[self.entry.event.peerId] {
author = peer
peers[peer.id] = peer
}
if let participantPeer = self.entry.peers[participantId] {
participant = participantPeer
peers[peer.id] = participantPeer
}
var text: String = ""
var entities: [MessageTextEntity] = []
let rawText: (String, [(Int, NSRange)])
if isMuted {
rawText = self.presentationData.strings.Channel_AdminLog_MutedParticipant(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", participant?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
} else {
rawText = self.presentationData.strings.Channel_AdminLog_UnmutedMutedParticipant(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", participant?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
}
appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
case let .updateGroupCallSettings(joinMuted):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
if let peer = self.entry.peers[self.entry.event.peerId] {
author = peer
peers[peer.id] = peer
}
var text: String = ""
var entities: [MessageTextEntity] = []
let rawText: (String, [(Int, NSRange)])
if joinMuted {
rawText = self.presentationData.strings.Channel_AdminLog_MutedNewMembers(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
} else {
rawText = self.presentationData.strings.Channel_AdminLog_AllowedNewMembersToSpeak(author?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
}
appendAttributedText(text: rawText, generateEntities: { index in
if index == 0, let author = author {
return [.TextMention(peerId: author.id)]
}
return []
}, to: &text, entities: &entities)
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
}

View File

@ -267,6 +267,11 @@ private final class PeerChannelMemberCategoriesContextsManagerImpl {
public final class PeerChannelMemberCategoriesContextsManager {
private let impl: QueueLocalObject<PeerChannelMemberCategoriesContextsManagerImpl>
private let removedChannelMembersPipe = ValuePipe<[(PeerId, PeerId)]>()
public var removedChannelMembers: Signal<[(PeerId, PeerId)], NoError> {
return self.removedChannelMembersPipe.signal()
}
public init() {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return PeerChannelMemberCategoriesContextsManagerImpl()
@ -363,6 +368,9 @@ public final class PeerChannelMemberCategoriesContextsManager {
}
}
}
if !isMember {
strongSelf.removedChannelMembersPipe.putNext([(peerId, memberId)])
}
}
}
|> mapToSignal { _ -> Signal<Void, NoError> in