mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +00:00
Voice Chat Improvements
This commit is contained in:
parent
d7694f997c
commit
845ca1e843
@ -5810,3 +5810,9 @@ Sorry for the inconvenience.";
|
|||||||
"Call.VoiceOver.VideoCallMissed" = "Missed Video Call";
|
"Call.VoiceOver.VideoCallMissed" = "Missed Video Call";
|
||||||
"Call.VoiceOver.VoiceCallCanceled" = "Cancelled Voice Call";
|
"Call.VoiceOver.VoiceCallCanceled" = "Cancelled Voice Call";
|
||||||
"Call.VoiceOver.VideoCallCanceled" = "Cancelled Video Call";
|
"Call.VoiceOver.VideoCallCanceled" = "Cancelled Video Call";
|
||||||
|
|
||||||
|
"VoiceChat.UnmuteForMe" = "Unmute for Me";
|
||||||
|
"VoiceChat.MuteForMe" = "Mute for Me";
|
||||||
|
|
||||||
|
"PeerInfo.ButtonVoiceChat" = "Voice Chat";
|
||||||
|
"VoiceChat.OpenChat" = "Open Chat";
|
||||||
|
|||||||
@ -294,7 +294,7 @@ public protocol PresentationGroupCall: class {
|
|||||||
func toggleIsMuted()
|
func toggleIsMuted()
|
||||||
func setIsMuted(action: PresentationGroupCallMuteAction)
|
func setIsMuted(action: PresentationGroupCallMuteAction)
|
||||||
func updateDefaultParticipantsAreMuted(isMuted: Bool)
|
func updateDefaultParticipantsAreMuted(isMuted: Bool)
|
||||||
func setVolume(peerId: PeerId, volume: Double)
|
func setVolume(peerId: PeerId, volume: Int32, sync: Bool)
|
||||||
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
||||||
|
|
||||||
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
||||||
|
|||||||
@ -89,7 +89,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
|||||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||||
itemNodes.append(.itemSeparator(separatorNode))
|
itemNodes.append(.itemSeparator(separatorNode))
|
||||||
}
|
}
|
||||||
case let .custom(item):
|
case let .custom(item, _):
|
||||||
itemNodes.append(.custom(item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)))
|
itemNodes.append(.custom(item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)))
|
||||||
if i != items.count - 1, case .action = items[i + 1] {
|
if i != items.count - 1, case .action = items[i + 1] {
|
||||||
let separatorNode = ASDisplayNode()
|
let separatorNode = ASDisplayNode()
|
||||||
@ -425,6 +425,7 @@ private final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
|||||||
final class ContextActionsContainerNode: ASDisplayNode {
|
final class ContextActionsContainerNode: ASDisplayNode {
|
||||||
private let blurBackground: Bool
|
private let blurBackground: Bool
|
||||||
private let shadowNode: ASImageNode
|
private let shadowNode: ASImageNode
|
||||||
|
private let additionalActionsNode: InnerActionsContainerNode?
|
||||||
private let actionsNode: InnerActionsContainerNode
|
private let actionsNode: InnerActionsContainerNode
|
||||||
private let textSelectionTipNode: InnerTextSelectionTipContainerNode?
|
private let textSelectionTipNode: InnerTextSelectionTipContainerNode?
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
@ -446,6 +447,14 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||||||
self.shadowNode.contentMode = .scaleToFill
|
self.shadowNode.contentMode = .scaleToFill
|
||||||
self.shadowNode.isHidden = true
|
self.shadowNode.isHidden = true
|
||||||
|
|
||||||
|
var items = items
|
||||||
|
if let firstItem = items.first, case let .custom(item, additional) = firstItem, additional {
|
||||||
|
self.additionalActionsNode = InnerActionsContainerNode(presentationData: presentationData, items: [firstItem], getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
||||||
|
items.removeFirst()
|
||||||
|
} else {
|
||||||
|
self.additionalActionsNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
||||||
if displayTextSelectionTip {
|
if displayTextSelectionTip {
|
||||||
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData)
|
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData)
|
||||||
@ -466,6 +475,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.shadowNode)
|
self.addSubnode(self.shadowNode)
|
||||||
|
self.additionalActionsNode.flatMap(self.scrollNode.addSubnode)
|
||||||
self.scrollNode.addSubnode(self.actionsNode)
|
self.scrollNode.addSubnode(self.actionsNode)
|
||||||
self.textSelectionTipNode.flatMap(self.scrollNode.addSubnode)
|
self.textSelectionTipNode.flatMap(self.scrollNode.addSubnode)
|
||||||
self.addSubnode(self.scrollNode)
|
self.addSubnode(self.scrollNode)
|
||||||
@ -477,13 +487,24 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||||||
widthClass = .regular
|
widthClass = .regular
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var contentSize = CGSize()
|
||||||
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, transition: transition)
|
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, transition: transition)
|
||||||
|
|
||||||
let bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: actionsSize)
|
if let additionalActionsNode = self.additionalActionsNode {
|
||||||
|
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, transition: transition)
|
||||||
|
contentSize = additionalActionsSize
|
||||||
|
|
||||||
|
transition.updateFrame(node: additionalActionsNode, frame: CGRect(origin: CGPoint(), size: additionalActionsSize))
|
||||||
|
contentSize.height += 8.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounds = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: actionsSize)
|
||||||
transition.updateFrame(node: self.shadowNode, frame: bounds.insetBy(dx: -30.0, dy: -30.0))
|
transition.updateFrame(node: self.shadowNode, frame: bounds.insetBy(dx: -30.0, dy: -30.0))
|
||||||
self.shadowNode.isHidden = widthClass == .compact
|
self.shadowNode.isHidden = widthClass == .compact
|
||||||
|
|
||||||
var contentSize = actionsSize
|
contentSize.width = max(contentSize.width, actionsSize.width)
|
||||||
|
contentSize.height += actionsSize.height
|
||||||
|
|
||||||
transition.updateFrame(node: self.actionsNode, frame: bounds)
|
transition.updateFrame(node: self.actionsNode, frame: bounds)
|
||||||
|
|
||||||
if let textSelectionTipNode = self.textSelectionTipNode {
|
if let textSelectionTipNode = self.textSelectionTipNode {
|
||||||
|
|||||||
@ -84,7 +84,7 @@ public protocol ContextMenuCustomItem {
|
|||||||
|
|
||||||
public enum ContextMenuItem {
|
public enum ContextMenuItem {
|
||||||
case action(ContextMenuActionItem)
|
case action(ContextMenuActionItem)
|
||||||
case custom(ContextMenuCustomItem)
|
case custom(ContextMenuCustomItem, Bool)
|
||||||
case separator
|
case separator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -139,7 +139,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[1454409673] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
|
dict[-1199443157] = { 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) }
|
||||||
|
|||||||
@ -5438,27 +5438,29 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
public enum GroupCallParticipant: TypeConstructorDescription {
|
public enum GroupCallParticipant: TypeConstructorDescription {
|
||||||
case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, activeDate: Int32?, source: Int32)
|
case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?, mutedCnt: 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 activeDate, let source):
|
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume, let mutedCnt):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(1454409673)
|
buffer.appendInt32(-1199443157)
|
||||||
}
|
}
|
||||||
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)}
|
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)
|
||||||
|
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(mutedCnt!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 activeDate, let source):
|
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume, let mutedCnt):
|
||||||
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", source)])
|
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", source), ("volume", volume), ("mutedCnt", mutedCnt)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5473,13 +5475,19 @@ public extension Api {
|
|||||||
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
|
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
|
||||||
var _5: Int32?
|
var _5: Int32?
|
||||||
_5 = reader.readInt32()
|
_5 = reader.readInt32()
|
||||||
|
var _6: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 7) != 0 {_6 = reader.readInt32() }
|
||||||
|
var _7: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 8) != 0 {_7 = 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 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
|
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
|
||||||
let _c5 = _5 != nil
|
let _c5 = _5 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil
|
||||||
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!)
|
let _c7 = (Int(_1!) & Int(1 << 8) == 0) || _7 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||||
|
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, mutedCnt: _7)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -7287,22 +7287,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func editGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(1662282468)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
call.serialize(buffer, true)
|
|
||||||
userId.serialize(buffer, true)
|
|
||||||
return (FunctionDescription(name: "phone.editGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId)]), 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 inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
public static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(2067345760)
|
buffer.appendInt32(2067345760)
|
||||||
@ -7406,6 +7390,23 @@ public extension Api {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func editGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser, volume: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1511559976)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
call.serialize(buffer, true)
|
||||||
|
userId.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "phone.editGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId), ("volume", volume)]), 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
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -325,7 +325,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let panelData = currentGroupCall != nil ? nil : availableState
|
let panelData = currentGroupCall != nil || availableState?.participantCount == 0 ? nil : availableState
|
||||||
|
|
||||||
let wasEmpty = strongSelf.groupCallPanelData == nil
|
let wasEmpty = strongSelf.groupCallPanelData == nil
|
||||||
strongSelf.groupCallPanelData = panelData
|
strongSelf.groupCallPanelData = panelData
|
||||||
|
|||||||
@ -171,7 +171,7 @@ private extension PresentationGroupCallState {
|
|||||||
networkState: .connecting,
|
networkState: .connecting,
|
||||||
canManageCall: false,
|
canManageCall: false,
|
||||||
adminIds: Set(),
|
adminIds: Set(),
|
||||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true),
|
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||||
defaultParticipantMuteState: nil
|
defaultParticipantMuteState: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -669,7 +669,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
ssrc: 0,
|
ssrc: 0,
|
||||||
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
||||||
activityTimestamp: nil,
|
activityTimestamp: nil,
|
||||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
|
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||||
|
volume: nil
|
||||||
))
|
))
|
||||||
participants.sort()
|
participants.sort()
|
||||||
}
|
}
|
||||||
@ -1042,7 +1043,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.stateValue.muteState = muteState
|
strongSelf.stateValue.muteState = muteState
|
||||||
} else if let currentMuteState = strongSelf.stateValue.muteState, !currentMuteState.canUnmute {
|
} else if let currentMuteState = strongSelf.stateValue.muteState, !currentMuteState.canUnmute {
|
||||||
strongSelf.isMutedValue = .muted(isPushToTalkActive: false)
|
strongSelf.isMutedValue = .muted(isPushToTalkActive: false)
|
||||||
strongSelf.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
|
strongSelf.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false)
|
||||||
strongSelf.callContext?.setIsMuted(true)
|
strongSelf.callContext?.setIsMuted(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1229,16 +1230,19 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.callContext?.setIsMuted(isEffectivelyMuted)
|
self.callContext?.setIsMuted(isEffectivelyMuted)
|
||||||
|
|
||||||
if isVisuallyMuted {
|
if isVisuallyMuted {
|
||||||
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
|
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false)
|
||||||
} else {
|
} else {
|
||||||
self.stateValue.muteState = nil
|
self.stateValue.muteState = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setVolume(peerId: PeerId, volume: Double) {
|
public func setVolume(peerId: PeerId, volume: Int32, sync: Bool) {
|
||||||
for (ssrc, id) in self.ssrcMapping {
|
for (ssrc, id) in self.ssrcMapping {
|
||||||
if id == peerId {
|
if id == peerId {
|
||||||
self.callContext?.setVolume(ssrc: ssrc, volume: volume)
|
self.callContext?.setVolume(ssrc: ssrc, volume: Double(volume) / 10000.0)
|
||||||
|
if sync {
|
||||||
|
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: volume)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1315,6 +1319,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
public func updateMuteState(peerId: PeerId, isMuted: Bool) {
|
public func updateMuteState(peerId: PeerId, isMuted: Bool) {
|
||||||
let canThenUnmute: Bool
|
let canThenUnmute: Bool
|
||||||
if isMuted {
|
if isMuted {
|
||||||
|
var mutedByYou = false
|
||||||
if peerId == self.accountContext.account.peerId {
|
if peerId == self.accountContext.account.peerId {
|
||||||
canThenUnmute = true
|
canThenUnmute = true
|
||||||
} else if self.stateValue.canManageCall {
|
} else if self.stateValue.canManageCall {
|
||||||
@ -1326,14 +1331,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
} else if self.stateValue.adminIds.contains(self.accountContext.account.peerId) {
|
} else if self.stateValue.adminIds.contains(self.accountContext.account.peerId) {
|
||||||
canThenUnmute = true
|
canThenUnmute = true
|
||||||
} else {
|
} else {
|
||||||
|
mutedByYou = true
|
||||||
canThenUnmute = true
|
canThenUnmute = true
|
||||||
}
|
}
|
||||||
self.participantsContext?.updateMuteState(peerId: peerId, muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canThenUnmute) : nil)
|
self.participantsContext?.updateMuteState(peerId: peerId, muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canThenUnmute, mutedByYou: mutedByYou) : nil, volume: nil)
|
||||||
} else {
|
} else {
|
||||||
if peerId == self.accountContext.account.peerId {
|
if peerId == self.accountContext.account.peerId {
|
||||||
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil)
|
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: nil)
|
||||||
|
} else if self.stateValue.canManageCall || self.stateValue.adminIds.contains(self.accountContext.account.peerId) {
|
||||||
|
self.participantsContext?.updateMuteState(peerId: peerId, muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), volume: nil)
|
||||||
} else {
|
} else {
|
||||||
self.participantsContext?.updateMuteState(peerId: peerId, muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true))
|
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -209,6 +209,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||||
var revealed: Bool?
|
var revealed: Bool?
|
||||||
var canManageCall: Bool
|
var canManageCall: Bool
|
||||||
|
var volume: Int32?
|
||||||
|
|
||||||
var stableId: PeerId {
|
var stableId: PeerId {
|
||||||
return self.peer.id
|
return self.peer.id
|
||||||
@ -236,6 +237,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
if lhs.canManageCall != rhs.canManageCall {
|
if lhs.canManageCall != rhs.canManageCall {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.volume != rhs.volume {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,17 +337,30 @@ public final class VoiceChatController: ViewController {
|
|||||||
let icon: VoiceChatParticipantItem.Icon
|
let icon: VoiceChatParticipantItem.Icon
|
||||||
switch peerEntry.state {
|
switch peerEntry.state {
|
||||||
case .listening:
|
case .listening:
|
||||||
text = .text(presentationData.strings.VoiceChat_StatusListening, .accent)
|
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||||
|
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
|
||||||
|
} else {
|
||||||
|
text = .text(presentationData.strings.VoiceChat_StatusListening, .accent)
|
||||||
|
}
|
||||||
let microphoneColor: UIColor
|
let microphoneColor: UIColor
|
||||||
if let muteState = peerEntry.muteState, !muteState.canUnmute {
|
if let muteState = peerEntry.muteState, !muteState.canUnmute || muteState.mutedByYou {
|
||||||
microphoneColor = UIColor(rgb: 0xff3b30)
|
microphoneColor = UIColor(rgb: 0xff3b30)
|
||||||
} else {
|
} else {
|
||||||
microphoneColor = UIColor(rgb: 0x979797)
|
microphoneColor = UIColor(rgb: 0x979797)
|
||||||
}
|
}
|
||||||
icon = .microphone(peerEntry.muteState != nil, microphoneColor)
|
icon = .microphone(peerEntry.muteState != nil, microphoneColor)
|
||||||
case .speaking:
|
case .speaking:
|
||||||
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
|
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||||
icon = .microphone(false, UIColor(rgb: 0x34c759))
|
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
|
||||||
|
icon = .microphone(true, UIColor(rgb: 0xff3b30))
|
||||||
|
} else {
|
||||||
|
if let volume = peerEntry.volume, volume != 10000 {
|
||||||
|
text = .text("\(volume / 100)% \(presentationData.strings.VoiceChat_StatusSpeaking)", .constructive)
|
||||||
|
} else {
|
||||||
|
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
|
||||||
|
}
|
||||||
|
icon = .microphone(false, UIColor(rgb: 0x34c759))
|
||||||
|
}
|
||||||
case .invited:
|
case .invited:
|
||||||
text = .text(presentationData.strings.VoiceChat_StatusInvited, .generic)
|
text = .text(presentationData.strings.VoiceChat_StatusInvited, .generic)
|
||||||
icon = .invite(true)
|
icon = .invite(true)
|
||||||
@ -355,7 +372,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
|
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
|
||||||
}, action: {
|
}, action: {
|
||||||
interaction.openPeer(peer.id)
|
interaction.openPeer(peer.id)
|
||||||
}, contextAction: peer.id == context.account.peerId || !peerEntry.canManageCall ? nil : { node, gesture in
|
}, contextAction: peer.id == context.account.peerId ? nil : { node, gesture in
|
||||||
interaction.peerContextAction(peerEntry, node, gesture)
|
interaction.peerContextAction(peerEntry, node, gesture)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -387,6 +404,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
private let topPanelNode: ASDisplayNode
|
private let topPanelNode: ASDisplayNode
|
||||||
private let topPanelEdgeNode: ASDisplayNode
|
private let topPanelEdgeNode: ASDisplayNode
|
||||||
private let topPanelBackgroundNode: ASDisplayNode
|
private let topPanelBackgroundNode: ASDisplayNode
|
||||||
|
private let recButton: VoiceChatHeaderButton
|
||||||
private let optionsButton: VoiceChatHeaderButton
|
private let optionsButton: VoiceChatHeaderButton
|
||||||
private let closeButton: VoiceChatHeaderButton
|
private let closeButton: VoiceChatHeaderButton
|
||||||
private let topCornersNode: ASImageNode
|
private let topCornersNode: ASImageNode
|
||||||
@ -493,6 +511,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.topPanelEdgeNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
self.topPanelEdgeNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.recButton = VoiceChatHeaderButton(rec: true)
|
||||||
|
self.recButton.setImage(optionsBackgroundImage(dark: false))
|
||||||
|
self.recButton.isHidden = true
|
||||||
self.optionsButton = VoiceChatHeaderButton()
|
self.optionsButton = VoiceChatHeaderButton()
|
||||||
self.optionsButton.setImage(optionsButtonImage(dark: false))
|
self.optionsButton.setImage(optionsButtonImage(dark: false))
|
||||||
self.closeButton = VoiceChatHeaderButton()
|
self.closeButton = VoiceChatHeaderButton()
|
||||||
@ -792,6 +813,16 @@ public final class VoiceChatController: ViewController {
|
|||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
if peer.id != strongSelf.context.account.peerId {
|
if peer.id != strongSelf.context.account.peerId {
|
||||||
|
if let muteState = entry.muteState, muteState.mutedByYou {
|
||||||
|
} else {
|
||||||
|
items.append(.custom(VoiceChatVolumeContextItem(value: entry.volume.flatMap { CGFloat($0) / 10000.0 } ?? 1.0, valueChanged: { newValue, finished in
|
||||||
|
if finished && newValue.isZero {
|
||||||
|
|
||||||
|
}
|
||||||
|
strongSelf.call.setVolume(peerId: peer.id, volume: Int32(newValue * 10000), sync: finished)
|
||||||
|
}), true))
|
||||||
|
}
|
||||||
|
|
||||||
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
||||||
if callState.adminIds.contains(peer.id) {
|
if callState.adminIds.contains(peer.id) {
|
||||||
if let _ = entry.muteState {
|
if let _ = entry.muteState {
|
||||||
@ -832,8 +863,43 @@ public final class VoiceChatController: ViewController {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if let muteState = entry.muteState, muteState.mutedByYou {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_UnmuteForMe, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.call.updateMuteState(peerId: peer.id, isMuted: false)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_MuteForMe, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.call.updateMuteState(peerId: peer.id, isMuted: true)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_OpenChat, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.itemInteraction?.openPeer(peer.id)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
|
||||||
if let callState = strongSelf.callState, (callState.canManageCall && !callState.adminIds.contains(peer.id)) {
|
if let callState = strongSelf.callState, (callState.canManageCall && !callState.adminIds.contains(peer.id)) {
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
@ -873,10 +939,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(.custom(VoiceChatVolumeContextItem(value: 1.0, valueChanged: { newValue in
|
|
||||||
strongSelf.call.setVolume(peerId: peer.id, volume: Double(newValue))
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !items.isEmpty else {
|
guard !items.isEmpty else {
|
||||||
@ -896,6 +958,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.topPanelNode.addSubnode(self.topPanelEdgeNode)
|
self.topPanelNode.addSubnode(self.topPanelEdgeNode)
|
||||||
self.topPanelNode.addSubnode(self.topPanelBackgroundNode)
|
self.topPanelNode.addSubnode(self.topPanelBackgroundNode)
|
||||||
self.topPanelNode.addSubnode(self.titleNode)
|
self.topPanelNode.addSubnode(self.titleNode)
|
||||||
|
self.topPanelNode.addSubnode(self.recButton)
|
||||||
self.topPanelNode.addSubnode(self.optionsButton)
|
self.topPanelNode.addSubnode(self.optionsButton)
|
||||||
self.topPanelNode.addSubnode(self.closeButton)
|
self.topPanelNode.addSubnode(self.closeButton)
|
||||||
self.topPanelNode.addSubnode(self.topCornersNode)
|
self.topPanelNode.addSubnode(self.topCornersNode)
|
||||||
@ -1080,7 +1143,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
|
self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.optionsButton.contextAction = { [weak self, weak optionsButton] sourceNode, gesture in
|
self.optionsButton.contextAction = { [weak self, weak optionsButton] sourceNode, gesture in
|
||||||
guard let strongSelf = self, let controller = strongSelf.controller, let strongOptionsButton = optionsButton else {
|
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1123,6 +1186,15 @@ public final class VoiceChatController: ViewController {
|
|||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.append(.custom(VoiceChatRecordingContextItem(timestamp: CFAbsoluteTimeGetCurrent(), action: { (_, f) in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
}), false))
|
||||||
|
|
||||||
|
if !items.isEmpty {
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
|
|
||||||
if let callState = strongSelf.callState, callState.canManageCall {
|
if let callState = strongSelf.callState, callState.canManageCall {
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
|
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)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
@ -1153,11 +1225,21 @@ public final class VoiceChatController: ViewController {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode, keepInPlace: false, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture)
|
let optionsButton: VoiceChatHeaderButton
|
||||||
|
if !strongSelf.recButton.isHidden {
|
||||||
|
optionsButton = strongSelf.recButton
|
||||||
|
} else {
|
||||||
|
optionsButton = strongSelf.optionsButton
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: optionsButton.extractedContainerNode, keepInPlace: false, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.recButton.contextAction = self.optionsButton.contextAction
|
||||||
|
|
||||||
self.optionsButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
|
self.optionsButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.recButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
|
||||||
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
|
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.actionButtonColorDisposable = (self.actionButton.outerColor
|
self.actionButtonColorDisposable = (self.actionButton.outerColor
|
||||||
@ -1566,6 +1648,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: isFullscreen)
|
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: isFullscreen)
|
||||||
|
|
||||||
|
self.recButton.setImage(optionsBackgroundImage(dark: isFullscreen), animated: transition.isAnimated)
|
||||||
self.optionsButton.setImage(optionsButtonImage(dark: isFullscreen), animated: transition.isAnimated)
|
self.optionsButton.setImage(optionsButtonImage(dark: isFullscreen), animated: transition.isAnimated)
|
||||||
self.closeButton.setImage(closeButtonImage(dark: isFullscreen), animated: transition.isAnimated)
|
self.closeButton.setImage(closeButtonImage(dark: isFullscreen), animated: transition.isAnimated)
|
||||||
|
|
||||||
@ -1670,6 +1753,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.updateTitle(transition: transition)
|
self.updateTitle(transition: transition)
|
||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: CGSize(width: size.width, height: 44.0)))
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: CGSize(width: size.width, height: 44.0)))
|
||||||
|
transition.updateFrame(node: self.recButton, frame: CGRect(origin: CGPoint(x: 20.0, y: 18.0), size: CGSize(width: 58.0, height: 28.0)))
|
||||||
transition.updateFrame(node: self.optionsButton, frame: CGRect(origin: CGPoint(x: 20.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
|
transition.updateFrame(node: self.optionsButton, frame: CGRect(origin: CGPoint(x: 20.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
|
||||||
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - 20.0 - 28.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
|
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - 20.0 - 28.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
|
||||||
|
|
||||||
@ -2019,7 +2103,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
activityTimestamp: Int32.max - 1 - index,
|
activityTimestamp: Int32.max - 1 - index,
|
||||||
state: memberState,
|
state: memberState,
|
||||||
muteState: memberMuteState,
|
muteState: memberMuteState,
|
||||||
canManageCall: callState?.canManageCall ?? false
|
canManageCall: self.callState?.canManageCall ?? false,
|
||||||
|
volume: member.volume
|
||||||
)))
|
)))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -2030,8 +2115,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
presence: nil,
|
presence: nil,
|
||||||
activityTimestamp: Int32.max - 1 - index,
|
activityTimestamp: Int32.max - 1 - index,
|
||||||
state: .listening,
|
state: .listening,
|
||||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true),
|
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||||
canManageCall: callState?.canManageCall ?? false
|
canManageCall: self.callState?.canManageCall ?? false,
|
||||||
|
volume: nil
|
||||||
)), at: 1)
|
)), at: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2047,7 +2133,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
activityTimestamp: Int32.max - 1 - index,
|
activityTimestamp: Int32.max - 1 - index,
|
||||||
state: .invited,
|
state: .invited,
|
||||||
muteState: nil,
|
muteState: nil,
|
||||||
canManageCall: false
|
canManageCall: false,
|
||||||
|
volume: nil
|
||||||
)))
|
)))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,15 @@ import UIKit
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
|
||||||
|
func optionsBackgroundImage(dark: Bool) -> UIImage? {
|
||||||
|
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
})?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14)
|
||||||
|
}
|
||||||
|
|
||||||
func optionsButtonImage(dark: Bool) -> UIImage? {
|
func optionsButtonImage(dark: Bool) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
@ -45,13 +54,30 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
|||||||
|
|
||||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
init() {
|
var textNode: ImmediateTextNode?
|
||||||
|
var dotNode: ASImageNode?
|
||||||
|
|
||||||
|
init(rec: Bool = false) {
|
||||||
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.containerNode.isGestureEnabled = false
|
self.containerNode.isGestureEnabled = false
|
||||||
self.iconNode = ASImageNode()
|
self.iconNode = ASImageNode()
|
||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
self.iconNode.displayWithoutProcessing = true
|
self.iconNode.displayWithoutProcessing = true
|
||||||
|
self.iconNode.contentMode = .scaleToFill
|
||||||
|
|
||||||
|
if rec {
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
self.textNode?.attributedText = NSAttributedString(string: "REC", font: Font.regular(12.0), textColor: .white)
|
||||||
|
if let textNode = self.textNode {
|
||||||
|
let textSize = textNode.updateLayout(CGSize(width: 58.0, height: 28.0))
|
||||||
|
textNode.frame = CGRect(origin: CGPoint(), size: textSize)
|
||||||
|
}
|
||||||
|
self.dotNode = ASImageNode()
|
||||||
|
self.dotNode?.displaysAsynchronously = false
|
||||||
|
self.dotNode?.displayWithoutProcessing = true
|
||||||
|
self.dotNode?.image = generateFilledCircleImage(diameter: 8.0, color: UIColor(rgb: 0xff3b30))
|
||||||
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -60,6 +86,11 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
|||||||
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
|
if rec, let textNode = self.textNode, let dotNode = self.dotNode {
|
||||||
|
self.extractedContainerNode.contentNode.addSubnode(textNode)
|
||||||
|
self.extractedContainerNode.contentNode.addSubnode(dotNode)
|
||||||
|
}
|
||||||
|
|
||||||
self.containerNode.shouldBegin = { [weak self] location in
|
self.containerNode.shouldBegin = { [weak self] location in
|
||||||
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||||
return false
|
return false
|
||||||
@ -75,7 +106,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
|||||||
|
|
||||||
self.iconNode.image = optionsButtonImage(dark: false)
|
self.iconNode.image = optionsButtonImage(dark: false)
|
||||||
|
|
||||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
|
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: rec ? 58.0 : 28.0, height: 28.0))
|
||||||
self.extractedContainerNode.frame = self.containerNode.bounds
|
self.extractedContainerNode.frame = self.containerNode.bounds
|
||||||
self.extractedContainerNode.contentRect = self.containerNode.bounds
|
self.extractedContainerNode.contentRect = self.containerNode.bounds
|
||||||
self.iconNode.frame = self.containerNode.bounds
|
self.iconNode.frame = self.containerNode.bounds
|
||||||
@ -96,10 +127,29 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
self.view.isOpaque = false
|
self.view.isOpaque = false
|
||||||
|
|
||||||
|
if let dotNode = self.dotNode {
|
||||||
|
let animation = CAKeyframeAnimation(keyPath: "opacity")
|
||||||
|
animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.0 as NSNumber]
|
||||||
|
animation.keyTimes = [0.0 as NSNumber, 0.4546 as NSNumber, 0.9091 as NSNumber, 1 as NSNumber]
|
||||||
|
animation.duration = 0.5
|
||||||
|
animation.autoreverses = true
|
||||||
|
animation.repeatCount = Float.infinity
|
||||||
|
dotNode.layer.add(animation, forKey: "recording")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||||
return CGSize(width: 28.0, height: 28.0)
|
return CGSize(width: self.dotNode != nil ? 58.0 : 28.0, height: 28.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layout() {
|
||||||
|
super.layout()
|
||||||
|
|
||||||
|
if let dotNode = self.dotNode, let textNode = self.textNode {
|
||||||
|
dotNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 8.0, height: 8.0))
|
||||||
|
textNode.frame = CGRect(origin: CGPoint(x: 22.0, y: 7.0), size: textNode.frame.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func onLayout() {
|
func onLayout() {
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import ContextUI
|
|||||||
|
|
||||||
final class VoiceChatVolumeContextItem: ContextMenuCustomItem {
|
final class VoiceChatVolumeContextItem: ContextMenuCustomItem {
|
||||||
private let value: CGFloat
|
private let value: CGFloat
|
||||||
private let valueChanged: (CGFloat) -> Void
|
private let valueChanged: (CGFloat, Bool) -> Void
|
||||||
|
|
||||||
init(value: CGFloat, valueChanged: @escaping (CGFloat) -> Void) {
|
init(value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
|
||||||
self.value = value
|
self.value = value
|
||||||
self.valueChanged = valueChanged
|
self.valueChanged = valueChanged
|
||||||
}
|
}
|
||||||
@ -38,11 +38,11 @@ private final class VoiceChatVolumeContextItemNode: ASDisplayNode, ContextMenuCu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let valueChanged: (CGFloat) -> Void
|
private let valueChanged: (CGFloat, Bool) -> Void
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
init(presentationData: PresentationData, getController: @escaping () -> ContextController?, value: CGFloat, valueChanged: @escaping (CGFloat) -> Void) {
|
init(presentationData: PresentationData, getController: @escaping () -> ContextController?, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.value = value
|
self.value = value
|
||||||
self.valueChanged = valueChanged
|
self.valueChanged = valueChanged
|
||||||
@ -169,7 +169,12 @@ private final class VoiceChatVolumeContextItemNode: ASDisplayNode, ContextMenuCu
|
|||||||
} else if self.value == 0.0 && previousValue != 0.0 {
|
} else if self.value == 0.0 && previousValue != 0.0 {
|
||||||
self.hapticFeedback.impact(.soft)
|
self.hapticFeedback.impact(.soft)
|
||||||
}
|
}
|
||||||
case .ended, .cancelled:
|
self.valueChanged(self.value, false)
|
||||||
|
case .ended:
|
||||||
|
let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
|
||||||
|
let delta = translation / self.bounds.width * 2.0
|
||||||
|
self.value = max(0.0, min(2.0, self.value + delta))
|
||||||
|
self.valueChanged(self.value, true)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -88,23 +88,28 @@ 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, activeDate, source):
|
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
|
||||||
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 {
|
||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
|
let muted = (flags & (1 << 0)) != 0
|
||||||
|
let mutedByYou = (flags & (1 << 9)) != 0
|
||||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||||
if (flags & (1 << 0)) != 0 {
|
if muted {
|
||||||
let canUnmute = (flags & (1 << 2)) != 0
|
let canUnmute = (flags & (1 << 2)) != 0
|
||||||
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
|
||||||
|
} else if mutedByYou {
|
||||||
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
|
||||||
}
|
}
|
||||||
parsedParticipants.append(GroupCallParticipantsContext.Participant(
|
parsedParticipants.append(GroupCallParticipantsContext.Participant(
|
||||||
peer: peer,
|
peer: peer,
|
||||||
ssrc: ssrc,
|
ssrc: ssrc,
|
||||||
joinTimestamp: date,
|
joinTimestamp: date,
|
||||||
activityTimestamp: activeDate.flatMap(Double.init),
|
activityTimestamp: activeDate.flatMap(Double.init),
|
||||||
muteState: muteState
|
muteState: muteState,
|
||||||
|
volume: volume
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,23 +228,28 @@ 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, activeDate, source):
|
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
|
||||||
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 {
|
||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
|
let muted = (flags & (1 << 0)) != 0
|
||||||
|
let mutedByYou = (flags & (1 << 9)) != 0
|
||||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||||
if (flags & (1 << 0)) != 0 {
|
if muted {
|
||||||
let canUnmute = (flags & (1 << 2)) != 0
|
let canUnmute = (flags & (1 << 2)) != 0
|
||||||
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
|
||||||
|
} else if mutedByYou {
|
||||||
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
|
||||||
}
|
}
|
||||||
parsedParticipants.append(GroupCallParticipantsContext.Participant(
|
parsedParticipants.append(GroupCallParticipantsContext.Participant(
|
||||||
peer: peer,
|
peer: peer,
|
||||||
ssrc: ssrc,
|
ssrc: ssrc,
|
||||||
joinTimestamp: date,
|
joinTimestamp: date,
|
||||||
activityTimestamp: activeDate.flatMap(Double.init),
|
activityTimestamp: activeDate.flatMap(Double.init),
|
||||||
muteState: muteState
|
muteState: muteState,
|
||||||
|
volume: volume
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,9 +554,11 @@ public final class GroupCallParticipantsContext {
|
|||||||
public struct Participant: Equatable, Comparable {
|
public struct Participant: Equatable, Comparable {
|
||||||
public struct MuteState: Equatable {
|
public struct MuteState: Equatable {
|
||||||
public var canUnmute: Bool
|
public var canUnmute: Bool
|
||||||
|
public var mutedByYou: Bool
|
||||||
|
|
||||||
public init(canUnmute: Bool) {
|
public init(canUnmute: Bool, mutedByYou: Bool) {
|
||||||
self.canUnmute = canUnmute
|
self.canUnmute = canUnmute
|
||||||
|
self.mutedByYou = mutedByYou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,19 +567,22 @@ public final class GroupCallParticipantsContext {
|
|||||||
public var joinTimestamp: Int32
|
public var joinTimestamp: Int32
|
||||||
public var activityTimestamp: Double?
|
public var activityTimestamp: Double?
|
||||||
public var muteState: MuteState?
|
public var muteState: MuteState?
|
||||||
|
public var volume: Int32?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
ssrc: UInt32,
|
ssrc: UInt32,
|
||||||
joinTimestamp: Int32,
|
joinTimestamp: Int32,
|
||||||
activityTimestamp: Double?,
|
activityTimestamp: Double?,
|
||||||
muteState: MuteState?
|
muteState: MuteState?,
|
||||||
|
volume: Int32?
|
||||||
) {
|
) {
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.ssrc = ssrc
|
self.ssrc = ssrc
|
||||||
self.joinTimestamp = joinTimestamp
|
self.joinTimestamp = joinTimestamp
|
||||||
self.activityTimestamp = activityTimestamp
|
self.activityTimestamp = activityTimestamp
|
||||||
self.muteState = muteState
|
self.muteState = muteState
|
||||||
|
self.volume = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Participant, rhs: Participant) -> Bool {
|
public static func ==(lhs: Participant, rhs: Participant) -> Bool {
|
||||||
@ -586,6 +601,9 @@ public final class GroupCallParticipantsContext {
|
|||||||
if lhs.muteState != rhs.muteState {
|
if lhs.muteState != rhs.muteState {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.volume != rhs.volume {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,12 +644,16 @@ public final class GroupCallParticipantsContext {
|
|||||||
private struct OverlayState: Equatable {
|
private struct OverlayState: Equatable {
|
||||||
struct MuteStateChange: Equatable {
|
struct MuteStateChange: Equatable {
|
||||||
var state: Participant.MuteState?
|
var state: Participant.MuteState?
|
||||||
|
var volume: Int32?
|
||||||
var disposable: Disposable
|
var disposable: Disposable
|
||||||
|
|
||||||
static func ==(lhs: MuteStateChange, rhs: MuteStateChange) -> Bool {
|
static func ==(lhs: MuteStateChange, rhs: MuteStateChange) -> Bool {
|
||||||
if lhs.state != rhs.state {
|
if lhs.state != rhs.state {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.volume != rhs.volume {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.disposable !== rhs.disposable {
|
if lhs.disposable !== rhs.disposable {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -669,6 +691,7 @@ public final class GroupCallParticipantsContext {
|
|||||||
public var activityTimestamp: Double?
|
public var activityTimestamp: Double?
|
||||||
public var muteState: Participant.MuteState?
|
public var muteState: Participant.MuteState?
|
||||||
public var participationStatusChange: ParticipationStatusChange
|
public var participationStatusChange: ParticipationStatusChange
|
||||||
|
public var volume: Int32?
|
||||||
}
|
}
|
||||||
|
|
||||||
public var participantUpdates: [ParticipantUpdate]
|
public var participantUpdates: [ParticipantUpdate]
|
||||||
@ -716,6 +739,7 @@ public final class GroupCallParticipantsContext {
|
|||||||
for i in 0 ..< publicState.participants.count {
|
for i in 0 ..< publicState.participants.count {
|
||||||
if let pendingMuteState = state.overlayState.pendingMuteStateChanges[publicState.participants[i].peer.id] {
|
if let pendingMuteState = state.overlayState.pendingMuteStateChanges[publicState.participants[i].peer.id] {
|
||||||
publicState.participants[i].muteState = pendingMuteState.state
|
publicState.participants[i].muteState = pendingMuteState.state
|
||||||
|
publicState.participants[i].volume = pendingMuteState.volume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return publicState
|
return publicState
|
||||||
@ -1009,7 +1033,8 @@ public final class GroupCallParticipantsContext {
|
|||||||
ssrc: participantUpdate.ssrc,
|
ssrc: participantUpdate.ssrc,
|
||||||
joinTimestamp: participantUpdate.joinTimestamp,
|
joinTimestamp: participantUpdate.joinTimestamp,
|
||||||
activityTimestamp: activityTimestamp,
|
activityTimestamp: activityTimestamp,
|
||||||
muteState: participantUpdate.muteState
|
muteState: participantUpdate.muteState,
|
||||||
|
volume: participantUpdate.volume
|
||||||
)
|
)
|
||||||
updatedParticipants.append(participant)
|
updatedParticipants.append(participant)
|
||||||
}
|
}
|
||||||
@ -1067,7 +1092,7 @@ public final class GroupCallParticipantsContext {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateMuteState(peerId: PeerId, muteState: Participant.MuteState?) {
|
public func updateMuteState(peerId: PeerId, muteState: Participant.MuteState?, volume: Int32?) {
|
||||||
if let current = self.stateValue.overlayState.pendingMuteStateChanges[peerId] {
|
if let current = self.stateValue.overlayState.pendingMuteStateChanges[peerId] {
|
||||||
if current.state == muteState {
|
if current.state == muteState {
|
||||||
return
|
return
|
||||||
@ -1078,7 +1103,7 @@ public final class GroupCallParticipantsContext {
|
|||||||
|
|
||||||
for participant in self.stateValue.state.participants {
|
for participant in self.stateValue.state.participants {
|
||||||
if participant.peer.id == peerId {
|
if participant.peer.id == peerId {
|
||||||
if participant.muteState == muteState {
|
if participant.muteState == muteState && participant.volume == volume {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1087,6 +1112,7 @@ public final class GroupCallParticipantsContext {
|
|||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
self.stateValue.overlayState.pendingMuteStateChanges[peerId] = OverlayState.MuteStateChange(
|
self.stateValue.overlayState.pendingMuteStateChanges[peerId] = OverlayState.MuteStateChange(
|
||||||
state: muteState,
|
state: muteState,
|
||||||
|
volume: volume,
|
||||||
disposable: disposable
|
disposable: disposable
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1102,11 +1128,13 @@ public final class GroupCallParticipantsContext {
|
|||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if let muteState = muteState, (!muteState.canUnmute || peerId == account.peerId) {
|
if let muteState = muteState, (!muteState.canUnmute || peerId == account.peerId || muteState.mutedByYou) {
|
||||||
flags |= 1 << 0
|
flags |= 1 << 0
|
||||||
|
} else if let _ = volume {
|
||||||
|
flags |= 1 << 1
|
||||||
}
|
}
|
||||||
|
|
||||||
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, volume: volume))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -1152,7 +1180,6 @@ public final class GroupCallParticipantsContext {
|
|||||||
}
|
}
|
||||||
self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted
|
self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted
|
||||||
|
|
||||||
|
|
||||||
self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), joinMuted: isMuted ? .boolTrue : .boolFalse))
|
self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), joinMuted: isMuted ? .boolTrue : .boolFalse))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] updates in
|
|> deliverOnMainQueue).start(next: { [weak self] updates in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -1166,13 +1193,17 @@ public final class GroupCallParticipantsContext {
|
|||||||
extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
|
extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
|
||||||
init(_ apiParticipant: Api.GroupCallParticipant) {
|
init(_ apiParticipant: Api.GroupCallParticipant) {
|
||||||
switch apiParticipant {
|
switch apiParticipant {
|
||||||
case let .groupCallParticipant(flags, userId, date, activeDate, source):
|
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
|
||||||
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)
|
||||||
|
let muted = (flags & (1 << 0)) != 0
|
||||||
|
let mutedByYou = (flags & (1 << 9)) != 0
|
||||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||||
if (flags & (1 << 0)) != 0 {
|
if muted {
|
||||||
let canUnmute = (flags & (1 << 2)) != 0
|
let canUnmute = (flags & (1 << 2)) != 0
|
||||||
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
|
||||||
|
} else if mutedByYou {
|
||||||
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
|
||||||
}
|
}
|
||||||
let isRemoved = (flags & (1 << 1)) != 0
|
let isRemoved = (flags & (1 << 1)) != 0
|
||||||
let justJoined = (flags & (1 << 4)) != 0
|
let justJoined = (flags & (1 << 4)) != 0
|
||||||
@ -1192,7 +1223,8 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
|
|||||||
joinTimestamp: date,
|
joinTimestamp: date,
|
||||||
activityTimestamp: activeDate.flatMap(Double.init),
|
activityTimestamp: activeDate.flatMap(Double.init),
|
||||||
muteState: muteState,
|
muteState: muteState,
|
||||||
participationStatusChange: participationStatusChange
|
participationStatusChange: participationStatusChange,
|
||||||
|
volume: volume
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1203,13 +1235,17 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
|
|||||||
var participantUpdates: [GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate] = []
|
var participantUpdates: [GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate] = []
|
||||||
for participant in participants {
|
for participant in participants {
|
||||||
switch participant {
|
switch participant {
|
||||||
case let .groupCallParticipant(flags, userId, date, activeDate, source):
|
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
|
||||||
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)
|
||||||
|
let muted = (flags & (1 << 0)) != 0
|
||||||
|
let mutedByYou = (flags & (1 << 9)) != 0
|
||||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||||
if (flags & (1 << 0)) != 0 {
|
if muted {
|
||||||
let canUnmute = (flags & (1 << 2)) != 0
|
let canUnmute = (flags & (1 << 2)) != 0
|
||||||
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
|
||||||
|
} else if mutedByYou {
|
||||||
|
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
|
||||||
}
|
}
|
||||||
let isRemoved = (flags & (1 << 1)) != 0
|
let isRemoved = (flags & (1 << 1)) != 0
|
||||||
let justJoined = (flags & (1 << 4)) != 0
|
let justJoined = (flags & (1 << 4)) != 0
|
||||||
@ -1229,7 +1265,8 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
|
|||||||
joinTimestamp: date,
|
joinTimestamp: date,
|
||||||
activityTimestamp: activeDate.flatMap(Double.init),
|
activityTimestamp: activeDate.flatMap(Double.init),
|
||||||
muteState: muteState,
|
muteState: muteState,
|
||||||
participationStatusChange: participationStatusChange
|
participationStatusChange: participationStatusChange,
|
||||||
|
volume: volume
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,26 +25,6 @@ public final class FoundStickerItem: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MutableCollection {
|
|
||||||
mutating func shuffle() {
|
|
||||||
let c = count
|
|
||||||
guard c > 1 else { return }
|
|
||||||
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
|
|
||||||
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
|
|
||||||
let i = index(firstUnshuffled, offsetBy: d)
|
|
||||||
swapAt(firstUnshuffled, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Sequence {
|
|
||||||
func shuffled() -> [Element] {
|
|
||||||
var result = Array(self)
|
|
||||||
result.shuffle()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200)
|
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200)
|
||||||
|
|
||||||
public struct SearchStickersScope: OptionSet {
|
public struct SearchStickersScope: OptionSet {
|
||||||
@ -97,8 +77,8 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
|
|||||||
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) {
|
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) {
|
||||||
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
|
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
|
||||||
if !currentItems.contains(file.fileId) {
|
if !currentItems.contains(file.fileId) {
|
||||||
for case let .Sticker(sticker) in file.attributes {
|
for case let .Sticker(displayText, _, _) in file.attributes {
|
||||||
if sticker.displayText.hasPrefix(query) {
|
if displayText.hasPrefix(query) {
|
||||||
matchingRecentItemsIds.insert(file.fileId)
|
matchingRecentItemsIds.insert(file.fileId)
|
||||||
}
|
}
|
||||||
recentItemsIds.insert(file.fileId)
|
recentItemsIds.insert(file.fileId)
|
||||||
|
|||||||
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 122
|
return 123
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
},
|
},
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"provides-namespace" : true
|
"provides-namespace" : true
|
||||||
|
|||||||
12
submodules/TelegramUI/Images.xcassets/Peer Info/ButtonVoiceChat.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/ButtonVoiceChat.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "profile_voice.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/ButtonVoiceChat.imageset/profile_voice.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/ButtonVoiceChat.imageset/profile_voice.pdf
vendored
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
},
|
},
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"provides-namespace" : true
|
"provides-namespace" : true
|
||||||
|
|||||||
Binary file not shown.
@ -504,7 +504,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .groupPhoneCall(callId, accessHash, _), let .inviteToGroupPhoneCall(callId, accessHash, _):
|
case .groupPhoneCall, .inviteToGroupPhoneCall:
|
||||||
if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall {
|
if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall {
|
||||||
strongSelf.context.joinGroupCall(peerId: message.id.peerId, activeCall: CachedChannelData.ActiveCall(id: activeCall.id, accessHash: activeCall.accessHash))
|
strongSelf.context.joinGroupCall(peerId: message.id.peerId, activeCall: CachedChannelData.ActiveCall(id: activeCall.id, accessHash: activeCall.accessHash))
|
||||||
} else {
|
} else {
|
||||||
@ -556,7 +556,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
|
||||||
}
|
}
|
||||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
}, completed: { [weak self] in
|
}, completed: {
|
||||||
dismissStatus?()
|
dismissStatus?()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -708,7 +708,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
|
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
|
||||||
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, snapshots: snapshots, transitionCompletion: {
|
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: "", snapshots: snapshots, transitionCompletion: {
|
||||||
transitionCompletion()
|
transitionCompletion()
|
||||||
}, presentStickers: { [weak self] completion in
|
}, presentStickers: { [weak self] completion in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -854,9 +854,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}, navigateToMessage: { [weak self] fromId, id in
|
}, navigateToMessage: { [weak self] fromId, id in
|
||||||
self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId)
|
self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId)
|
||||||
}, navigateToMessageStandalone: { [weak self] id in
|
}, navigateToMessageStandalone: { [weak self] id in
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self?.navigateToMessage(from: nil, to: .id(id), forceInCurrentChat: false)
|
self?.navigateToMessage(from: nil, to: .id(id), forceInCurrentChat: false)
|
||||||
}, tapMessage: nil, clickThroughMessage: { [weak self] in
|
}, tapMessage: nil, clickThroughMessage: { [weak self] in
|
||||||
self?.chatDisplayNode.dismissInput()
|
self?.chatDisplayNode.dismissInput()
|
||||||
|
|||||||
@ -65,6 +65,26 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation
|
|||||||
return updates
|
return updates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct StickersSearchConfiguration {
|
||||||
|
static var defaultValue: StickersSearchConfiguration {
|
||||||
|
return StickersSearchConfiguration(disableLocalSuggestions: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public let disableLocalSuggestions: Bool
|
||||||
|
|
||||||
|
fileprivate init(disableLocalSuggestions: Bool) {
|
||||||
|
self.disableLocalSuggestions = disableLocalSuggestions
|
||||||
|
}
|
||||||
|
|
||||||
|
static func with(appConfiguration: AppConfiguration) -> StickersSearchConfiguration {
|
||||||
|
if let data = appConfiguration.data, let suggestOnlyApi = data["stickers_emoji_suggest_only_api"] as? Bool {
|
||||||
|
return StickersSearchConfiguration(disableLocalSuggestions: suggestOnlyApi)
|
||||||
|
} else {
|
||||||
|
return .defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer, chatLocation: ChatLocation, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?, requestBotLocationStatus: @escaping (PeerId) -> Void) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
|
private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer, chatLocation: ChatLocation, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?, requestBotLocationStatus: @escaping (PeerId) -> Void) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
|
||||||
switch inputQuery {
|
switch inputQuery {
|
||||||
case let .emoji(query):
|
case let .emoji(query):
|
||||||
@ -79,18 +99,30 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
} else {
|
} else {
|
||||||
signal = .single({ _ in return .stickers([]) })
|
signal = .single({ _ in return .stickers([]) })
|
||||||
}
|
}
|
||||||
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in
|
|
||||||
|
let stickerConfiguration = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||||
|
|> map { preferencesView -> StickersSearchConfiguration in
|
||||||
|
let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
|
||||||
|
return StickersSearchConfiguration.with(appConfiguration: appConfiguration)
|
||||||
|
}
|
||||||
|
let stickerSettings = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in
|
||||||
let stickerSettings: StickerSettings = (transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings) as? StickerSettings) ?? .defaultSettings
|
let stickerSettings: StickerSettings = (transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings) as? StickerSettings) ?? .defaultSettings
|
||||||
return stickerSettings
|
return stickerSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = combineLatest(stickerConfiguration, stickerSettings)
|
||||||
|> castError(ChatContextQueryError.self)
|
|> castError(ChatContextQueryError.self)
|
||||||
|> mapToSignal { stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
|
|> mapToSignal { stickerConfiguration, stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
|
||||||
let scope: SearchStickersScope
|
let scope: SearchStickersScope
|
||||||
switch stickerSettings.emojiStickerSuggestionMode {
|
switch stickerSettings.emojiStickerSuggestionMode {
|
||||||
case .none:
|
case .none:
|
||||||
scope = []
|
scope = []
|
||||||
case .all:
|
case .all:
|
||||||
scope = [.installed, .remote]
|
if stickerConfiguration.disableLocalSuggestions {
|
||||||
|
scope = [.remote]
|
||||||
|
} else {
|
||||||
|
scope = [.installed, .remote]
|
||||||
|
}
|
||||||
case .installed:
|
case .installed:
|
||||||
scope = [.installed]
|
scope = [.installed]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -936,6 +936,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
|||||||
var displayLeave = !channel.flags.contains(.isCreator)
|
var displayLeave = !channel.flags.contains(.isCreator)
|
||||||
var canViewStats = false
|
var canViewStats = false
|
||||||
var hasDiscussion = false
|
var hasDiscussion = false
|
||||||
|
var hasVoiceChat = false
|
||||||
if let cachedChannelData = cachedData as? CachedChannelData {
|
if let cachedChannelData = cachedData as? CachedChannelData {
|
||||||
canViewStats = cachedChannelData.flags.contains(.canViewStats)
|
canViewStats = cachedChannelData.flags.contains(.canViewStats)
|
||||||
}
|
}
|
||||||
@ -952,6 +953,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
|||||||
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
|
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
|
||||||
result.append(.addMember)
|
result.append(.addMember)
|
||||||
}
|
}
|
||||||
|
if channel.flags.contains(.hasVoiceChat) {
|
||||||
|
hasVoiceChat = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
switch channel.participationStatus {
|
switch channel.participationStatus {
|
||||||
case .member:
|
case .member:
|
||||||
@ -963,6 +967,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
|||||||
displayLeave = false
|
displayLeave = false
|
||||||
}
|
}
|
||||||
result.append(.mute)
|
result.append(.mute)
|
||||||
|
if hasVoiceChat {
|
||||||
|
result.append(.voiceChat)
|
||||||
|
}
|
||||||
if hasDiscussion {
|
if hasDiscussion {
|
||||||
result.append(.discussion)
|
result.append(.discussion)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ enum PeerInfoHeaderButtonKey: Hashable {
|
|||||||
case discussion
|
case discussion
|
||||||
case call
|
case call
|
||||||
case videoCall
|
case videoCall
|
||||||
|
case voiceChat
|
||||||
case mute
|
case mute
|
||||||
case more
|
case more
|
||||||
case addMember
|
case addMember
|
||||||
@ -36,6 +37,7 @@ enum PeerInfoHeaderButtonIcon {
|
|||||||
case message
|
case message
|
||||||
case call
|
case call
|
||||||
case videoCall
|
case videoCall
|
||||||
|
case voiceChat
|
||||||
case mute
|
case mute
|
||||||
case unmute
|
case unmute
|
||||||
case more
|
case more
|
||||||
@ -112,6 +114,8 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
imageName = "Peer Info/ButtonCall"
|
imageName = "Peer Info/ButtonCall"
|
||||||
case .videoCall:
|
case .videoCall:
|
||||||
imageName = "Peer Info/ButtonVideo"
|
imageName = "Peer Info/ButtonVideo"
|
||||||
|
case .voiceChat:
|
||||||
|
imageName = "Peer Info/ButtonVoiceChat"
|
||||||
case .mute:
|
case .mute:
|
||||||
imageName = "Peer Info/ButtonMute"
|
imageName = "Peer Info/ButtonMute"
|
||||||
case .unmute:
|
case .unmute:
|
||||||
@ -3273,6 +3277,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
case .videoCall:
|
case .videoCall:
|
||||||
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
|
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
|
||||||
buttonIcon = .videoCall
|
buttonIcon = .videoCall
|
||||||
|
case .voiceChat:
|
||||||
|
buttonText = presentationData.strings.PeerInfo_ButtonVoiceChat
|
||||||
|
buttonIcon = .voiceChat
|
||||||
case .mute:
|
case .mute:
|
||||||
if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState {
|
if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState {
|
||||||
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
|
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
|
||||||
|
|||||||
@ -2850,6 +2850,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
self.requestCall(isVideo: false)
|
self.requestCall(isVideo: false)
|
||||||
case .videoCall:
|
case .videoCall:
|
||||||
self.requestCall(isVideo: true)
|
self.requestCall(isVideo: true)
|
||||||
|
case .voiceChat:
|
||||||
|
if let cachedData = self.data?.cachedData as? CachedChannelData, let activeCall = cachedData.activeCall {
|
||||||
|
self.context.joinGroupCall(peerId: self.peerId, activeCall: activeCall)
|
||||||
|
}
|
||||||
case .mute:
|
case .mute:
|
||||||
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
|
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
|
||||||
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start()
|
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user