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

This commit is contained in:
Ilya Laktyushin 2020-11-27 13:01:19 +04:00
commit f990207192
24 changed files with 2736 additions and 2375 deletions

View File

@ -4,8 +4,6 @@ build --action_env=ZERO_AR_DATE=1
build --strategy=Genrule=local build --strategy=Genrule=local
build --apple_platform_type=ios build --apple_platform_type=ios
build --cxxopt='-std=c++14' build --cxxopt='-std=c++14'
build --per_file_copt="third-party/webrtc/.*\.m$","@-fno-stack-protector"
build --per_file_copt="third-party/webrtc/.*\.mm$","@-fno-stack-protector"
build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++14" build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++14"
build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++14" build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++14"
build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++14" build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++14"

View File

@ -1534,7 +1534,7 @@ ios_application(
":NotificationContentExtension", ":NotificationContentExtension",
":NotificationServiceExtension", ":NotificationServiceExtension",
":IntentsExtension", ":IntentsExtension",
":WidgetExtension", #":WidgetExtension",
], ],
watch_application = ":TelegramWatchApp", watch_application = ":TelegramWatchApp",
deps = [ deps = [

View File

@ -2612,6 +2612,7 @@ Unused sets are archived when you add more.";
"Channel.AdminLog.CanAddAdmins" = "Add New Admins"; "Channel.AdminLog.CanAddAdmins" = "Add New Admins";
"Channel.AdminLog.CanBeAnonymous" = "Remain Anonymous"; "Channel.AdminLog.CanBeAnonymous" = "Remain Anonymous";
"Channel.AdminLog.CanEditMessages" = "Edit Messages"; "Channel.AdminLog.CanEditMessages" = "Edit Messages";
"Channel.AdminLog.CanManageCalls" = "Manage Calls";
"Channel.AdminLog.MessageToggleInvitesOn" = "%@ enabled group invites"; "Channel.AdminLog.MessageToggleInvitesOn" = "%@ enabled group invites";
"Channel.AdminLog.MessageToggleInvitesOff" = "%@ disabled group invites"; "Channel.AdminLog.MessageToggleInvitesOff" = "%@ disabled group invites";

View File

@ -163,14 +163,20 @@ public struct PresentationGroupCallState: Equatable {
} }
public var networkState: NetworkState public var networkState: NetworkState
public var isMuted: Bool public var canManageCall: Bool
public var adminIds: Set<PeerId>
public var muteState: GroupCallParticipantsContext.Participant.MuteState?
public init( public init(
networkState: NetworkState, networkState: NetworkState,
isMuted: Bool canManageCall: Bool,
adminIds: Set<PeerId>,
muteState: GroupCallParticipantsContext.Participant.MuteState?
) { ) {
self.networkState = networkState self.networkState = networkState
self.isMuted = isMuted self.canManageCall = canManageCall
self.adminIds = adminIds
self.muteState = muteState
} }
} }
@ -209,6 +215,11 @@ public struct PresentationGroupCallMemberState: Equatable {
} }
} }
public enum PresentationGroupCallMuteAction: Equatable {
case muted(isPushToTalkActive: Bool)
case unmuted
}
public protocol PresentationGroupCall: class { public protocol PresentationGroupCall: class {
var account: Account { get } var account: Account { get }
var accountContext: AccountContext { get } var accountContext: AccountContext { get }
@ -225,10 +236,10 @@ public protocol PresentationGroupCall: class {
var myAudioLevel: Signal<Float, NoError> { get } var myAudioLevel: Signal<Float, NoError> { get }
var isMuted: Signal<Bool, NoError> { get } var isMuted: Signal<Bool, NoError> { get }
func leave() -> Signal<Bool, NoError> func leave(terminateIfPossible: Bool) -> Signal<Bool, NoError>
func toggleIsMuted() func toggleIsMuted()
func setIsMuted(_ value: Bool) func setIsMuted(action: PresentationGroupCallMuteAction)
func setCurrentAudioOutput(_ output: AudioSessionOutput) func setCurrentAudioOutput(_ output: AudioSessionOutput)
func updateMuteState(peerId: PeerId, isMuted: Bool) func updateMuteState(peerId: PeerId, isMuted: Bool)

View File

@ -242,13 +242,25 @@ public extension UIColor {
func interpolateTo(_ color: UIColor, fraction: CGFloat) -> UIColor? { func interpolateTo(_ color: UIColor, fraction: CGFloat) -> UIColor? {
let f = min(max(0, fraction), 1) let f = min(max(0, fraction), 1)
guard let c1 = self.cgColor.components, let c2 = color.cgColor.components else { return nil } var r1: CGFloat = 0.0
let r: CGFloat = CGFloat(c1[0] + (c2[0] - c1[0]) * f) var r2: CGFloat = 0.0
let g: CGFloat = CGFloat(c1[1] + (c2[1] - c1[1]) * f) var g1: CGFloat = 0.0
let b: CGFloat = CGFloat(c1[2] + (c2[2] - c1[2]) * f) var g2: CGFloat = 0.0
let a: CGFloat = CGFloat(c1[3] + (c2[3] - c1[3]) * f) var b1: CGFloat = 0.0
var b2: CGFloat = 0.0
return UIColor(red: r, green: g, blue: b, alpha: a) var a1: CGFloat = 0.0
var a2: CGFloat = 0.0
if self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) &&
color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) {
let r: CGFloat = CGFloat(r1 + (r2 - r1) * f)
let g: CGFloat = CGFloat(g1 + (g2 - g1) * f)
let b: CGFloat = CGFloat(b1 + (b2 - b1) * f)
let a: CGFloat = CGFloat(a1 + (a2 - a1) * f)
return UIColor(red: r, green: g, blue: b, alpha: a)
} else {
return self
}
} }
private var colorComponents: (r: Int32, g: Int32, b: Int32) { private var colorComponents: (r: Int32, g: Int32, b: Int32) {

View File

@ -489,6 +489,8 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm
return strings.Channel_EditAdmin_PermissionAddAdmins return strings.Channel_EditAdmin_PermissionAddAdmins
} else if right.contains(.canBeAnonymous) { } else if right.contains(.canBeAnonymous) {
return strings.Channel_AdminLog_CanBeAnonymous return strings.Channel_AdminLog_CanBeAnonymous
} else if right.contains(.canManageCalls) {
return strings.Channel_AdminLog_CanManageCalls
} else { } else {
return "" return ""
} }
@ -511,6 +513,8 @@ private func rightDependencies(_ right: TelegramChatAdminRightsFlags) -> [Telegr
return [] return []
} else if right.contains(.canAddAdmins) { } else if right.contains(.canAddAdmins) {
return [] return []
} else if right.contains(.canManageCalls) {
return []
} else if right.contains(.canBeAnonymous) { } else if right.contains(.canBeAnonymous) {
return [] return []
} else { } else {
@ -611,6 +615,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
.canBanUsers, .canBanUsers,
.canInviteUsers, .canInviteUsers,
.canPinMessages, .canPinMessages,
.canManageCalls,
.canBeAnonymous, .canBeAnonymous,
.canAddAdmins .canAddAdmins
] ]

View File

@ -20,6 +20,7 @@ public struct TelegramChatAdminRightsFlags: OptionSet {
public static let canPinMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 7) public static let canPinMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 7)
public static let canAddAdmins = TelegramChatAdminRightsFlags(rawValue: 1 << 9) public static let canAddAdmins = TelegramChatAdminRightsFlags(rawValue: 1 << 9)
public static let canBeAnonymous = TelegramChatAdminRightsFlags(rawValue: 1 << 10) public static let canBeAnonymous = TelegramChatAdminRightsFlags(rawValue: 1 << 10)
public static let canManageCalls = TelegramChatAdminRightsFlags(rawValue: 1 << 11)
public static var groupSpecific: TelegramChatAdminRightsFlags = [ public static var groupSpecific: TelegramChatAdminRightsFlags = [
.canChangeInfo, .canChangeInfo,
@ -28,7 +29,8 @@ public struct TelegramChatAdminRightsFlags: OptionSet {
.canInviteUsers, .canInviteUsers,
.canPinMessages, .canPinMessages,
.canBeAnonymous, .canBeAnonymous,
.canAddAdmins .canAddAdmins,
.canManageCalls
] ]
public static var broadcastSpecific: TelegramChatAdminRightsFlags = [ public static var broadcastSpecific: TelegramChatAdminRightsFlags = [

View File

@ -134,7 +134,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1511503333] = { return Api.InputEncryptedFile.parse_inputEncryptedFile($0) } dict[1511503333] = { return Api.InputEncryptedFile.parse_inputEncryptedFile($0) }
dict[767652808] = { return Api.InputEncryptedFile.parse_inputEncryptedFileBigUploaded($0) } dict[767652808] = { return Api.InputEncryptedFile.parse_inputEncryptedFileBigUploaded($0) }
dict[-1456996667] = { return Api.messages.InactiveChats.parse_inactiveChats($0) } dict[-1456996667] = { return Api.messages.InactiveChats.parse_inactiveChats($0) }
dict[-1985949076] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) } dict[1454409673] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) } dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) }
dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) } dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) }
dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) } dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) }
@ -503,7 +503,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) }
dict[411017418] = { return Api.SecureValue.parse_secureValue($0) } dict[411017418] = { return Api.SecureValue.parse_secureValue($0) }
dict[-316748368] = { return Api.SecureValueHash.parse_secureValueHash($0) } dict[-316748368] = { return Api.SecureValueHash.parse_secureValueHash($0) }
dict[1447862232] = { return Api.phone.GroupCall.parse_groupCall($0) } dict[-1738792825] = { return Api.phone.GroupCall.parse_groupCall($0) }
dict[-398136321] = { return Api.messages.SearchCounter.parse_searchCounter($0) } dict[-398136321] = { return Api.messages.SearchCounter.parse_searchCounter($0) }
dict[-2128698738] = { return Api.auth.CheckedPhone.parse_checkedPhone($0) } dict[-2128698738] = { return Api.auth.CheckedPhone.parse_checkedPhone($0) }
dict[-1188055347] = { return Api.PageListItem.parse_pageListItemText($0) } dict[-1188055347] = { return Api.PageListItem.parse_pageListItemText($0) }
@ -531,7 +531,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-2042159726] = { return Api.SecurePasswordKdfAlgo.parse_securePasswordKdfAlgoSHA512($0) } dict[-2042159726] = { return Api.SecurePasswordKdfAlgo.parse_securePasswordKdfAlgoSHA512($0) }
dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) } dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) }
dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) } dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) }
dict[1021016465] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) } dict[-1661028051] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) }
dict[-2066640507] = { return Api.messages.AffectedMessages.parse_affectedMessages($0) } dict[-2066640507] = { return Api.messages.AffectedMessages.parse_affectedMessages($0) }
dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) } dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) }
dict[772213157] = { return Api.messages.SavedGifs.parse_savedGifs($0) } dict[772213157] = { return Api.messages.SavedGifs.parse_savedGifs($0) }

