mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
8102308d35
@ -5810,3 +5810,9 @@ Sorry for the inconvenience.";
|
||||
"Call.VoiceOver.VideoCallMissed" = "Missed Video Call";
|
||||
"Call.VoiceOver.VoiceCallCanceled" = "Cancelled Voice 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 setIsMuted(action: PresentationGroupCallMuteAction)
|
||||
func updateDefaultParticipantsAreMuted(isMuted: Bool)
|
||||
func setVolume(peerId: PeerId, volume: Double)
|
||||
func setVolume(peerId: PeerId, volume: Int32, sync: Bool)
|
||||
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
||||
|
||||
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
||||
|
@ -89,7 +89,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
itemNodes.append(.itemSeparator(separatorNode))
|
||||
}
|
||||
case let .custom(item):
|
||||
case let .custom(item, _):
|
||||
itemNodes.append(.custom(item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)))
|
||||
if i != items.count - 1, case .action = items[i + 1] {
|
||||
let separatorNode = ASDisplayNode()
|
||||
@ -425,6 +425,7 @@ private final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
final class ContextActionsContainerNode: ASDisplayNode {
|
||||
private let blurBackground: Bool
|
||||
private let shadowNode: ASImageNode
|
||||
private let additionalActionsNode: InnerActionsContainerNode?
|
||||
private let actionsNode: InnerActionsContainerNode
|
||||
private let textSelectionTipNode: InnerTextSelectionTipContainerNode?
|
||||
private let scrollNode: ASScrollNode
|
||||
@ -446,6 +447,14 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
self.shadowNode.contentMode = .scaleToFill
|
||||
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)
|
||||
if displayTextSelectionTip {
|
||||
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData)
|
||||
@ -466,6 +475,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.shadowNode)
|
||||
self.additionalActionsNode.flatMap(self.scrollNode.addSubnode)
|
||||
self.scrollNode.addSubnode(self.actionsNode)
|
||||
self.textSelectionTipNode.flatMap(self.scrollNode.addSubnode)
|
||||
self.addSubnode(self.scrollNode)
|
||||
@ -477,13 +487,24 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
widthClass = .regular
|
||||
}
|
||||
|
||||
var contentSize = CGSize()
|
||||
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))
|
||||
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)
|
||||
|
||||
if let textSelectionTipNode = self.textSelectionTipNode {
|
||||
|
@ -84,7 +84,7 @@ public protocol ContextMenuCustomItem {
|
||||
|
||||
public enum ContextMenuItem {
|
||||
case action(ContextMenuActionItem)
|
||||
case custom(ContextMenuCustomItem)
|
||||
case custom(ContextMenuCustomItem, Bool)
|
||||
case separator
|
||||
}
|
||||
|
||||
|
@ -99,8 +99,11 @@ private final class NavigationControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
override func accessibilityPerformEscape() -> Bool {
|
||||
let _ = self.controller?.popViewController(animated: true)
|
||||
return true
|
||||
if let controller = self.controller, controller.viewControllers.count > 1 {
|
||||
let _ = self.controller?.popViewController(animated: true)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -793,7 +793,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if let strongSelf = self, !isAnimated {
|
||||
videoNode?.seek(0.0)
|
||||
|
||||
if strongSelf.actionAtEnd == .stop {
|
||||
if strongSelf.actionAtEnd == .stop && strongSelf.isCentral {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
strongSelf.controlsTimer?.invalidate()
|
||||
strongSelf.controlsTimer = nil
|
||||
@ -900,6 +900,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.controlsTimer?.invalidate()
|
||||
self.controlsTimer = nil
|
||||
|
||||
self.dismissOnOrientationChange = false
|
||||
if videoNode.ownsContentNode {
|
||||
videoNode.pause()
|
||||
|
@ -164,6 +164,10 @@ public class StickerShimmerEffectNode: ASDisplayNode {
|
||||
self.addSubnode(self.foregroundNode)
|
||||
}
|
||||
|
||||
public var isEmpty: Bool {
|
||||
return self.currentData == nil
|
||||
}
|
||||
|
||||
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1511503333] = { return Api.InputEncryptedFile.parse_inputEncryptedFile($0) }
|
||||
dict[767652808] = { return Api.InputEncryptedFile.parse_inputEncryptedFileBigUploaded($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[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) }
|
||||
dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) }
|
||||
@ -629,9 +629,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1670052855] = { return Api.FoundGif.parse_foundGifCached($0) }
|
||||
dict[537022650] = { return Api.User.parse_userEmpty($0) }
|
||||
dict[-1820043071] = { return Api.User.parse_user($0) }
|
||||
dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) }
|
||||
dict[1487813065] = { return Api.Message.parse_message($0) }
|
||||
dict[678405636] = { return Api.Message.parse_messageService($0) }
|
||||
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
||||
dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) }
|
||||
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
|
||||
dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) }
|
||||
@ -652,7 +652,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
|
||||
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
|
||||
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) }
|
||||
dict[-905587442] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsAndroidPay($0) }
|
||||
dict[-1966921727] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsGooglePay($0) }
|
||||
dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
|
||||
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
|
||||
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }
|
||||
|
@ -5438,27 +5438,29 @@ public extension Api {
|
||||
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
buffer.appendInt32(1454409673)
|
||||
buffer.appendInt32(-1199443157)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(userId, 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source):
|
||||
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", 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), ("volume", volume), ("mutedCnt", mutedCnt)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -5473,13 +5475,19 @@ public extension Api {
|
||||
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
|
||||
var _5: Int32?
|
||||
_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 _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!)
|
||||
let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil
|
||||
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 {
|
||||
return nil
|
||||
@ -18144,18 +18152,12 @@ public extension Api {
|
||||
|
||||
}
|
||||
public enum Message: TypeConstructorDescription {
|
||||
case messageEmpty(id: Int32)
|
||||
case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?)
|
||||
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction)
|
||||
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .messageEmpty(let id):
|
||||
if boxed {
|
||||
buffer.appendInt32(-2082087340)
|
||||
}
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason):
|
||||
if boxed {
|
||||
buffer.appendInt32(1487813065)
|
||||
@ -18200,31 +18202,28 @@ public extension Api {
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
action.serialize(buffer, true)
|
||||
break
|
||||
case .messageEmpty(let flags, let id, let peerId):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1868117372)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .messageEmpty(let id):
|
||||
return ("messageEmpty", [("id", id)])
|
||||
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason):
|
||||
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)])
|
||||
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action):
|
||||
return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("replyTo", replyTo), ("date", date), ("action", action)])
|
||||
case .messageEmpty(let flags, let id, let peerId):
|
||||
return ("messageEmpty", [("flags", flags), ("id", id), ("peerId", peerId)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_messageEmpty(_ reader: BufferReader) -> Message? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.Message.messageEmpty(id: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_message(_ reader: BufferReader) -> Message? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
@ -18345,6 +18344,25 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_messageEmpty(_ reader: BufferReader) -> Message? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Api.Peer?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.Message.messageEmpty(flags: _1!, id: _2!, peerId: _3)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsGroupTopInviter: TypeConstructorDescription {
|
||||
@ -18903,7 +18921,7 @@ public extension Api {
|
||||
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
|
||||
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
|
||||
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
|
||||
case inputPaymentCredentialsAndroidPay(paymentToken: Api.DataJSON, googleTransactionId: String)
|
||||
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -18927,12 +18945,11 @@ public extension Api {
|
||||
}
|
||||
paymentData.serialize(buffer, true)
|
||||
break
|
||||
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
|
||||
case .inputPaymentCredentialsGooglePay(let paymentToken):
|
||||
if boxed {
|
||||
buffer.appendInt32(-905587442)
|
||||
buffer.appendInt32(-1966921727)
|
||||
}
|
||||
paymentToken.serialize(buffer, true)
|
||||
serializeString(googleTransactionId, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -18945,8 +18962,8 @@ public extension Api {
|
||||
return ("inputPaymentCredentials", [("flags", flags), ("data", data)])
|
||||
case .inputPaymentCredentialsApplePay(let paymentData):
|
||||
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData)])
|
||||
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
|
||||
return ("inputPaymentCredentialsAndroidPay", [("paymentToken", paymentToken), ("googleTransactionId", googleTransactionId)])
|
||||
case .inputPaymentCredentialsGooglePay(let paymentToken):
|
||||
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -18993,17 +19010,14 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputPaymentCredentialsAndroidPay(_ reader: BufferReader) -> InputPaymentCredentials? {
|
||||
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
|
||||
var _1: Api.DataJSON?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
|
||||
}
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.InputPaymentCredentials.inputPaymentCredentialsAndroidPay(paymentToken: _1!, googleTransactionId: _2!)
|
||||
if _c1 {
|
||||
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
|
||||
}
|
||||
else {
|
||||
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>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(2067345760)
|
||||
@ -7406,6 +7390,23 @@ public extension Api {
|
||||
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
|
||||
}
|
||||
|
||||
let panelData = currentGroupCall != nil ? nil : availableState
|
||||
let panelData = currentGroupCall != nil || availableState?.participantCount == 0 ? nil : availableState
|
||||
|
||||
let wasEmpty = strongSelf.groupCallPanelData == nil
|
||||
strongSelf.groupCallPanelData = panelData
|
||||
|
@ -171,7 +171,7 @@ private extension PresentationGroupCallState {
|
||||
networkState: .connecting,
|
||||
canManageCall: false,
|
||||
adminIds: Set(),
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true),
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
defaultParticipantMuteState: nil
|
||||
)
|
||||
}
|
||||
@ -669,7 +669,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
ssrc: 0,
|
||||
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
||||
activityTimestamp: nil,
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
volume: nil
|
||||
))
|
||||
participants.sort()
|
||||
}
|
||||
@ -1042,7 +1043,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.stateValue.muteState = muteState
|
||||
} else if let currentMuteState = strongSelf.stateValue.muteState, !currentMuteState.canUnmute {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -1229,16 +1230,19 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.callContext?.setIsMuted(isEffectivelyMuted)
|
||||
|
||||
if isVisuallyMuted {
|
||||
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
|
||||
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false)
|
||||
} else {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1315,6 +1319,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
public func updateMuteState(peerId: PeerId, isMuted: Bool) {
|
||||
let canThenUnmute: Bool
|
||||
if isMuted {
|
||||
var mutedByYou = false
|
||||
if peerId == self.accountContext.account.peerId {
|
||||
canThenUnmute = true
|
||||
} else if self.stateValue.canManageCall {
|
||||
@ -1326,14 +1331,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
} else if self.stateValue.adminIds.contains(self.accountContext.account.peerId) {
|
||||
canThenUnmute = true
|
||||
} else {
|
||||
mutedByYou = 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 {
|
||||
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 {
|
||||
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 revealed: Bool?
|
||||
var canManageCall: Bool
|
||||
var volume: Int32?
|
||||
|
||||
var stableId: PeerId {
|
||||
return self.peer.id
|
||||
@ -236,6 +237,9 @@ public final class VoiceChatController: ViewController {
|
||||
if lhs.canManageCall != rhs.canManageCall {
|
||||
return false
|
||||
}
|
||||
if lhs.volume != rhs.volume {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -333,17 +337,30 @@ public final class VoiceChatController: ViewController {
|
||||
let icon: VoiceChatParticipantItem.Icon
|
||||
switch peerEntry.state {
|
||||
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
|
||||
if let muteState = peerEntry.muteState, !muteState.canUnmute {
|
||||
if let muteState = peerEntry.muteState, !muteState.canUnmute || muteState.mutedByYou {
|
||||
microphoneColor = UIColor(rgb: 0xff3b30)
|
||||
} else {
|
||||
microphoneColor = UIColor(rgb: 0x979797)
|
||||
}
|
||||
icon = .microphone(peerEntry.muteState != nil, microphoneColor)
|
||||
case .speaking:
|
||||
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
|
||||
icon = .microphone(false, UIColor(rgb: 0x34c759))
|
||||
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||
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:
|
||||
text = .text(presentationData.strings.VoiceChat_StatusInvited, .generic)
|
||||
icon = .invite(true)
|
||||
@ -355,7 +372,7 @@ public final class VoiceChatController: ViewController {
|
||||
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
|
||||
}, action: {
|
||||
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)
|
||||
})
|
||||
}
|
||||
@ -387,6 +404,7 @@ public final class VoiceChatController: ViewController {
|
||||
private let topPanelNode: ASDisplayNode
|
||||
private let topPanelEdgeNode: ASDisplayNode
|
||||
private let topPanelBackgroundNode: ASDisplayNode
|
||||
private let recButton: VoiceChatHeaderButton
|
||||
private let optionsButton: VoiceChatHeaderButton
|
||||
private let closeButton: VoiceChatHeaderButton
|
||||
private let topCornersNode: ASImageNode
|
||||
@ -493,6 +511,9 @@ public final class VoiceChatController: ViewController {
|
||||
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.setImage(optionsButtonImage(dark: false))
|
||||
self.closeButton = VoiceChatHeaderButton()
|
||||
@ -792,6 +813,16 @@ public final class VoiceChatController: ViewController {
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
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 callState.adminIds.contains(peer.id) {
|
||||
if let _ = entry.muteState {
|
||||
@ -832,7 +863,42 @@ 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)) {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in
|
||||
@ -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 {
|
||||
@ -896,6 +958,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.topPanelNode.addSubnode(self.topPanelEdgeNode)
|
||||
self.topPanelNode.addSubnode(self.topPanelBackgroundNode)
|
||||
self.topPanelNode.addSubnode(self.titleNode)
|
||||
self.topPanelNode.addSubnode(self.recButton)
|
||||
self.topPanelNode.addSubnode(self.optionsButton)
|
||||
self.topPanelNode.addSubnode(self.closeButton)
|
||||
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.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
|
||||
}
|
||||
|
||||
@ -1123,6 +1186,15 @@ public final class VoiceChatController: ViewController {
|
||||
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 {
|
||||
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)
|
||||
@ -1152,12 +1224,22 @@ public final class VoiceChatController: ViewController {
|
||||
strongSelf.controller?.present(alert, in: .window(.root))
|
||||
})))
|
||||
}
|
||||
|
||||
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: strongOptionsButton.extractedContainerNode, keepInPlace: false, blurBackground: false)), 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: optionsButton.extractedContainerNode, keepInPlace: false, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
self.recButton.contextAction = self.optionsButton.contextAction
|
||||
|
||||
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.actionButtonColorDisposable = (self.actionButton.outerColor
|
||||
@ -1566,6 +1648,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
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.closeButton.setImage(closeButtonImage(dark: isFullscreen), animated: transition.isAnimated)
|
||||
|
||||
@ -1670,6 +1753,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
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.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.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,
|
||||
state: memberState,
|
||||
muteState: memberMuteState,
|
||||
canManageCall: callState?.canManageCall ?? false
|
||||
canManageCall: self.callState?.canManageCall ?? false,
|
||||
volume: member.volume
|
||||
)))
|
||||
index += 1
|
||||
}
|
||||
@ -2030,8 +2115,9 @@ public final class VoiceChatController: ViewController {
|
||||
presence: nil,
|
||||
activityTimestamp: Int32.max - 1 - index,
|
||||
state: .listening,
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true),
|
||||
canManageCall: callState?.canManageCall ?? false
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
canManageCall: self.callState?.canManageCall ?? false,
|
||||
volume: nil
|
||||
)), at: 1)
|
||||
}
|
||||
|
||||
@ -2047,7 +2133,8 @@ public final class VoiceChatController: ViewController {
|
||||
activityTimestamp: Int32.max - 1 - index,
|
||||
state: .invited,
|
||||
muteState: nil,
|
||||
canManageCall: false
|
||||
canManageCall: false,
|
||||
volume: nil
|
||||
)))
|
||||
index += 1
|
||||
}
|
||||
|
@ -3,6 +3,15 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
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? {
|
||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
@ -45,13 +54,30 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
init() {
|
||||
var textNode: ImmediateTextNode?
|
||||
var dotNode: ASImageNode?
|
||||
|
||||
init(rec: Bool = false) {
|
||||
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.isGestureEnabled = false
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
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()
|
||||
|
||||
@ -60,6 +86,11 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
|
||||
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
|
||||
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||
return false
|
||||
@ -75,7 +106,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
|
||||
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.contentRect = self.containerNode.bounds
|
||||
self.iconNode.frame = self.containerNode.bounds
|
||||
@ -96,10 +127,29 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
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 {
|
||||
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() {
|
||||
|
@ -8,9 +8,9 @@ import ContextUI
|
||||
|
||||
final class VoiceChatVolumeContextItem: ContextMenuCustomItem {
|
||||
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.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()
|
||||
|
||||
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.value = value
|
||||
self.valueChanged = valueChanged
|
||||
@ -169,7 +169,12 @@ private final class VoiceChatVolumeContextItemNode: ASDisplayNode, ContextMenuCu
|
||||
} else if self.value == 0.0 && previousValue != 0.0 {
|
||||
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
|
||||
default:
|
||||
break
|
||||
|
@ -88,23 +88,28 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
|
||||
|
||||
loop: for participant in participants {
|
||||
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 ssrc = UInt32(bitPattern: source)
|
||||
guard let peer = transaction.getPeer(peerId) else {
|
||||
continue loop
|
||||
}
|
||||
let muted = (flags & (1 << 0)) != 0
|
||||
let mutedByYou = (flags & (1 << 9)) != 0
|
||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
if muted {
|
||||
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(
|
||||
peer: peer,
|
||||
ssrc: ssrc,
|
||||
joinTimestamp: date,
|
||||
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 {
|
||||
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 ssrc = UInt32(bitPattern: source)
|
||||
guard let peer = transaction.getPeer(peerId) else {
|
||||
continue loop
|
||||
}
|
||||
let muted = (flags & (1 << 0)) != 0
|
||||
let mutedByYou = (flags & (1 << 9)) != 0
|
||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
if muted {
|
||||
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(
|
||||
peer: peer,
|
||||
ssrc: ssrc,
|
||||
joinTimestamp: date,
|
||||
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 MuteState: Equatable {
|
||||
public var canUnmute: Bool
|
||||
public var mutedByYou: Bool
|
||||
|
||||
public init(canUnmute: Bool) {
|
||||
public init(canUnmute: Bool, mutedByYou: Bool) {
|
||||
self.canUnmute = canUnmute
|
||||
self.mutedByYou = mutedByYou
|
||||
}
|
||||
}
|
||||
|
||||
@ -555,19 +567,22 @@ public final class GroupCallParticipantsContext {
|
||||
public var joinTimestamp: Int32
|
||||
public var activityTimestamp: Double?
|
||||
public var muteState: MuteState?
|
||||
public var volume: Int32?
|
||||
|
||||
public init(
|
||||
peer: Peer,
|
||||
ssrc: UInt32,
|
||||
joinTimestamp: Int32,
|
||||
activityTimestamp: Double?,
|
||||
muteState: MuteState?
|
||||
muteState: MuteState?,
|
||||
volume: Int32?
|
||||
) {
|
||||
self.peer = peer
|
||||
self.ssrc = ssrc
|
||||
self.joinTimestamp = joinTimestamp
|
||||
self.activityTimestamp = activityTimestamp
|
||||
self.muteState = muteState
|
||||
self.volume = volume
|
||||
}
|
||||
|
||||
public static func ==(lhs: Participant, rhs: Participant) -> Bool {
|
||||
@ -586,6 +601,9 @@ public final class GroupCallParticipantsContext {
|
||||
if lhs.muteState != rhs.muteState {
|
||||
return false
|
||||
}
|
||||
if lhs.volume != rhs.volume {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -626,12 +644,16 @@ public final class GroupCallParticipantsContext {
|
||||
private struct OverlayState: Equatable {
|
||||
struct MuteStateChange: Equatable {
|
||||
var state: Participant.MuteState?
|
||||
var volume: Int32?
|
||||
var disposable: Disposable
|
||||
|
||||
static func ==(lhs: MuteStateChange, rhs: MuteStateChange) -> Bool {
|
||||
if lhs.state != rhs.state {
|
||||
return false
|
||||
}
|
||||
if lhs.volume != rhs.volume {
|
||||
return false
|
||||
}
|
||||
if lhs.disposable !== rhs.disposable {
|
||||
return false
|
||||
}
|
||||
@ -669,6 +691,7 @@ public final class GroupCallParticipantsContext {
|
||||
public var activityTimestamp: Double?
|
||||
public var muteState: Participant.MuteState?
|
||||
public var participationStatusChange: ParticipationStatusChange
|
||||
public var volume: Int32?
|
||||
}
|
||||
|
||||
public var participantUpdates: [ParticipantUpdate]
|
||||
@ -716,6 +739,7 @@ public final class GroupCallParticipantsContext {
|
||||
for i in 0 ..< publicState.participants.count {
|
||||
if let pendingMuteState = state.overlayState.pendingMuteStateChanges[publicState.participants[i].peer.id] {
|
||||
publicState.participants[i].muteState = pendingMuteState.state
|
||||
publicState.participants[i].volume = pendingMuteState.volume
|
||||
}
|
||||
}
|
||||
return publicState
|
||||
@ -1009,7 +1033,8 @@ public final class GroupCallParticipantsContext {
|
||||
ssrc: participantUpdate.ssrc,
|
||||
joinTimestamp: participantUpdate.joinTimestamp,
|
||||
activityTimestamp: activityTimestamp,
|
||||
muteState: participantUpdate.muteState
|
||||
muteState: participantUpdate.muteState,
|
||||
volume: participantUpdate.volume
|
||||
)
|
||||
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 current.state == muteState {
|
||||
return
|
||||
@ -1078,7 +1103,7 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
for participant in self.stateValue.state.participants {
|
||||
if participant.peer.id == peerId {
|
||||
if participant.muteState == muteState {
|
||||
if participant.muteState == muteState && participant.volume == volume {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -1087,6 +1112,7 @@ public final class GroupCallParticipantsContext {
|
||||
let disposable = MetaDisposable()
|
||||
self.stateValue.overlayState.pendingMuteStateChanges[peerId] = OverlayState.MuteStateChange(
|
||||
state: muteState,
|
||||
volume: volume,
|
||||
disposable: disposable
|
||||
)
|
||||
|
||||
@ -1102,11 +1128,13 @@ public final class GroupCallParticipantsContext {
|
||||
return .single(nil)
|
||||
}
|
||||
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
|
||||
} 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)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
@ -1152,7 +1180,6 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
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))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] updates in
|
||||
guard let strongSelf = self else {
|
||||
@ -1166,13 +1193,17 @@ public final class GroupCallParticipantsContext {
|
||||
extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
|
||||
init(_ apiParticipant: Api.GroupCallParticipant) {
|
||||
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 ssrc = UInt32(bitPattern: source)
|
||||
let muted = (flags & (1 << 0)) != 0
|
||||
let mutedByYou = (flags & (1 << 9)) != 0
|
||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
if muted {
|
||||
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 justJoined = (flags & (1 << 4)) != 0
|
||||
@ -1192,7 +1223,8 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
|
||||
joinTimestamp: date,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
muteState: muteState,
|
||||
participationStatusChange: participationStatusChange
|
||||
participationStatusChange: participationStatusChange,
|
||||
volume: volume
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1203,13 +1235,17 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
|
||||
var participantUpdates: [GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate] = []
|
||||
for participant in participants {
|
||||
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 ssrc = UInt32(bitPattern: source)
|
||||
let muted = (flags & (1 << 0)) != 0
|
||||
let mutedByYou = (flags & (1 << 9)) != 0
|
||||
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
if muted {
|
||||
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 justJoined = (flags & (1 << 4)) != 0
|
||||
@ -1229,7 +1265,8 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
|
||||
joinTimestamp: date,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
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)
|
||||
|
||||
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) {
|
||||
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
|
||||
if !currentItems.contains(file.fileId) {
|
||||
for case let .Sticker(sticker) in file.attributes {
|
||||
if sticker.displayText.hasPrefix(query) {
|
||||
for case let .Sticker(displayText, _, _) in file.attributes {
|
||||
if displayText.hasPrefix(query) {
|
||||
matchingRecentItemsIds.insert(file.fileId)
|
||||
}
|
||||
recentItemsIds.insert(file.fileId)
|
||||
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 122
|
||||
return 123
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
@ -116,8 +116,12 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
||||
case let .message(message):
|
||||
let chatPeerId = message.peerId
|
||||
return chatPeerId.peerId
|
||||
case .messageEmpty:
|
||||
return nil
|
||||
case let .messageEmpty(_, id, peerId):
|
||||
if let peerId = peerId {
|
||||
return peerId.peerId
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let .messageService(flags, _, fromId, chatPeerId, _, _, _):
|
||||
return chatPeerId.peerId
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ extension Api.Message {
|
||||
switch self {
|
||||
case let .message(message):
|
||||
return message.id
|
||||
case let .messageEmpty(id):
|
||||
case let .messageEmpty(_, id, _):
|
||||
return id
|
||||
case let .messageService(_, id, _, _, _, _, _):
|
||||
return id
|
||||
@ -115,8 +115,12 @@ extension Api.Message {
|
||||
|
||||
let peerId: PeerId = message.peerId.peerId
|
||||
return MessageId(peerId: peerId, namespace: namespace, id: id)
|
||||
case .messageEmpty:
|
||||
return nil
|
||||
case let .messageEmpty(_, id, peerId):
|
||||
if let peerId = peerId {
|
||||
return MessageId(peerId: peerId.peerId, namespace: Namespaces.Message.Cloud, id: id)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let .messageService(flags, id, fromId, chatPeerId, _, _, _):
|
||||
let peerId: PeerId = chatPeerId.peerId
|
||||
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"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,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -504,7 +504,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
break
|
||||
}
|
||||
}
|
||||
case let .groupPhoneCall(callId, accessHash, _), let .inviteToGroupPhoneCall(callId, accessHash, _):
|
||||
case .groupPhoneCall, .inviteToGroupPhoneCall:
|
||||
if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall {
|
||||
strongSelf.context.joinGroupCall(peerId: message.id.peerId, activeCall: CachedChannelData.ActiveCall(id: activeCall.id, accessHash: activeCall.accessHash))
|
||||
} else {
|
||||
@ -556,7 +556,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
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))
|
||||
}, completed: { [weak self] in
|
||||
}, completed: {
|
||||
dismissStatus?()
|
||||
}))
|
||||
}
|
||||
@ -708,7 +708,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
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()
|
||||
}, presentStickers: { [weak self] completion in
|
||||
if let strongSelf = self {
|
||||
@ -854,9 +854,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}, navigateToMessage: { [weak self] fromId, id in
|
||||
self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId)
|
||||
}, navigateToMessageStandalone: { [weak self] id in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
self?.navigateToMessage(from: nil, to: .id(id), forceInCurrentChat: false)
|
||||
}, tapMessage: nil, clickThroughMessage: { [weak self] in
|
||||
self?.chatDisplayNode.dismissInput()
|
||||
|
@ -65,6 +65,26 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation
|
||||
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> {
|
||||
switch inputQuery {
|
||||
case let .emoji(query):
|
||||
@ -79,18 +99,30 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
||||
} else {
|
||||
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
|
||||
return stickerSettings
|
||||
}
|
||||
|
||||
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = combineLatest(stickerConfiguration, stickerSettings)
|
||||
|> castError(ChatContextQueryError.self)
|
||||
|> mapToSignal { stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
|
||||
|> mapToSignal { stickerConfiguration, stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
|
||||
let scope: SearchStickersScope
|
||||
switch stickerSettings.emojiStickerSuggestionMode {
|
||||
case .none:
|
||||
scope = []
|
||||
case .all:
|
||||
scope = [.installed, .remote]
|
||||
if stickerConfiguration.disableLocalSuggestions {
|
||||
scope = [.remote]
|
||||
} else {
|
||||
scope = [.installed, .remote]
|
||||
}
|
||||
case .installed:
|
||||
scope = [.installed]
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
let imageNode: TransformImageNode
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode
|
||||
private var animationNode: GenericAnimatedStickerNode?
|
||||
private var didSetUpAnimationNode = false
|
||||
private var isPlaying = false
|
||||
@ -194,7 +194,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
self.placeholderNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init(layerBacked: false)
|
||||
|
||||
@ -242,10 +242,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
if firstTime {
|
||||
strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { [weak self] _ in
|
||||
self?.removePlaceholder(animated: false)
|
||||
})
|
||||
if firstTime && !strongSelf.placeholderNode.isEmpty {
|
||||
strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
strongSelf.removePlaceholder(animated: true)
|
||||
} else {
|
||||
strongSelf.removePlaceholder(animated: true)
|
||||
}
|
||||
@ -258,10 +257,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||
self.addSubnode(self.containerNode)
|
||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.contextSourceNode.contentNode.addSubnode(placeholderNode)
|
||||
}
|
||||
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
||||
|
||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||
|
||||
@ -283,16 +279,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
if !animated {
|
||||
placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
placeholderNode.alpha = 0.0
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||
placeholderNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
if !animated {
|
||||
self.placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
self.placeholderNode.alpha = 0.0
|
||||
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.placeholderNode.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,11 +422,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let animationNode = self.animationNode, !self.animateGreeting {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: placeholderNode)
|
||||
} else {
|
||||
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: self.imageNode)
|
||||
}
|
||||
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: self.placeholderNode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -573,9 +562,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
var rect = rect
|
||||
rect.origin.y = containerSize.height - rect.maxY + self.insets.top
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
@ -969,11 +956,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
file = telegramFile
|
||||
}
|
||||
|
||||
if let file = file, let immediateThumbnailData = file.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
|
||||
if let file = file, let immediateThumbnailData = file.immediateThumbnailData {
|
||||
let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationNodeFrame.size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0))
|
||||
placeholderNode.frame = animationNodeFrame
|
||||
strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationNodeFrame.size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0))
|
||||
strongSelf.placeholderNode.frame = animationNodeFrame
|
||||
}
|
||||
|
||||
if let animationNode = strongSelf.animationNode, let parentNode = strongSelf.greetingStickerParentNode, strongSelf.animateGreeting {
|
||||
|
@ -22,7 +22,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
let imageNode: TransformImageNode
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode
|
||||
var textNode: TextNode?
|
||||
|
||||
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
||||
@ -53,7 +53,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.imageNode = TransformImageNode()
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
self.placeholderNode.isUserInteractionEnabled = false
|
||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||
|
||||
super.init(layerBacked: false)
|
||||
@ -64,8 +64,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
if firstTime {
|
||||
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { [weak self] _ in
|
||||
if firstTime && !strongSelf.placeholderNode.isEmpty {
|
||||
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.removePlaceholder(animated: false)
|
||||
})
|
||||
} else {
|
||||
@ -117,9 +117,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
self.containerNode.addSubnode(self.contextSourceNode)
|
||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||
self.addSubnode(self.containerNode)
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.contextSourceNode.contentNode.addSubnode(placeholderNode)
|
||||
}
|
||||
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
|
||||
self.contextSourceNode.contentNode.addSubnode(self.imageNode)
|
||||
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
|
||||
|
||||
@ -141,16 +139,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
if !animated {
|
||||
placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
placeholderNode.alpha = 0.0
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||
placeholderNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
if !animated {
|
||||
self.placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
self.placeholderNode.alpha = 0.0
|
||||
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.placeholderNode.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,9 +234,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
var rect = rect
|
||||
rect.origin.y = containerSize.height - rect.maxY + self.insets.top
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
@ -608,13 +601,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
|
||||
imageApply()
|
||||
|
||||
if let immediateThumbnailData = telegramFile?.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
|
||||
if let immediateThumbnailData = telegramFile?.immediateThumbnailData {
|
||||
let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
|
||||
|
||||
let placeholderFrame = updatedImageFrame.insetBy(dx: innerImageInset, dy: innerImageInset)
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: placeholderFrame.size)
|
||||
placeholderNode.frame = placeholderFrame
|
||||
strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: placeholderFrame.size)
|
||||
strongSelf.placeholderNode.frame = placeholderFrame
|
||||
}
|
||||
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
|
@ -936,6 +936,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
var displayLeave = !channel.flags.contains(.isCreator)
|
||||
var canViewStats = false
|
||||
var hasDiscussion = false
|
||||
var hasVoiceChat = false
|
||||
if let cachedChannelData = cachedData as? CachedChannelData {
|
||||
canViewStats = cachedChannelData.flags.contains(.canViewStats)
|
||||
}
|
||||
@ -952,6 +953,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
|
||||
result.append(.addMember)
|
||||
}
|
||||
if channel.flags.contains(.hasVoiceChat) {
|
||||
hasVoiceChat = true
|
||||
}
|
||||
}
|
||||
switch channel.participationStatus {
|
||||
case .member:
|
||||
@ -963,6 +967,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
displayLeave = false
|
||||
}
|
||||
result.append(.mute)
|
||||
if hasVoiceChat {
|
||||
result.append(.voiceChat)
|
||||
}
|
||||
if hasDiscussion {
|
||||
result.append(.discussion)
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ enum PeerInfoHeaderButtonKey: Hashable {
|
||||
case discussion
|
||||
case call
|
||||
case videoCall
|
||||
case voiceChat
|
||||
case mute
|
||||
case more
|
||||
case addMember
|
||||
@ -36,6 +37,7 @@ enum PeerInfoHeaderButtonIcon {
|
||||
case message
|
||||
case call
|
||||
case videoCall
|
||||
case voiceChat
|
||||
case mute
|
||||
case unmute
|
||||
case more
|
||||
@ -112,6 +114,8 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
imageName = "Peer Info/ButtonCall"
|
||||
case .videoCall:
|
||||
imageName = "Peer Info/ButtonVideo"
|
||||
case .voiceChat:
|
||||
imageName = "Peer Info/ButtonVoiceChat"
|
||||
case .mute:
|
||||
imageName = "Peer Info/ButtonMute"
|
||||
case .unmute:
|
||||
@ -3273,6 +3277,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
case .videoCall:
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
|
||||
buttonIcon = .videoCall
|
||||
case .voiceChat:
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonVoiceChat
|
||||
buttonIcon = .voiceChat
|
||||
case .mute:
|
||||
if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState {
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
|
||||
|
@ -2850,6 +2850,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.requestCall(isVideo: false)
|
||||
case .videoCall:
|
||||
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:
|
||||
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
|
||||
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start()
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 727044698c8f3df83c0b6f9b37cc0ec3acba0c98
|
||||
Subproject commit 0d1189620b78c41620414ef619115d8cf81598d8
|
Loading…
x
Reference in New Issue
Block a user