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