View File

@ -5358,17 +5358,18 @@ public extension Api {
} }
public enum GroupCallParticipant: TypeConstructorDescription { public enum GroupCallParticipant: TypeConstructorDescription {
case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, source: Int32) case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, activeDate: Int32?, source: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .groupCallParticipant(let flags, let userId, let date, let source): case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source):
if boxed { if boxed {
buffer.appendInt32(-1985949076) buffer.appendInt32(1454409673)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(userId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(activeDate!, buffer: buffer, boxed: false)}
serializeInt32(source, buffer: buffer, boxed: false) serializeInt32(source, buffer: buffer, boxed: false)
break break
} }
@ -5376,8 +5377,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .groupCallParticipant(let flags, let userId, let date, let source): case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source):
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("source", source)]) return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", source)])
} }
} }
@ -5389,13 +5390,16 @@ public extension Api {
var _3: Int32? var _3: Int32?
_3 = reader.readInt32() _3 = reader.readInt32()
var _4: Int32? var _4: Int32?
_4 = reader.readInt32() if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
var _5: Int32?
_5 = reader.readInt32()
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = _5 != nil
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, source: _4!) if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!)
} }
else { else {
return nil return nil

View File

@ -1649,13 +1649,13 @@ public struct photos {
public extension Api { public extension Api {
public struct phone { public struct phone {
public enum GroupCall: TypeConstructorDescription { public enum GroupCall: TypeConstructorDescription {
case groupCall(call: Api.GroupCall, sources: [Int32], participants: [Api.GroupCallParticipant], users: [Api.User]) case groupCall(call: Api.GroupCall, sources: [Int32], participants: [Api.GroupCallParticipant], participantsNextOffset: String, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .groupCall(let call, let sources, let participants, let users): case .groupCall(let call, let sources, let participants, let participantsNextOffset, let users):
if boxed { if boxed {
buffer.appendInt32(1447862232) buffer.appendInt32(-1738792825)
} }
call.serialize(buffer, true) call.serialize(buffer, true)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -1668,6 +1668,7 @@ public struct phone {
for item in participants { for item in participants {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
serializeString(participantsNextOffset, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count)) buffer.appendInt32(Int32(users.count))
for item in users { for item in users {
@ -1679,8 +1680,8 @@ public struct phone {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .groupCall(let call, let sources, let participants, let users): case .groupCall(let call, let sources, let participants, let participantsNextOffset, let users):
return ("groupCall", [("call", call), ("sources", sources), ("participants", participants), ("users", users)]) return ("groupCall", [("call", call), ("sources", sources), ("participants", participants), ("participantsNextOffset", participantsNextOffset), ("users", users)])
} }
} }
@ -1697,16 +1698,19 @@ public struct phone {
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
} }
var _4: [Api.User]? var _4: String?
_4 = parseString(reader)
var _5: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = _5 != nil
return Api.phone.GroupCall.groupCall(call: _1!, sources: _2!, participants: _3!, users: _4!) if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.phone.GroupCall.groupCall(call: _1!, sources: _2!, participants: _3!, participantsNextOffset: _4!, users: _5!)
} }
else { else {
return nil return nil
@ -1715,13 +1719,13 @@ public struct phone {
} }
public enum GroupParticipants: TypeConstructorDescription { public enum GroupParticipants: TypeConstructorDescription {
case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], users: [Api.User], version: Int32) case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], nextOffset: String, users: [Api.User], version: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .groupParticipants(let count, let participants, let users, let version): case .groupParticipants(let count, let participants, let nextOffset, let users, let version):
if boxed { if boxed {
buffer.appendInt32(1021016465) buffer.appendInt32(-1661028051)
} }
serializeInt32(count, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -1729,6 +1733,7 @@ public struct phone {
for item in participants { for item in participants {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
serializeString(nextOffset, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count)) buffer.appendInt32(Int32(users.count))
for item in users { for item in users {
@ -1741,8 +1746,8 @@ public struct phone {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .groupParticipants(let count, let participants, let users, let version): case .groupParticipants(let count, let participants, let nextOffset, let users, let version):
return ("groupParticipants", [("count", count), ("participants", participants), ("users", users), ("version", version)]) return ("groupParticipants", [("count", count), ("participants", participants), ("nextOffset", nextOffset), ("users", users), ("version", version)])
} }
} }
@ -1753,18 +1758,21 @@ public struct phone {
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
} }
var _3: [Api.User]? var _3: String?
_3 = parseString(reader)
var _4: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
var _4: Int32? var _5: Int32?
_4 = reader.readInt32() _5 = reader.readInt32()
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = _5 != nil
return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, users: _3!, version: _4!) if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, users: _4!, version: _5!)
} }
else { else {
return nil return nil
@ -7258,12 +7266,13 @@ public extension Api {
}) })
} }
public static func joinGroupCall(call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { public static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(893342305) buffer.appendInt32(1604095586)
serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true) call.serialize(buffer, true)
params.serialize(buffer, true) params.serialize(buffer, true)
return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("call", call), ("params", params)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("flags", flags), ("call", call), ("params", params)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -7273,11 +7282,12 @@ public extension Api {
}) })
} }
public static func leaveGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { public static func leaveGroupCall(call: Api.InputGroupCall, source: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1625919071) buffer.appendInt32(1342404601)
call.serialize(buffer, true) call.serialize(buffer, true)
return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in serializeInt32(source, buffer: buffer, boxed: false)
return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", call), ("source", source)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -7332,6 +7342,22 @@ public extension Api {
}) })
} }
public static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1958458429)
serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {joinMuted!.serialize(buffer, true)}
return (FunctionDescription(name: "phone.toggleGroupCallSettings", parameters: [("flags", flags), ("call", call), ("joinMuted", joinMuted)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func getGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCall>) { public static func getGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCall>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(209498135) buffer.appendInt32(209498135)
@ -7346,13 +7372,13 @@ public extension Api {
}) })
} }
public static func getGroupParticipants(call: Api.InputGroupCall, maxDate: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupParticipants>) { public static func getGroupParticipants(call: Api.InputGroupCall, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupParticipants>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-566111310) buffer.appendInt32(-1374089052)
call.serialize(buffer, true) call.serialize(buffer, true)
serializeInt32(maxDate, buffer: buffer, boxed: false) serializeString(offset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", call), ("maxDate", maxDate), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", call), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.phone.GroupParticipants? var result: Api.phone.GroupParticipants?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -151,15 +151,23 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
call.toggleIsMuted() call.toggleIsMuted()
} }
private var actionButtonPressGestureStartTime: Double = 0.0
@objc private func micButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { @objc private func micButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard let call = self.currentData?.groupCall else { guard let call = self.currentData?.groupCall else {
return return
} }
switch gestureRecognizer.state { switch gestureRecognizer.state {
case .began: case .began:
call.setIsMuted(false) self.actionButtonPressGestureStartTime = CACurrentMediaTime()
call.setIsMuted(action: .muted(isPushToTalkActive: true))
case .ended, .cancelled: case .ended, .cancelled:
call.setIsMuted(true) let timestamp = CACurrentMediaTime()
if timestamp - self.actionButtonPressGestureStartTime < 0.2 {
call.toggleIsMuted()
} else {
call.setIsMuted(action: .muted(isPushToTalkActive: false))
}
default: default:
break break
} }

View File

@ -598,7 +598,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
} }
if let currentGroupCall = self.currentGroupCallValue { if let currentGroupCall = self.currentGroupCallValue {
if endCurrentIfAny { if endCurrentIfAny {
let endSignal = currentGroupCall.leave() let endSignal = currentGroupCall.leave(terminateIfPossible: false)
|> filter { $0 } |> filter { $0 }
|> take(1) |> take(1)
|> deliverOnMainQueue |> deliverOnMainQueue

View File

@ -18,7 +18,9 @@ private extension PresentationGroupCallState {
static var initialValue: PresentationGroupCallState { static var initialValue: PresentationGroupCallState {
return PresentationGroupCallState( return PresentationGroupCallState(
networkState: .connecting, networkState: .connecting,
isMuted: true canManageCall: false,
adminIds: Set(),
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
) )
} }
} }
@ -83,7 +85,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
init() { init() {
} }
func update(levels: [(PeerId, Float)]) { func update(levels: [(PeerId, Float)]) {
@ -123,7 +124,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
func get() -> Signal<Set<PeerId>, NoError> { func get() -> Signal<Set<PeerId>, NoError> {
return self.speakingParticipantsPromise.get() return self.speakingParticipantsPromise.get() |> distinctUntilChanged
} }
} }
@ -156,10 +157,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
private var summaryStateDisposable: Disposable? private var summaryStateDisposable: Disposable?
private let isMutedPromise = ValuePromise<Bool>(true) private var isMutedValue: PresentationGroupCallMuteAction = .muted(isPushToTalkActive: false)
private var isMutedValue = true private let isMutedPromise = ValuePromise<PresentationGroupCallMuteAction>(.muted(isPushToTalkActive: false))
public var isMuted: Signal<Bool, NoError> { public var isMuted: Signal<Bool, NoError> {
return self.isMutedPromise.get() return self.isMutedPromise.get()
|> map { value -> Bool in
switch value {
case let .muted(isPushToTalkActive):
return isPushToTalkActive
case .unmuted:
return true
}
}
} }
private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil)) private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil))
@ -446,8 +455,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
strongSelf.requestDisposable.set((joinGroupCall( strongSelf.requestDisposable.set((joinGroupCall(
account: strongSelf.account, account: strongSelf.account,
peerId: strongSelf.peerId,
callId: callInfo.id, callId: callInfo.id,
accessHash: callInfo.accessHash, accessHash: callInfo.accessHash,
preferMuted: true,
joinPayload: joinPayload joinPayload: joinPayload
) )
|> deliverOnMainQueue).start(next: { joinCallResult in |> deliverOnMainQueue).start(next: { joinCallResult in
@ -460,14 +471,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
})) }))
})) }))
self.isMutedDisposable.set((callContext.isMuted
|> deliverOnMainQueue).start(next: { [weak self] isMuted in
guard let strongSelf = self else {
return
}
strongSelf.stateValue.isMuted = isMuted
}))
self.networkStateDisposable.set((callContext.networkState self.networkStateDisposable.set((callContext.networkState
|> deliverOnMainQueue).start(next: { [weak self] state in |> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -531,6 +534,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if case let .estabilished(callInfo, clientParams, _, initialState) = internalState { if case let .estabilished(callInfo, clientParams, _, initialState) = internalState {
self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo))) self.summaryInfoState.set(.single(SummaryInfoState(info: callInfo)))
self.stateValue.canManageCall = initialState.isCreator
self.ssrcMapping.removeAll() self.ssrcMapping.removeAll()
var ssrcs: [UInt32] = [] var ssrcs: [UInt32] = []
for participant in initialState.participants { for participant in initialState.participants {
@ -566,9 +571,21 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
muteState: participant.muteState, muteState: participant.muteState,
speaking: speakingParticipants.contains(participant.peer.id) speaking: speakingParticipants.contains(participant.peer.id)
) )
if participant.peer.id == strongSelf.accountContext.account.peerId {
if let muteState = participant.muteState {
strongSelf.stateValue.muteState = muteState
strongSelf.callContext?.setIsMuted(true)
} else if let currentMuteState = strongSelf.stateValue.muteState, !currentMuteState.canUnmute {
strongSelf.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
strongSelf.callContext?.setIsMuted(true)
}
}
} }
strongSelf.membersValue = memberStates strongSelf.membersValue = memberStates
strongSelf.stateValue.adminIds = state.adminIds
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState( strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
participantCount: state.totalCount, participantCount: state.totalCount,
topParticipants: topParticipants topParticipants: topParticipants
@ -619,13 +636,20 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
} }
public func leave() -> Signal<Bool, NoError> { public func leave(terminateIfPossible: Bool) -> Signal<Bool, NoError> {
if case let .estabilished(callInfo, _, _, _) = self.internalState { if case let .estabilished(callInfo, _, localSsrc, _) = self.internalState {
self.leaveDisposable.set((leaveGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash) if terminateIfPossible {
|> deliverOnMainQueue).start(completed: { [weak self] in self.leaveDisposable.set((stopGroupCall(account: self.account, peerId: self.peerId, callId: callInfo.id, accessHash: callInfo.accessHash)
self?._canBeRemoved.set(.single(true)) |> deliverOnMainQueue).start(completed: { [weak self] in
})) self?._canBeRemoved.set(.single(true))
} else if case .requesting = self.internalState { }))
} else {
self.leaveDisposable.set((leaveGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, source: localSsrc)
|> deliverOnMainQueue).start(completed: { [weak self] in
self?._canBeRemoved.set(.single(true))
}))
}
} else if case .requesting = self.internalState {
self.callContext?.stop() self.callContext?.stop()
self.callContext = nil self.callContext = nil
self.requestDisposable.set(nil) self.requestDisposable.set(nil)
@ -635,13 +659,39 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
public func toggleIsMuted() { public func toggleIsMuted() {
self.setIsMuted(!self.isMutedValue) switch self.isMutedValue {
case .muted:
self.setIsMuted(action: .unmuted)
case .unmuted:
self.setIsMuted(action: .muted(isPushToTalkActive: false))
}
} }
public func setIsMuted(_ value: Bool) { public func setIsMuted(action: PresentationGroupCallMuteAction) {
self.isMutedValue = value if self.isMutedValue == action {
return
}
if let muteState = self.stateValue.muteState, !muteState.canUnmute {
return
}
self.isMutedValue = action
self.isMutedPromise.set(self.isMutedValue) self.isMutedPromise.set(self.isMutedValue)
self.callContext?.setIsMuted(self.isMutedValue) let isEffectivelyMuted: Bool
switch self.isMutedValue {
case let .muted(isPushToTalkActive):
isEffectivelyMuted = !isPushToTalkActive
self.updateMuteState(peerId: self.accountContext.account.peerId, isMuted: true)
case .unmuted:
isEffectivelyMuted = false
self.updateMuteState(peerId: self.accountContext.account.peerId, isMuted: false)
}
self.callContext?.setIsMuted(isEffectivelyMuted)
if isEffectivelyMuted {
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
} else {
self.stateValue.muteState = nil
}
} }
public func setCurrentAudioOutput(_ output: AudioSessionOutput) { public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
@ -662,7 +712,29 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
public func updateMuteState(peerId: PeerId, isMuted: Bool) { public func updateMuteState(peerId: PeerId, isMuted: Bool) {
self.participantsContext?.updateMuteState(peerId: peerId, muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: peerId == self.accountContext.account.peerId) : nil) let canThenUnmute: Bool
if isMuted {
if peerId == self.accountContext.account.peerId {
canThenUnmute = true
} else if self.stateValue.canManageCall {
if self.stateValue.adminIds.contains(peerId) {
canThenUnmute = true
} else {
canThenUnmute = false
}
} else if self.stateValue.adminIds.contains(self.accountContext.account.peerId) {
canThenUnmute = true
} else {
canThenUnmute = true
}
self.participantsContext?.updateMuteState(peerId: peerId, muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canThenUnmute) : nil)
} else {
if peerId == self.accountContext.account.peerId {
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil)
} else {
self.participantsContext?.updateMuteState(peerId: peerId, muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true))
}
}
} }
private func requestCall() { private func requestCall() {

View File

@ -118,7 +118,7 @@ public final class VoiceChatController: ViewController {
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
} }
func getAudioLevel(_ peerId: PeerId) -> Signal<Float, NoError>? { func getAudioLevel(_ peerId: PeerId) -> Signal<Float, NoError> {
let signal: Signal<Float, NoError> let signal: Signal<Float, NoError>
if let current = self.audioLevels[peerId] { if let current = self.audioLevels[peerId] {
signal = current.signal() signal = current.signal()
@ -225,7 +225,7 @@ public final class VoiceChatController: ViewController {
let revealOptions: [VoiceChatParticipantItem.RevealOption] = [] let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, presence: self.presence, text: text, icon: icon, enabled: true, audioLevel: interaction.getAudioLevel(peer.id), revealOptions: revealOptions, revealed: self.revealed, setPeerIdWithRevealedOptions: { peerId, fromPeerId in return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, presence: self.presence, text: text, icon: icon, enabled: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, revealOptions: revealOptions, revealed: self.revealed, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId) interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
}, action: { }, action: {
interaction.invitePeer(peer) interaction.invitePeer(peer)
@ -377,28 +377,30 @@ public final class VoiceChatController: ViewController {
}))) })))
} }
default: default:
if entry.muteState == nil { if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_MutePeer, icon: { theme in if let muteState = entry.muteState, !muteState.canUnmute {
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: theme.actionSheet.primaryTextColor) items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_UnmutePeer, icon: { theme in
}, action: { _, f in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor)
guard let strongSelf = self else { }, action: { _, f in
return guard let strongSelf = self else {
} return
}
strongSelf.call.updateMuteState(peerId: peer.id, isMuted: true)
f(.default) strongSelf.call.updateMuteState(peerId: peer.id, isMuted: false)
}))) f(.default)
} else { })))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_UnmutePeer, icon: { theme in } else {
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor) items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_MutePeer, icon: { theme in
}, action: { _, f in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: theme.actionSheet.primaryTextColor)
guard let strongSelf = self else { }, action: { _, f in
return guard let strongSelf = self else {
} return
}
strongSelf.call.updateMuteState(peerId: peer.id, isMuted: false)
f(.default) strongSelf.call.updateMuteState(peerId: peer.id, isMuted: true)
}))) f(.default)
})))
}
} }
if peer.id != strongSelf.context.account.peerId { if peer.id != strongSelf.context.account.peerId {
@ -459,7 +461,7 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.updateMembers(isMuted: strongSelf.callState?.isMuted ?? true, members: state.list, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: strongSelf.currentInvitedPeers ?? Set()) strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, members: state.list, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: strongSelf.currentInvitedPeers ?? Set())
} }
}) })
@ -469,7 +471,7 @@ public final class VoiceChatController: ViewController {
return return
} }
if let members = strongSelf.currentMembers { if let members = strongSelf.currentMembers {
strongSelf.updateMembers(isMuted: strongSelf.callState?.isMuted ?? true, members: members, memberStates: memberStates, invitedPeers: strongSelf.currentInvitedPeers ?? Set()) strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, members: members, memberStates: memberStates, invitedPeers: strongSelf.currentInvitedPeers ?? Set())
} else { } else {
strongSelf.currentMemberStates = memberStates strongSelf.currentMemberStates = memberStates
} }
@ -481,7 +483,7 @@ public final class VoiceChatController: ViewController {
return return
} }
if let members = strongSelf.currentMembers { if let members = strongSelf.currentMembers {
strongSelf.updateMembers(isMuted: strongSelf.callState?.isMuted ?? true, members: members, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: invitedPeers) strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, members: members, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: invitedPeers)
} else { } else {
strongSelf.currentInvitedPeers = invitedPeers strongSelf.currentInvitedPeers = invitedPeers
} }
@ -531,11 +533,20 @@ public final class VoiceChatController: ViewController {
return return
} }
if strongSelf.callState != state { if strongSelf.callState != state {
let wasMuted = strongSelf.callState?.isMuted ?? true let wasMuted = strongSelf.callState?.muteState != nil
strongSelf.callState = state strongSelf.callState = state
if wasMuted != state.isMuted, let members = strongSelf.currentMembers { if state.muteState != nil {
strongSelf.updateMembers(isMuted: state.isMuted, members: members, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: strongSelf.currentInvitedPeers ?? Set()) if strongSelf.pushingToTalk {
strongSelf.pushingToTalk = false
strongSelf.actionButton.pressing = false
strongSelf.actionButton.isUserInteractionEnabled = false
strongSelf.actionButton.isUserInteractionEnabled = true
}
}
if wasMuted != (state.muteState != nil), let members = strongSelf.currentMembers {
strongSelf.updateMembers(muteState: state.muteState, members: members, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: strongSelf.currentInvitedPeers ?? Set())
} }
if let (layout, navigationHeight) = strongSelf.validLayout { if let (layout, navigationHeight) = strongSelf.validLayout {
@ -569,7 +580,7 @@ public final class VoiceChatController: ViewController {
return return
} }
var effectiveLevel: Float = 0.0 var effectiveLevel: Float = 0.0
if let state = strongSelf.callState, !state.isMuted { if let state = strongSelf.callState, state.muteState == nil {
effectiveLevel = level effectiveLevel = level
} }
strongSelf.itemInteraction?.updateAudioLevels([(strongSelf.context.account.peerId, effectiveLevel)]) strongSelf.itemInteraction?.updateAudioLevels([(strongSelf.context.account.peerId, effectiveLevel)])
@ -609,12 +620,24 @@ public final class VoiceChatController: ViewController {
strongSelf.controller?.present(shareController, in: .window(.root)) strongSelf.controller?.present(shareController, in: .window(.root))
} }
}))) })))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor) if let callState = strongSelf.callState, callState.canManageCall {
}, action: { _, f in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
f(.dismissWithoutContent) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in
}))) f(.dismissWithoutContent)
guard let strongSelf = self else {
return
}
let _ = (strongSelf.call.leave(terminateIfPossible: true)
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(completed: {
self?.controller?.dismiss()
})
})))
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode, keepInPlace: true)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode, keepInPlace: true)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController) strongSelf.controller?.presentInGlobalOverlay(contextController)
@ -642,12 +665,12 @@ public final class VoiceChatController: ViewController {
super.didLoad() super.didLoad()
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.actionButtonPressGesture(_:))) let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.actionButtonPressGesture(_:)))
longTapRecognizer.minimumPressDuration = 0.1 longTapRecognizer.minimumPressDuration = 0.001
self.actionButton.view.addGestureRecognizer(longTapRecognizer) self.actionButton.view.addGestureRecognizer(longTapRecognizer)
let panRecognizer = CallPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) let panRecognizer = CallPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
panRecognizer.shouldBegin = { [weak self] _ in panRecognizer.shouldBegin = { [weak self] _ in
guard let strongSelf = self else { guard let _ = self else {
return false return false
} }
return true return true
@ -660,22 +683,40 @@ public final class VoiceChatController: ViewController {
} }
@objc private func leavePressed() { @objc private func leavePressed() {
self.leaveDisposable.set((self.call.leave() self.leaveDisposable.set((self.call.leave(terminateIfPossible: false)
|> deliverOnMainQueue).start(completed: { [weak self] in |> deliverOnMainQueue).start(completed: { [weak self] in
self?.controller?.dismiss() self?.controller?.dismiss()
})) }))
} }
private var actionButtonPressGestureStartTime: Double = 0.0
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { @objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard let callState = self.callState else {
return
}
if let muteState = callState.muteState {
if !muteState.canUnmute {
return
}
}
switch gestureRecognizer.state { switch gestureRecognizer.state {
case .began: case .began:
self.pushingToTalk = true self.actionButtonPressGestureStartTime = CACurrentMediaTime()
self.actionButton.pressing = true self.actionButton.pressing = true
self.call.setIsMuted(false) if callState.muteState != nil {
self.pushingToTalk = true
self.call.setIsMuted(action: .muted(isPushToTalkActive: true))
}
case .ended, .cancelled: case .ended, .cancelled:
self.pushingToTalk = false self.pushingToTalk = false
self.actionButton.pressing = false self.actionButton.pressing = false
self.call.setIsMuted(true) let timestamp = CACurrentMediaTime()
if callState.muteState != nil || timestamp - self.actionButtonPressGestureStartTime < 0.1 {
self.call.toggleIsMuted()
} else {
self.call.setIsMuted(action: .muted(isPushToTalkActive: false))
}
default: default:
break break
} }
@ -776,16 +817,12 @@ public final class VoiceChatController: ViewController {
let actionButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - centralButtonSize.width) / 2.0), y: layout.size.height - bottomAreaHeight - layout.intrinsicInsets.bottom + floor((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize) let actionButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - centralButtonSize.width) / 2.0), y: layout.size.height - bottomAreaHeight - layout.intrinsicInsets.bottom + floor((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
var isMicOn = false
let actionButtonState: VoiceChatActionButtonState let actionButtonState: VoiceChatActionButtonState
let actionButtonTitle: String let actionButtonTitle: String
let actionButtonSubtitle: String let actionButtonSubtitle: String
let audioButtonAppearance: CallControllerButtonItemNode.Content.Appearance let audioButtonAppearance: CallControllerButtonItemNode.Content.Appearance
var actionButtonEnabled = true var actionButtonEnabled = true
if let callState = callState { if let callState = self.callState {
isMicOn = !callState.isMuted
switch callState.networkState { switch callState.networkState {
case .connecting: case .connecting:
actionButtonState = .connecting actionButtonState = .connecting
@ -794,15 +831,26 @@ public final class VoiceChatController: ViewController {
audioButtonAppearance = .color(.custom(0x1c1c1e)) audioButtonAppearance = .color(.custom(0x1c1c1e))
actionButtonEnabled = false actionButtonEnabled = false
case .connected: case .connected:
actionButtonState = .active(state: isMicOn ? .on : .muted) if let muteState = callState.muteState {
if isMicOn { if muteState.canUnmute {
actionButtonState = .active(state: .muted)
actionButtonTitle = self.presentationData.strings.VoiceChat_Unmute
actionButtonSubtitle = self.presentationData.strings.VoiceChat_UnmuteHelp
audioButtonAppearance = .color(.custom(0x00274d))
} else {
actionButtonState = .active(state: .cantSpeak)
actionButtonTitle = self.presentationData.strings.VoiceChat_Muted
actionButtonSubtitle = self.presentationData.strings.VoiceChat_MutedHelp
audioButtonAppearance = .color(.custom(0x00274d))
}
} else {
actionButtonState = .active(state: .on)
actionButtonTitle = self.pushingToTalk ? self.presentationData.strings.VoiceChat_Live : self.presentationData.strings.VoiceChat_Mute actionButtonTitle = self.pushingToTalk ? self.presentationData.strings.VoiceChat_Live : self.presentationData.strings.VoiceChat_Mute
actionButtonSubtitle = "" actionButtonSubtitle = ""
audioButtonAppearance = .color(.custom(0x005720)) audioButtonAppearance = .color(.custom(0x005720))
} else {
actionButtonTitle = self.presentationData.strings.VoiceChat_Unmute
actionButtonSubtitle = self.presentationData.strings.VoiceChat_UnmuteHelp
audioButtonAppearance = .color(.custom(0x00274d))
} }
} }
} else { } else {
@ -951,7 +999,7 @@ public final class VoiceChatController: ViewController {
}) })
} }
private func updateMembers(isMuted: Bool, members: [RenderedChannelParticipant], memberStates: [PeerId: PresentationGroupCallMemberState], invitedPeers: Set<PeerId>) { private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, members: [RenderedChannelParticipant], memberStates: [PeerId: PresentationGroupCallMemberState], invitedPeers: Set<PeerId>) {
var members = members var members = members
members.sort(by: { lhs, rhs in members.sort(by: { lhs, rhs in
if lhs.peer.id == self.context.account.peerId { if lhs.peer.id == self.context.account.peerId {
@ -988,7 +1036,7 @@ public final class VoiceChatController: ViewController {
let memberState: PeerEntry.State let memberState: PeerEntry.State
var memberMuteState: GroupCallParticipantsContext.Participant.MuteState? var memberMuteState: GroupCallParticipantsContext.Participant.MuteState?
if member.peer.id == self.context.account.peerId { if member.peer.id == self.context.account.peerId {
if !isMuted { if muteState == nil {
memberState = .speaking memberState = .speaking
} else { } else {
memberState = .listening memberState = .listening

View File

@ -65,14 +65,14 @@ public final class VoiceChatParticipantItem: ListViewItem {
let text: ParticipantText let text: ParticipantText
let icon: Icon let icon: Icon
let enabled: Bool let enabled: Bool
let audioLevel: Signal<Float, NoError>? let getAudioLevel: (() -> Signal<Float, NoError>)?
let revealOptions: [RevealOption] let revealOptions: [RevealOption]
let revealed: Bool? let revealed: Bool?
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let action: (() -> Void)? let action: (() -> Void)?
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, presence: PeerPresence?, text: ParticipantText, icon: Icon, enabled: Bool, audioLevel: Signal<Float, NoError>?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: (() -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) { public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, presence: PeerPresence?, text: ParticipantText, icon: Icon, enabled: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: (() -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
self.presentationData = presentationData self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
@ -82,7 +82,7 @@ public final class VoiceChatParticipantItem: ListViewItem {
self.text = text self.text = text
self.icon = icon self.icon = icon
self.enabled = enabled self.enabled = enabled
self.audioLevel = audioLevel self.getAudioLevel = getAudioLevel
self.revealOptions = revealOptions self.revealOptions = revealOptions
self.revealed = revealed self.revealed = revealed
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
@ -543,54 +543,60 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
transition.updateFrameAsPositionAndBounds(node: strongSelf.avatarNode, frame: avatarFrame) transition.updateFrameAsPositionAndBounds(node: strongSelf.avatarNode, frame: avatarFrame)
let blobFrame = avatarFrame.insetBy(dx: -12.0, dy: -12.0) let blobFrame = avatarFrame.insetBy(dx: -12.0, dy: -12.0)
if let audioLevel = item.audioLevel, !strongSelf.didSetupAudioLevel || currentItem?.peer.id != item.peer.id { if let getAudioLevel = item.getAudioLevel {
strongSelf.audioLevelView?.frame = blobFrame if !strongSelf.didSetupAudioLevel || currentItem?.peer.id != item.peer.id {
strongSelf.didSetupAudioLevel = true strongSelf.audioLevelView?.frame = blobFrame
strongSelf.audioLevelDisposable.set((audioLevel strongSelf.didSetupAudioLevel = true
|> deliverOnMainQueue).start(next: { value in strongSelf.audioLevelDisposable.set((getAudioLevel()
guard let strongSelf = self else { |> deliverOnMainQueue).start(next: { value in
return guard let strongSelf = self else {
} return
}
if strongSelf.audioLevelView == nil {
let audioLevelView = VoiceBlobView(
frame: blobFrame,
maxLevel: 0.3,
smallBlobRange: (0, 0),
mediumBlobRange: (0.7, 0.8),
bigBlobRange: (0.8, 0.9)
)
let maskRect = CGRect(origin: .zero, size: blobFrame.size) if strongSelf.audioLevelView == nil {
let playbackMaskLayer = CAShapeLayer() let audioLevelView = VoiceBlobView(
playbackMaskLayer.frame = maskRect frame: blobFrame,
playbackMaskLayer.fillRule = .evenOdd maxLevel: 0.3,
let maskPath = UIBezierPath() smallBlobRange: (0, 0),
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22)) mediumBlobRange: (0.7, 0.8),
maskPath.append(UIBezierPath(rect: maskRect)) bigBlobRange: (0.8, 0.9)
playbackMaskLayer.path = maskPath.cgPath )
audioLevelView.layer.mask = playbackMaskLayer
let maskRect = CGRect(origin: .zero, size: blobFrame.size)
let playbackMaskLayer = CAShapeLayer()
playbackMaskLayer.frame = maskRect
playbackMaskLayer.fillRule = .evenOdd
let maskPath = UIBezierPath()
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22))
maskPath.append(UIBezierPath(rect: maskRect))
playbackMaskLayer.path = maskPath.cgPath
audioLevelView.layer.mask = playbackMaskLayer
audioLevelView.setColor(.green)
strongSelf.audioLevelView = audioLevelView
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
}
audioLevelView.setColor(.green) var value = value
strongSelf.audioLevelView = audioLevelView if value <= 0.15 {
strongSelf.containerNode.view.insertSubview(audioLevelView, at: 0) value = 0.0
} }
let level = min(1.0, max(0.0, CGFloat(value)))
let level = min(1.0, max(0.0, CGFloat(value))) let avatarScale: CGFloat
let avatarScale: CGFloat
strongSelf.audioLevelView?.updateLevel(CGFloat(value) * 2.0)
strongSelf.audioLevelView?.updateLevel(CGFloat(value) * 2.0) if value > 0.0 {
if value > 0.0 { strongSelf.audioLevelView?.startAnimating()
strongSelf.audioLevelView?.startAnimating() avatarScale = 1.03 + level * 0.1
avatarScale = 1.03 + level * 0.1 } else {
} else { strongSelf.audioLevelView?.stopAnimating(duration: 0.5)
strongSelf.audioLevelView?.stopAnimating(duration: 0.5) avatarScale = 1.0
avatarScale = 1.0 }
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .spring)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .spring) transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale, beginWithCurrentState: true)
transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale, beginWithCurrentState: true) }))
})) }
} else if let audioLevelView = strongSelf.audioLevelView { } else if let audioLevelView = strongSelf.audioLevelView {
strongSelf.audioLevelView = nil strongSelf.audioLevelView = nil
audioLevelView.removeFromSuperview() audioLevelView.removeFromSuperview()

View File

@ -50,7 +50,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
} }
|> mapToSignal { result -> Signal<GroupCallSummary?, GetCurrentGroupCallError> in |> mapToSignal { result -> Signal<GroupCallSummary?, GetCurrentGroupCallError> in
switch result { switch result {
case let .groupCall(call, _, participants, users): case let .groupCall(call, _, participants, _, users):
return account.postbox.transaction { transaction -> GroupCallSummary? in return account.postbox.transaction { transaction -> GroupCallSummary? in
guard let info = GroupCallInfo(call) else { guard let info = GroupCallInfo(call) else {
return nil return nil
@ -76,7 +76,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
loop: for participant in participants { loop: for participant in participants {
switch participant { switch participant {
case let .groupCallParticipant(flags, userId, date, source): case let .groupCallParticipant(flags, userId, date, activeDate, source):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source) let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else { guard let peer = transaction.getPeer(peerId) else {
@ -127,8 +127,6 @@ public func createGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCal
return .generic return .generic
} }
|> mapToSignal { result -> Signal<GroupCallInfo, CreateGroupCallError> in |> mapToSignal { result -> Signal<GroupCallInfo, CreateGroupCallError> in
account.stateManager.addUpdates(result)
var parsedCall: GroupCallInfo? var parsedCall: GroupCallInfo?
loop: for update in result.allUpdates { loop: for update in result.allUpdates {
switch update { switch update {
@ -140,11 +138,24 @@ public func createGroupCall(account: Account, peerId: PeerId) -> Signal<GroupCal
} }
} }
if let parsedCall = parsedCall { guard let callInfo = parsedCall else {
return .single(parsedCall)
} else {
return .fail(.generic) return .fail(.generic)
} }
return account.postbox.transaction { transaction -> GroupCallInfo in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData {
return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash))
} else {
return cachedData
}
})
account.stateManager.addUpdates(result)
return callInfo
}
|> castError(CreateGroupCallError.self)
} }
} }
} }
@ -153,8 +164,8 @@ public enum GetGroupCallParticipantsError {
case generic case generic
} }
public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, maxDate: Int32, limit: Int32) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> { public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, limit: Int32) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), maxDate: maxDate, limit: limit)) return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), offset: offset, limit: limit))
|> mapError { _ -> GetGroupCallParticipantsError in |> mapError { _ -> GetGroupCallParticipantsError in
return .generic return .generic
} }
@ -163,12 +174,19 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
var parsedParticipants: [GroupCallParticipantsContext.Participant] = [] var parsedParticipants: [GroupCallParticipantsContext.Participant] = []
let totalCount: Int let totalCount: Int
let version: Int32 let version: Int32
let nextParticipantsFetchOffset: String?
switch result { switch result {
case let .groupParticipants(count, participants, users, apiVersion): case let .groupParticipants(count, participants, nextOffset, users, apiVersion):
totalCount = Int(count) totalCount = Int(count)
version = apiVersion version = apiVersion
if participants.count != 0 && !nextOffset.isEmpty {
nextParticipantsFetchOffset = nextOffset
} else {
nextParticipantsFetchOffset = nil
}
var peers: [Peer] = [] var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:] var peerPresences: [PeerId: PeerPresence] = [:]
@ -187,7 +205,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
loop: for participant in participants { loop: for participant in participants {
switch participant { switch participant {
case let .groupCallParticipant(flags, userId, date, source): case let .groupCallParticipant(flags, userId, date, activeDate, source):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source) let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else { guard let peer = transaction.getPeer(peerId) else {
@ -202,6 +220,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
peer: peer, peer: peer,
ssrc: ssrc, ssrc: ssrc,
joinTimestamp: date, joinTimestamp: date,
activityTimestamp: activeDate,
muteState: muteState muteState: muteState
)) ))
} }
@ -210,6 +229,9 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
return GroupCallParticipantsContext.State( return GroupCallParticipantsContext.State(
participants: parsedParticipants, participants: parsedParticipants,
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
adminIds: Set(),
isCreator: false,
totalCount: totalCount, totalCount: totalCount,
version: version version: version
) )
@ -227,23 +249,56 @@ public struct JoinGroupCallResult {
public var state: GroupCallParticipantsContext.State public var state: GroupCallParticipantsContext.State
} }
public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal<JoinGroupCallResult, JoinGroupCallError> { public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String) -> Signal<JoinGroupCallResult, JoinGroupCallError> {
return account.network.request(Api.functions.phone.joinGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), params: .dataJSON(data: joinPayload))) var flags: Int32 = 0
if preferMuted {
flags |= (1 << 0)
}
return account.network.request(Api.functions.phone.joinGroupCall(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), params: .dataJSON(data: joinPayload)))
|> mapError { _ -> JoinGroupCallError in |> mapError { _ -> JoinGroupCallError in
return .generic return .generic
} }
|> mapToSignal { updates -> Signal<JoinGroupCallResult, JoinGroupCallError> in |> mapToSignal { updates -> Signal<JoinGroupCallResult, JoinGroupCallError> in
let admins = account.postbox.transaction { transaction -> Api.InputChannel? in
return transaction.getPeer(peerId).flatMap(apiInputChannel)
}
|> castError(JoinGroupCallError.self)
|> mapToSignal { inputChannel -> Signal<Api.channels.ChannelParticipants, JoinGroupCallError> in
guard let inputChannel = inputChannel else {
return .fail(.generic)
}
return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: 0))
|> mapError { _ -> JoinGroupCallError in
return .generic
}
}
let channel = account.postbox.transaction { transaction -> TelegramChannel? in
return transaction.getPeer(peerId) as? TelegramChannel
}
|> castError(JoinGroupCallError.self)
return combineLatest( return combineLatest(
account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|> mapError { _ -> JoinGroupCallError in |> mapError { _ -> JoinGroupCallError in
return .generic return .generic
}, },
getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, maxDate: 0, limit: 100) getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", limit: 100)
|> mapError { _ -> JoinGroupCallError in |> mapError { _ -> JoinGroupCallError in
return .generic return .generic
} },
admins,
channel
) )
|> mapToSignal { result, state -> Signal<JoinGroupCallResult, JoinGroupCallError> in |> mapToSignal { result, state, admins, channel -> Signal<JoinGroupCallResult, JoinGroupCallError> in
guard let channel = channel else {
return .fail(.generic)
}
var state = state
state.isCreator = channel.flags.contains(.isCreator)
account.stateManager.addUpdates(updates) account.stateManager.addUpdates(updates)
var maybeParsedCall: GroupCallInfo? var maybeParsedCall: GroupCallInfo?
@ -261,12 +316,55 @@ public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, jo
return .fail(.generic) return .fail(.generic)
} }
var apiUsers: [Api.User] = []
var adminIds = Set<PeerId>()
switch admins {
case let .channelParticipants(_, participants, users):
apiUsers.append(contentsOf: users)
for participant in participants {
let parsedParticipant = ChannelParticipant(apiParticipant: participant)
switch parsedParticipant {
case .creator:
adminIds.insert(parsedParticipant.peerId)
case let .member(_, _, adminInfo, _, _):
if let adminInfo = adminInfo, adminInfo.rights.flags.contains(.canManageCalls) {
adminIds.insert(parsedParticipant.peerId)
}
}
}
default:
break
}
state.adminIds = adminIds
switch result { switch result {
case let .groupCall(call, sources, _, users): case let .groupCall(call, sources, _, _, users):
guard let _ = GroupCallInfo(call) else { guard let _ = GroupCallInfo(call) else {
return .fail(.generic) return .fail(.generic)
} }
apiUsers.append(contentsOf: users)
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
for user in apiUsers {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
if let presence = TelegramUserPresence(apiUser: user) {
peerPresences[telegramUser.id] = presence
}
}
return account.postbox.transaction { transaction -> JoinGroupCallResult in return account.postbox.transaction { transaction -> JoinGroupCallResult in
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
return JoinGroupCallResult( return JoinGroupCallResult(
callInfo: parsedCall, callInfo: parsedCall,
state: state state: state
@ -282,8 +380,8 @@ public enum LeaveGroupCallError {
case generic case generic
} }
public func leaveGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal<Never, LeaveGroupCallError> { public func leaveGroupCall(account: Account, callId: Int64, accessHash: Int64, source: UInt32) -> Signal<Never, LeaveGroupCallError> {
return account.network.request(Api.functions.phone.leaveGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) return account.network.request(Api.functions.phone.leaveGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), source: Int32(bitPattern: source)))
|> mapError { _ -> LeaveGroupCallError in |> mapError { _ -> LeaveGroupCallError in
return .generic return .generic
} }
@ -294,15 +392,25 @@ public enum StopGroupCallError {
case generic case generic
} }
public func stopGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal<Never, StopGroupCallError> { public func stopGroupCall(account: Account, peerId: PeerId, callId: Int64, accessHash: Int64) -> Signal<Never, StopGroupCallError> {
return account.network.request(Api.functions.phone.discardGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) return account.network.request(Api.functions.phone.discardGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|> mapError { _ -> StopGroupCallError in |> mapError { _ -> StopGroupCallError in
return .generic return .generic
} }
|> mapToSignal { result -> Signal<Never, StopGroupCallError> in |> mapToSignal { result -> Signal<Never, StopGroupCallError> in
account.stateManager.addUpdates(result) return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
return .complete() if let cachedData = cachedData as? CachedChannelData {
return cachedData.withUpdatedActiveCall(nil)
} else {
return cachedData
}
})
account.stateManager.addUpdates(result)
}
|> castError(StopGroupCallError.self)
|> ignoreValues
} }
} }
@ -355,6 +463,7 @@ public final class GroupCallParticipantsContext {
public var peer: Peer public var peer: Peer
public var ssrc: UInt32 public var ssrc: UInt32
public var joinTimestamp: Int32 public var joinTimestamp: Int32
public var activityTimestamp: Int32?
public var muteState: MuteState? public var muteState: MuteState?
public static func ==(lhs: Participant, rhs: Participant) -> Bool { public static func ==(lhs: Participant, rhs: Participant) -> Bool {
@ -367,6 +476,9 @@ public final class GroupCallParticipantsContext {
if lhs.joinTimestamp != rhs.joinTimestamp { if lhs.joinTimestamp != rhs.joinTimestamp {
return false return false
} }
if lhs.activityTimestamp != rhs.activityTimestamp {
return false
}
if lhs.muteState != rhs.muteState { if lhs.muteState != rhs.muteState {
return false return false
} }
@ -376,6 +488,9 @@ public final class GroupCallParticipantsContext {
public struct State: Equatable { public struct State: Equatable {
public var participants: [Participant] public var participants: [Participant]
public var nextParticipantsFetchOffset: String?
public var adminIds: Set<PeerId>
public var isCreator: Bool
public var totalCount: Int public var totalCount: Int
public var version: Int32 public var version: Int32
} }
@ -416,6 +531,7 @@ public final class GroupCallParticipantsContext {
public var peerId: PeerId public var peerId: PeerId
public var ssrc: UInt32 public var ssrc: UInt32
public var joinTimestamp: Int32 public var joinTimestamp: Int32
public var activityTimestamp: Int32?
public var muteState: Participant.MuteState? public var muteState: Participant.MuteState?
public var isRemoved: Bool public var isRemoved: Bool
} }
@ -561,9 +677,16 @@ public final class GroupCallParticipantsContext {
updatedOverlayState.pendingMuteStateChanges.removeValue(forKey: peerId) updatedOverlayState.pendingMuteStateChanges.removeValue(forKey: peerId)
} }
let nextParticipantsFetchOffset = strongSelf.stateValue.state.nextParticipantsFetchOffset
let adminIds = strongSelf.stateValue.state.adminIds
let isCreator = strongSelf.stateValue.state.isCreator
strongSelf.stateValue = InternalState( strongSelf.stateValue = InternalState(
state: State( state: State(
participants: Array(updatedParticipants.reversed()), participants: Array(updatedParticipants.reversed()),
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
adminIds: adminIds,
isCreator: isCreator,
totalCount: updatedTotalCount, totalCount: updatedTotalCount,
version: update.version version: update.version
), ),
@ -577,8 +700,7 @@ public final class GroupCallParticipantsContext {
private func resetStateFromServer() { private func resetStateFromServer() {
self.updateQueue.removeAll() self.updateQueue.removeAll()
self.disposable.set(( self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", limit: 100)
getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, maxDate: 0, limit: 100)
|> deliverOnMainQueue).start(next: { [weak self] state in |> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -615,9 +737,10 @@ public final class GroupCallParticipantsContext {
return .single(nil) return .single(nil)
} }
var flags: Int32 = 0 var flags: Int32 = 0
if muteState != nil { if let muteState = muteState, !muteState.canUnmute {
flags |= 1 << 0 flags |= 1 << 0
} }
return account.network.request(Api.functions.phone.editGroupCallMember(flags: flags, call: .inputGroupCall(id: id, accessHash: accessHash), userId: inputUser)) return account.network.request(Api.functions.phone.editGroupCallMember(flags: flags, call: .inputGroupCall(id: id, accessHash: accessHash), userId: inputUser))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
@ -664,7 +787,7 @@ extension GroupCallParticipantsContext.StateUpdate {
var participantUpdates: [GroupCallParticipantsContext.StateUpdate.ParticipantUpdate] = [] var participantUpdates: [GroupCallParticipantsContext.StateUpdate.ParticipantUpdate] = []
for participant in participants { for participant in participants {
switch participant { switch participant {
case let .groupCallParticipant(flags, userId, date, source): case let .groupCallParticipant(flags, userId, date, activeDate, source):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source) let ssrc = UInt32(bitPattern: source)
var muteState: GroupCallParticipantsContext.Participant.MuteState? var muteState: GroupCallParticipantsContext.Participant.MuteState?
@ -677,6 +800,7 @@ extension GroupCallParticipantsContext.StateUpdate {
peerId: peerId, peerId: peerId,
ssrc: ssrc, ssrc: ssrc,
joinTimestamp: date, joinTimestamp: date,
activityTimestamp: activeDate,
muteState: muteState, muteState: muteState,
isRemoved: isRemoved isRemoved: isRemoved
)) ))

View File

@ -13,6 +13,7 @@ public enum TelegramChannelPermission {
case addAdmins case addAdmins
case changeInfo case changeInfo
case canBeAnonymous case canBeAnonymous
case manageCalls
} }
public extension TelegramChannel { public extension TelegramChannel {
@ -124,6 +125,11 @@ public extension TelegramChannel {
return true return true
} }
return false return false
case .manageCalls:
if let adminRights = self.adminRights, adminRights.flags.contains(.canManageCalls) {
return true
}
return false
case .canBeAnonymous: case .canBeAnonymous:
if let adminRights = self.adminRights, adminRights.flags.contains(.canBeAnonymous) { if let adminRights = self.adminRights, adminRights.flags.contains(.canBeAnonymous) {
return true return true

View File

@ -718,7 +718,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
(.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages), (.canEditMessages, self.presentationData.strings.Channel_AdminLog_CanEditMessages),
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers), (.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages), (.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins) (.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
(.canManageCalls, self.presentationData.strings.Channel_AdminLog_CanManageCalls)
] ]
} else { } else {
order = [ order = [
@ -728,7 +729,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
(.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers), (.canInviteUsers, self.presentationData.strings.Channel_AdminLog_CanInviteUsers),
(.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages), (.canPinMessages, self.presentationData.strings.Channel_AdminLog_CanPinMessages),
(.canBeAnonymous, self.presentationData.strings.Channel_AdminLog_CanBeAnonymous), (.canBeAnonymous, self.presentationData.strings.Channel_AdminLog_CanBeAnonymous),
(.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins) (.canAddAdmins, self.presentationData.strings.Channel_AdminLog_CanAddAdmins),
(.canManageCalls, self.presentationData.strings.Channel_AdminLog_CanManageCalls)
] ]
} }

View File

@ -51,7 +51,7 @@ public final class OngoingGroupCallContext {
let audioLevels = ValuePipe<[(UInt32, Float)]>() let audioLevels = ValuePipe<[(UInt32, Float)]>()
let myAudioLevel = ValuePipe<Float>() let myAudioLevel = ValuePipe<Float>()
init(queue: Queue) { init(queue: Queue, inputDeviceId: String, outputDeviceId: String) {
self.queue = queue self.queue = queue
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)? var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
@ -68,7 +68,9 @@ public final class OngoingGroupCallContext {
}, },
myAudioLevelUpdated: { level in myAudioLevelUpdated: { level in
myAudioLevelUpdatedImpl?(level) myAudioLevelUpdatedImpl?(level)
} },
inputDeviceId: inputDeviceId,
outputDeviceId: outputDeviceId
) )
let queue = self.queue let queue = self.queue
@ -190,6 +192,13 @@ public final class OngoingGroupCallContext {
self.isMuted.set(isMuted) self.isMuted.set(isMuted)
self.context.setIsMuted(isMuted) self.context.setIsMuted(isMuted)
} }
func switchAudioInput(_ deviceId: String) {
self.context.switchAudioInput(deviceId)
}
func switchAudioOutput(_ deviceId: String) {
self.context.switchAudioOutput(deviceId)
}
} }
private let queue = Queue() private let queue = Queue()
@ -267,10 +276,10 @@ public final class OngoingGroupCallContext {
} }
} }
public init() { public init(inputDeviceId: String = "", outputDeviceId: String = "") {
let queue = self.queue let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: { self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue) return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId)
}) })
} }
@ -280,6 +289,16 @@ public final class OngoingGroupCallContext {
} }
} }
public func switchAudioInput(_ deviceId: String) {
self.impl.with { impl in
impl.switchAudioInput(deviceId)
}
}
public func switchAudioOutput(_ deviceId: String) {
self.impl.with { impl in
impl.switchAudioOutput(deviceId)
}
}
public func setJoinResponse(payload: String, ssrcs: [UInt32]) { public func setJoinResponse(payload: String, ssrcs: [UInt32]) {
self.impl.with { impl in self.impl.with { impl in
impl.setJoinResponse(payload: payload, ssrcs: ssrcs) impl.setJoinResponse(payload: payload, ssrcs: ssrcs)

View File

@ -158,7 +158,7 @@ typedef NS_ENUM(int32_t, GroupCallNetworkState) {
@interface GroupCallThreadLocalContext : NSObject @interface GroupCallThreadLocalContext : NSObject
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue networkStateUpdated:(void (^ _Nonnull)(GroupCallNetworkState))networkStateUpdated audioLevelsUpdated:(void (^ _Nonnull)(NSArray<NSNumber *> * _Nonnull))audioLevelsUpdated myAudioLevelUpdated:(void (^ _Nonnull)(float))myAudioLevelUpdated; - (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue networkStateUpdated:(void (^ _Nonnull)(GroupCallNetworkState))networkStateUpdated audioLevelsUpdated:(void (^ _Nonnull)(NSArray<NSNumber *> * _Nonnull))audioLevelsUpdated myAudioLevelUpdated:(void (^ _Nonnull)(float))myAudioLevelUpdated inputDeviceId:(NSString * _Nonnull)inputDeviceId outputDeviceId:(NSString * _Nonnull)outputDeviceId;
- (void)stop; - (void)stop;
@ -167,6 +167,9 @@ typedef NS_ENUM(int32_t, GroupCallNetworkState) {
- (void)setSsrcs:(NSArray<NSNumber *> * _Nonnull)ssrcs; - (void)setSsrcs:(NSArray<NSNumber *> * _Nonnull)ssrcs;
- (void)setIsMuted:(bool)isMuted; - (void)setIsMuted:(bool)isMuted;
- (void)switchAudioOutput:(NSString * _Nonnull)deviceId;
- (void)switchAudioInput:(NSString * _Nonnull)deviceId;
@end @end
#endif #endif

View File

@ -808,7 +808,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
@implementation GroupCallThreadLocalContext @implementation GroupCallThreadLocalContext
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue networkStateUpdated:(void (^ _Nonnull)(GroupCallNetworkState))networkStateUpdated audioLevelsUpdated:(void (^ _Nonnull)(NSArray<NSNumber *> * _Nonnull))audioLevelsUpdated myAudioLevelUpdated:(void (^ _Nonnull)(float))myAudioLevelUpdated { - (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue networkStateUpdated:(void (^ _Nonnull)(GroupCallNetworkState))networkStateUpdated audioLevelsUpdated:(void (^ _Nonnull)(NSArray<NSNumber *> * _Nonnull))audioLevelsUpdated myAudioLevelUpdated:(void (^ _Nonnull)(float))myAudioLevelUpdated inputDeviceId:(NSString * _Nonnull)inputDeviceId outputDeviceId:(NSString * _Nonnull)outputDeviceId {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_queue = queue; _queue = queue;
@ -836,7 +836,9 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
}, },
.myAudioLevelUpdated = [myAudioLevelUpdated](float level) { .myAudioLevelUpdated = [myAudioLevelUpdated](float level) {
myAudioLevelUpdated(level); myAudioLevelUpdated(level);
} },
.initialInputDeviceId = inputDeviceId.UTF8String,
.initialOutputDeviceId = outputDeviceId.UTF8String
})); }));
} }
return self; return self;
@ -1043,5 +1045,16 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
} }
- (void)switchAudioOutput:(NSString * _Nonnull)deviceId {
if (_instance) {
_instance->setAudioOutputDevice(deviceId.UTF8String);
}
}
- (void)switchAudioInput:(NSString * _Nonnull)deviceId {
if (_instance) {
_instance->setAudioInputDevice(deviceId.UTF8String);
}
}
@end @end

@ -1 +1 @@
Subproject commit aeaa63b502a1ee88feef282af739980001138993 Subproject commit 9e25105d8662f54b7151070225c5866d0f0a6231