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

This commit is contained in:
overtake 2021-01-07 14:32:23 +03:00
commit 8102308d35
34 changed files with 5807 additions and 5537 deletions

View File

@ -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";

View File

@ -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)

View File

@ -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 {

View File

@ -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
} }

View File

@ -99,9 +99,12 @@ private final class NavigationControllerNode: ASDisplayNode {
} }
override func accessibilityPerformEscape() -> Bool { override func accessibilityPerformEscape() -> Bool {
if let controller = self.controller, controller.viewControllers.count > 1 {
let _ = self.controller?.popViewController(animated: true) let _ = self.controller?.popViewController(animated: true)
return true return true
} }
return false
}
} }
public protocol NavigationControllerDropContentItem: class { public protocol NavigationControllerDropContentItem: class {

View File

@ -793,7 +793,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let strongSelf = self, !isAnimated { if let strongSelf = self, !isAnimated {
videoNode?.seek(0.0) videoNode?.seek(0.0)
if strongSelf.actionAtEnd == .stop { if strongSelf.actionAtEnd == .stop && strongSelf.isCentral {
strongSelf.updateControlsVisibility(true) strongSelf.updateControlsVisibility(true)
strongSelf.controlsTimer?.invalidate() strongSelf.controlsTimer?.invalidate()
strongSelf.controlsTimer = nil strongSelf.controlsTimer = nil
@ -900,6 +900,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
} else { } else {
self.controlsTimer?.invalidate()
self.controlsTimer = nil
self.dismissOnOrientationChange = false self.dismissOnOrientationChange = false
if videoNode.ownsContentNode { if videoNode.ownsContentNode {
videoNode.pause() videoNode.pause()

View File

@ -164,6 +164,10 @@ public class StickerShimmerEffectNode: ASDisplayNode {
self.addSubnode(self.foregroundNode) self.addSubnode(self.foregroundNode)
} }
public var isEmpty: Bool {
return self.currentData == nil
}
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
self.effectNode.updateAbsoluteRect(rect, within: containerSize) self.effectNode.updateAbsoluteRect(rect, within: containerSize)
} }

View File

@ -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) }
@ -629,9 +629,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1670052855] = { return Api.FoundGif.parse_foundGifCached($0) } dict[-1670052855] = { return Api.FoundGif.parse_foundGifCached($0) }
dict[537022650] = { return Api.User.parse_userEmpty($0) } dict[537022650] = { return Api.User.parse_userEmpty($0) }
dict[-1820043071] = { return Api.User.parse_user($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[1487813065] = { return Api.Message.parse_message($0) }
dict[678405636] = { return Api.Message.parse_messageService($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[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) }
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($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[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) } dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($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[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) } dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) } dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }

View File

@ -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
@ -18144,18 +18152,12 @@ public extension Api {
} }
public enum Message: TypeConstructorDescription { 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 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 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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): 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 { if boxed {
buffer.appendInt32(1487813065) buffer.appendInt32(1487813065)
@ -18200,31 +18202,28 @@ public extension Api {
serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false)
action.serialize(buffer, true) action.serialize(buffer, true)
break 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)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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): 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)]) 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): 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)]) 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? { public static func parse_message(_ reader: BufferReader) -> Message? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()
@ -18345,6 +18344,25 @@ public extension Api {
return nil 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 { public enum StatsGroupTopInviter: TypeConstructorDescription {
@ -18903,7 +18921,7 @@ public extension Api {
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer) case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON) case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -18927,12 +18945,11 @@ public extension Api {
} }
paymentData.serialize(buffer, true) paymentData.serialize(buffer, true)
break break
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId): case .inputPaymentCredentialsGooglePay(let paymentToken):
if boxed { if boxed {
buffer.appendInt32(-905587442) buffer.appendInt32(-1966921727)
} }
paymentToken.serialize(buffer, true) paymentToken.serialize(buffer, true)
serializeString(googleTransactionId, buffer: buffer, boxed: false)
break break
} }
} }
@ -18945,8 +18962,8 @@ public extension Api {
return ("inputPaymentCredentials", [("flags", flags), ("data", data)]) return ("inputPaymentCredentials", [("flags", flags), ("data", data)])
case .inputPaymentCredentialsApplePay(let paymentData): case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData)]) return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData)])
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId): case .inputPaymentCredentialsGooglePay(let paymentToken):
return ("inputPaymentCredentialsAndroidPay", [("paymentToken", paymentToken), ("googleTransactionId", googleTransactionId)]) return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken)])
} }
} }
@ -18993,17 +19010,14 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_inputPaymentCredentialsAndroidPay(_ reader: BufferReader) -> InputPaymentCredentials? { public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON? var _1: Api.DataJSON?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON _1 = Api.parse(reader, signature: signature) as? Api.DataJSON
} }
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil if _c1 {
if _c1 && _c2 { return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
return Api.InputPaymentCredentials.inputPaymentCredentialsAndroidPay(paymentToken: _1!, googleTransactionId: _2!)
} }
else { else {
return nil return nil

View File

@ -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
})
}
} }
} }
} }

View File

@ -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

View File

@ -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)
} }
} }
} }

View File

@ -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:
if let muteState = peerEntry.muteState, muteState.mutedByYou {
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
} else {
text = .text(presentationData.strings.VoiceChat_StatusListening, .accent) 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:
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) text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
}
icon = .microphone(false, UIColor(rgb: 0x34c759)) 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
} }

View File

@ -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() {

View File

@ -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

View File

@ -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
)) ))
} }
} }

View File

@ -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)

View File

@ -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! {

View File

@ -116,8 +116,12 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
case let .message(message): case let .message(message):
let chatPeerId = message.peerId let chatPeerId = message.peerId
return chatPeerId.peerId return chatPeerId.peerId
case .messageEmpty: case let .messageEmpty(_, id, peerId):
if let peerId = peerId {
return peerId.peerId
} else {
return nil return nil
}
case let .messageService(flags, _, fromId, chatPeerId, _, _, _): case let .messageService(flags, _, fromId, chatPeerId, _, _, _):
return chatPeerId.peerId return chatPeerId.peerId
} }

View File

@ -99,7 +99,7 @@ extension Api.Message {
switch self { switch self {
case let .message(message): case let .message(message):
return message.id return message.id
case let .messageEmpty(id): case let .messageEmpty(_, id, _):
return id return id
case let .messageService(_, id, _, _, _, _, _): case let .messageService(_, id, _, _, _, _, _):
return id return id
@ -115,8 +115,12 @@ extension Api.Message {
let peerId: PeerId = message.peerId.peerId let peerId: PeerId = message.peerId.peerId
return MessageId(peerId: peerId, namespace: namespace, id: id) return MessageId(peerId: peerId, namespace: namespace, id: id)
case .messageEmpty: case let .messageEmpty(_, id, peerId):
if let peerId = peerId {
return MessageId(peerId: peerId.peerId, namespace: Namespaces.Message.Cloud, id: id)
} else {
return nil return nil
}
case let .messageService(flags, id, fromId, chatPeerId, _, _, _): case let .messageService(flags, id, fromId, chatPeerId, _, _, _):
let peerId: PeerId = chatPeerId.peerId let peerId: PeerId = chatPeerId.peerId
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id) return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)

View File

@ -1,7 +1,7 @@
{ {
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
}, },
"properties" : { "properties" : {
"provides-namespace" : true "provides-namespace" : true

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "profile_voice.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,7 +1,7 @@
{ {
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
}, },
"properties" : { "properties" : {
"provides-namespace" : true "provides-namespace" : true

View File

@ -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()

View File

@ -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:
if stickerConfiguration.disableLocalSuggestions {
scope = [.remote]
} else {
scope = [.installed, .remote] scope = [.installed, .remote]
}
case .installed: case .installed:
scope = [.installed] scope = [.installed]
} }

View File

@ -144,7 +144,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private let contextSourceNode: ContextExtractedContentContainingNode private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
let imageNode: TransformImageNode let imageNode: TransformImageNode
private var placeholderNode: StickerShimmerEffectNode? private var placeholderNode: StickerShimmerEffectNode
private var animationNode: GenericAnimatedStickerNode? private var animationNode: GenericAnimatedStickerNode?
private var didSetUpAnimationNode = false private var didSetUpAnimationNode = false
private var isPlaying = false private var isPlaying = false
@ -194,7 +194,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.dateAndStatusNode = ChatMessageDateAndStatusNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode()
self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode = StickerShimmerEffectNode()
self.placeholderNode?.isUserInteractionEnabled = false self.placeholderNode.isUserInteractionEnabled = false
super.init(layerBacked: false) super.init(layerBacked: false)
@ -242,10 +242,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
return return
} }
if image != nil { if image != nil {
if firstTime { if firstTime && !strongSelf.placeholderNode.isEmpty {
strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { [weak self] _ in strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self?.removePlaceholder(animated: false) strongSelf.removePlaceholder(animated: true)
})
} else { } else {
strongSelf.removePlaceholder(animated: true) strongSelf.removePlaceholder(animated: true)
} }
@ -258,10 +257,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.contextSourceNode.contentNode.addSubnode(self.imageNode) self.contextSourceNode.contentNode.addSubnode(self.imageNode)
self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
if let placeholderNode = self.placeholderNode {
self.contextSourceNode.contentNode.addSubnode(placeholderNode)
}
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode) self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
@ -283,18 +279,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
private func removePlaceholder(animated: Bool) { private func removePlaceholder(animated: Bool) {
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
if !animated { if !animated {
placeholderNode.removeFromSupernode() self.placeholderNode.removeFromSupernode()
} else { } else {
placeholderNode.alpha = 0.0 self.placeholderNode.alpha = 0.0
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
placeholderNode?.removeFromSupernode() self?.placeholderNode.removeFromSupernode()
}) })
} }
} }
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
@ -429,11 +422,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
if let animationNode = self.animationNode, !self.animateGreeting { if let animationNode = self.animationNode, !self.animateGreeting {
if let placeholderNode = self.placeholderNode { self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: self.placeholderNode)
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: placeholderNode)
} else {
self.contextSourceNode.contentNode.insertSubnode(animationNode, aboveSubnode: self.imageNode)
}
} }
} }
@ -573,9 +562,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var rect = rect var rect = rect
rect.origin.y = containerSize.height - rect.maxY + self.insets.top rect.origin.y = containerSize.height - rect.maxY + self.insets.top
if let placeholderNode = self.placeholderNode { 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)
placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
}
} }
} }
@ -969,11 +956,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
file = telegramFile 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 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 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)) 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))
placeholderNode.frame = animationNodeFrame strongSelf.placeholderNode.frame = animationNodeFrame
} }
if let animationNode = strongSelf.animationNode, let parentNode = strongSelf.greetingStickerParentNode, strongSelf.animateGreeting { if let animationNode = strongSelf.animationNode, let parentNode = strongSelf.greetingStickerParentNode, strongSelf.animateGreeting {

View File

@ -22,7 +22,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private let contextSourceNode: ContextExtractedContentContainingNode private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
let imageNode: TransformImageNode let imageNode: TransformImageNode
private var placeholderNode: StickerShimmerEffectNode? private var placeholderNode: StickerShimmerEffectNode
var textNode: TextNode? var textNode: TextNode?
private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
@ -53,7 +53,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.containerNode = ContextControllerSourceNode() self.containerNode = ContextControllerSourceNode()
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode = StickerShimmerEffectNode()
self.placeholderNode?.isUserInteractionEnabled = false self.placeholderNode.isUserInteractionEnabled = false
self.dateAndStatusNode = ChatMessageDateAndStatusNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode()
super.init(layerBacked: false) super.init(layerBacked: false)
@ -64,8 +64,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
return return
} }
if image != nil { if image != nil {
if firstTime { if firstTime && !strongSelf.placeholderNode.isEmpty {
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { [weak self] _ in strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in
self?.removePlaceholder(animated: false) self?.removePlaceholder(animated: false)
}) })
} else { } else {
@ -117,9 +117,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.containerNode.addSubnode(self.contextSourceNode) self.containerNode.addSubnode(self.contextSourceNode)
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
if let placeholderNode = self.placeholderNode { self.contextSourceNode.contentNode.addSubnode(self.placeholderNode)
self.contextSourceNode.contentNode.addSubnode(placeholderNode)
}
self.contextSourceNode.contentNode.addSubnode(self.imageNode) self.contextSourceNode.contentNode.addSubnode(self.imageNode)
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode) self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
@ -141,18 +139,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private func removePlaceholder(animated: Bool) { private func removePlaceholder(animated: Bool) {
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
if !animated { if !animated {
placeholderNode.removeFromSupernode() self.placeholderNode.removeFromSupernode()
} else { } else {
placeholderNode.alpha = 0.0 self.placeholderNode.alpha = 0.0
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
placeholderNode?.removeFromSupernode() self?.placeholderNode.removeFromSupernode()
}) })
} }
} }
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
@ -239,9 +234,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
var rect = rect var rect = rect
rect.origin.y = containerSize.height - rect.maxY + self.insets.top rect.origin.y = containerSize.height - rect.maxY + self.insets.top
if let placeholderNode = self.placeholderNode { self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + placeholderNode.frame.minX, y: rect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize)
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) transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
imageApply() 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 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 shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
let placeholderFrame = updatedImageFrame.insetBy(dx: innerImageInset, dy: innerImageInset) let placeholderFrame = updatedImageFrame.insetBy(dx: innerImageInset, dy: innerImageInset)
placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: placeholderFrame.size) strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: placeholderFrame.size)
placeholderNode.frame = placeholderFrame strongSelf.placeholderNode.frame = placeholderFrame
} }
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)

View File

@ -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)
} }

View File

@ -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

View File

@ -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()

@ -1 +1 @@
Subproject commit 727044698c8f3df83c0b6f9b37cc0ec3acba0c98 Subproject commit 0d1189620b78c41620414ef619115d8cf81598d8