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

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

View File

@ -5810,3 +5810,9 @@ Sorry for the inconvenience.";
"Call.VoiceOver.VideoCallMissed" = "Missed Video Call";
"Call.VoiceOver.VoiceCallCanceled" = "Cancelled Voice Call";
"Call.VoiceOver.VideoCallCanceled" = "Cancelled Video Call";
"VoiceChat.UnmuteForMe" = "Unmute for Me";
"VoiceChat.MuteForMe" = "Mute for Me";
"PeerInfo.ButtonVoiceChat" = "Voice Chat";
"VoiceChat.OpenChat" = "Open Chat";

View File

@ -294,7 +294,7 @@ public protocol PresentationGroupCall: class {
func toggleIsMuted()
func setIsMuted(action: PresentationGroupCallMuteAction)
func updateDefaultParticipantsAreMuted(isMuted: Bool)
func setVolume(peerId: PeerId, volume: Double)
func setVolume(peerId: PeerId, volume: Int32, sync: Bool)
func setCurrentAudioOutput(_ output: AudioSessionOutput)
func updateMuteState(peerId: PeerId, isMuted: Bool)

View File

@ -89,7 +89,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.itemSeparator(separatorNode))
}
case let .custom(item):
case let .custom(item, _):
itemNodes.append(.custom(item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)))
if i != items.count - 1, case .action = items[i + 1] {
let separatorNode = ASDisplayNode()
@ -425,6 +425,7 @@ private final class InnerTextSelectionTipContainerNode: ASDisplayNode {
final class ContextActionsContainerNode: ASDisplayNode {
private let blurBackground: Bool
private let shadowNode: ASImageNode
private let additionalActionsNode: InnerActionsContainerNode?
private let actionsNode: InnerActionsContainerNode
private let textSelectionTipNode: InnerTextSelectionTipContainerNode?
private let scrollNode: ASScrollNode
@ -446,6 +447,14 @@ final class ContextActionsContainerNode: ASDisplayNode {
self.shadowNode.contentMode = .scaleToFill
self.shadowNode.isHidden = true
var items = items
if let firstItem = items.first, case let .custom(item, additional) = firstItem, additional {
self.additionalActionsNode = InnerActionsContainerNode(presentationData: presentationData, items: [firstItem], getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
items.removeFirst()
} else {
self.additionalActionsNode = nil
}
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
if displayTextSelectionTip {
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData)
@ -466,6 +475,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
super.init()
self.addSubnode(self.shadowNode)
self.additionalActionsNode.flatMap(self.scrollNode.addSubnode)
self.scrollNode.addSubnode(self.actionsNode)
self.textSelectionTipNode.flatMap(self.scrollNode.addSubnode)
self.addSubnode(self.scrollNode)
@ -477,13 +487,24 @@ final class ContextActionsContainerNode: ASDisplayNode {
widthClass = .regular
}
var contentSize = CGSize()
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, transition: transition)
let bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: actionsSize)
if let additionalActionsNode = self.additionalActionsNode {
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, transition: transition)
contentSize = additionalActionsSize
transition.updateFrame(node: additionalActionsNode, frame: CGRect(origin: CGPoint(), size: additionalActionsSize))
contentSize.height += 8.0
}
let bounds = CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: actionsSize)
transition.updateFrame(node: self.shadowNode, frame: bounds.insetBy(dx: -30.0, dy: -30.0))
self.shadowNode.isHidden = widthClass == .compact
var contentSize = actionsSize
contentSize.width = max(contentSize.width, actionsSize.width)
contentSize.height += actionsSize.height
transition.updateFrame(node: self.actionsNode, frame: bounds)
if let textSelectionTipNode = self.textSelectionTipNode {

View File

@ -84,7 +84,7 @@ public protocol ContextMenuCustomItem {
public enum ContextMenuItem {
case action(ContextMenuActionItem)
case custom(ContextMenuCustomItem)
case custom(ContextMenuCustomItem, Bool)
case separator
}

View File

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

View File

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

View File

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

View File

@ -139,7 +139,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1511503333] = { return Api.InputEncryptedFile.parse_inputEncryptedFile($0) }
dict[767652808] = { return Api.InputEncryptedFile.parse_inputEncryptedFileBigUploaded($0) }
dict[-1456996667] = { return Api.messages.InactiveChats.parse_inactiveChats($0) }
dict[1454409673] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
dict[-1199443157] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) }
dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) }
dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) }
@ -629,9 +629,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1670052855] = { return Api.FoundGif.parse_foundGifCached($0) }
dict[537022650] = { return Api.User.parse_userEmpty($0) }
dict[-1820043071] = { return Api.User.parse_user($0) }
dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) }
dict[1487813065] = { return Api.Message.parse_message($0) }
dict[678405636] = { return Api.Message.parse_messageService($0) }
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) }
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) }
@ -652,7 +652,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) }
dict[-905587442] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsAndroidPay($0) }
dict[-1966921727] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsGooglePay($0) }
dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }

View File

@ -5438,27 +5438,29 @@ public extension Api {
}
public enum GroupCallParticipant: TypeConstructorDescription {
case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, activeDate: Int32?, source: Int32)
case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?, mutedCnt: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source):
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume, let mutedCnt):
if boxed {
buffer.appendInt32(1454409673)
buffer.appendInt32(-1199443157)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(activeDate!, buffer: buffer, boxed: false)}
serializeInt32(source, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(mutedCnt!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source):
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", source)])
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume, let mutedCnt):
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", source), ("volume", volume), ("mutedCnt", mutedCnt)])
}
}
@ -5473,13 +5475,19 @@ public extension Api {
if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() }
var _5: Int32?
_5 = reader.readInt32()
var _6: Int32?
if Int(_1!) & Int(1 << 7) != 0 {_6 = reader.readInt32() }
var _7: Int32?
if Int(_1!) & Int(1 << 8) != 0 {_7 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!)
let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 8) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, mutedCnt: _7)
}
else {
return nil
@ -18144,18 +18152,12 @@ public extension Api {
}
public enum Message: TypeConstructorDescription {
case messageEmpty(id: Int32)
case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?)
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction)
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageEmpty(let id):
if boxed {
buffer.appendInt32(-2082087340)
}
serializeInt32(id, buffer: buffer, boxed: false)
break
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason):
if boxed {
buffer.appendInt32(1487813065)
@ -18200,31 +18202,28 @@ public extension Api {
serializeInt32(date, buffer: buffer, boxed: false)
action.serialize(buffer, true)
break
case .messageEmpty(let flags, let id, let peerId):
if boxed {
buffer.appendInt32(-1868117372)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageEmpty(let id):
return ("messageEmpty", [("id", id)])
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason):
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)])
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action):
return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("replyTo", replyTo), ("date", date), ("action", action)])
case .messageEmpty(let flags, let id, let peerId):
return ("messageEmpty", [("flags", flags), ("id", id), ("peerId", peerId)])
}
}
public static func parse_messageEmpty(_ reader: BufferReader) -> Message? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.Message.messageEmpty(id: _1!)
}
else {
return nil
}
}
public static func parse_message(_ reader: BufferReader) -> Message? {
var _1: Int32?
_1 = reader.readInt32()
@ -18345,6 +18344,25 @@ public extension Api {
return nil
}
}
public static func parse_messageEmpty(_ reader: BufferReader) -> Message? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.Peer?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Peer
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.Message.messageEmpty(flags: _1!, id: _2!, peerId: _3)
}
else {
return nil
}
}
}
public enum StatsGroupTopInviter: TypeConstructorDescription {
@ -18903,7 +18921,7 @@ public extension Api {
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
case inputPaymentCredentialsAndroidPay(paymentToken: Api.DataJSON, googleTransactionId: String)
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -18927,12 +18945,11 @@ public extension Api {
}
paymentData.serialize(buffer, true)
break
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
case .inputPaymentCredentialsGooglePay(let paymentToken):
if boxed {
buffer.appendInt32(-905587442)
buffer.appendInt32(-1966921727)
}
paymentToken.serialize(buffer, true)
serializeString(googleTransactionId, buffer: buffer, boxed: false)
break
}
}
@ -18945,8 +18962,8 @@ public extension Api {
return ("inputPaymentCredentials", [("flags", flags), ("data", data)])
case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData)])
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
return ("inputPaymentCredentialsAndroidPay", [("paymentToken", paymentToken), ("googleTransactionId", googleTransactionId)])
case .inputPaymentCredentialsGooglePay(let paymentToken):
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken)])
}
}
@ -18993,17 +19010,14 @@ public extension Api {
return nil
}
}
public static func parse_inputPaymentCredentialsAndroidPay(_ reader: BufferReader) -> InputPaymentCredentials? {
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentialsAndroidPay(paymentToken: _1!, googleTransactionId: _2!)
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
}
else {
return nil

View File

@ -7287,22 +7287,6 @@ public extension Api {
})
}
public static func editGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1662282468)
serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true)
userId.serialize(buffer, true)
return (FunctionDescription(name: "phone.editGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(2067345760)
@ -7406,6 +7390,23 @@ public extension Api {
return result
})
}
public static func editGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser, volume: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1511559976)
serializeInt32(flags, buffer: buffer, boxed: false)
call.serialize(buffer, true)
userId.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "phone.editGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId), ("volume", volume)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
}
}

View File

@ -325,7 +325,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
return
}
let panelData = currentGroupCall != nil ? nil : availableState
let panelData = currentGroupCall != nil || availableState?.participantCount == 0 ? nil : availableState
let wasEmpty = strongSelf.groupCallPanelData == nil
strongSelf.groupCallPanelData = panelData

View File

@ -171,7 +171,7 @@ private extension PresentationGroupCallState {
networkState: .connecting,
canManageCall: false,
adminIds: Set(),
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true),
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
defaultParticipantMuteState: nil
)
}
@ -669,7 +669,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
ssrc: 0,
joinTimestamp: strongSelf.temporaryJoinTimestamp,
activityTimestamp: nil,
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
volume: nil
))
participants.sort()
}
@ -1042,7 +1043,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
strongSelf.stateValue.muteState = muteState
} else if let currentMuteState = strongSelf.stateValue.muteState, !currentMuteState.canUnmute {
strongSelf.isMutedValue = .muted(isPushToTalkActive: false)
strongSelf.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
strongSelf.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false)
strongSelf.callContext?.setIsMuted(true)
}
}
@ -1229,16 +1230,19 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.callContext?.setIsMuted(isEffectivelyMuted)
if isVisuallyMuted {
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true)
self.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false)
} else {
self.stateValue.muteState = nil
}
}
public func setVolume(peerId: PeerId, volume: Double) {
public func setVolume(peerId: PeerId, volume: Int32, sync: Bool) {
for (ssrc, id) in self.ssrcMapping {
if id == peerId {
self.callContext?.setVolume(ssrc: ssrc, volume: volume)
self.callContext?.setVolume(ssrc: ssrc, volume: Double(volume) / 10000.0)
if sync {
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: volume)
}
break
}
}
@ -1315,6 +1319,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
public func updateMuteState(peerId: PeerId, isMuted: Bool) {
let canThenUnmute: Bool
if isMuted {
var mutedByYou = false
if peerId == self.accountContext.account.peerId {
canThenUnmute = true
} else if self.stateValue.canManageCall {
@ -1326,14 +1331,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} else if self.stateValue.adminIds.contains(self.accountContext.account.peerId) {
canThenUnmute = true
} else {
mutedByYou = true
canThenUnmute = true
}
self.participantsContext?.updateMuteState(peerId: peerId, muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canThenUnmute) : nil)
self.participantsContext?.updateMuteState(peerId: peerId, muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: canThenUnmute, mutedByYou: mutedByYou) : nil, volume: nil)
} else {
if peerId == self.accountContext.account.peerId {
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil)
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: nil)
} else if self.stateValue.canManageCall || self.stateValue.adminIds.contains(self.accountContext.account.peerId) {
self.participantsContext?.updateMuteState(peerId: peerId, muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false), volume: nil)
} else {
self.participantsContext?.updateMuteState(peerId: peerId, muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true))
self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: nil)
}
}
}

View File

@ -209,6 +209,7 @@ public final class VoiceChatController: ViewController {
var muteState: GroupCallParticipantsContext.Participant.MuteState?
var revealed: Bool?
var canManageCall: Bool
var volume: Int32?
var stableId: PeerId {
return self.peer.id
@ -236,6 +237,9 @@ public final class VoiceChatController: ViewController {
if lhs.canManageCall != rhs.canManageCall {
return false
}
if lhs.volume != rhs.volume {
return false
}
return true
}
@ -333,17 +337,30 @@ public final class VoiceChatController: ViewController {
let icon: VoiceChatParticipantItem.Icon
switch peerEntry.state {
case .listening:
if let muteState = peerEntry.muteState, muteState.mutedByYou {
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
} else {
text = .text(presentationData.strings.VoiceChat_StatusListening, .accent)
}
let microphoneColor: UIColor
if let muteState = peerEntry.muteState, !muteState.canUnmute {
if let muteState = peerEntry.muteState, !muteState.canUnmute || muteState.mutedByYou {
microphoneColor = UIColor(rgb: 0xff3b30)
} else {
microphoneColor = UIColor(rgb: 0x979797)
}
icon = .microphone(peerEntry.muteState != nil, microphoneColor)
case .speaking:
if let muteState = peerEntry.muteState, muteState.mutedByYou {
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
icon = .microphone(true, UIColor(rgb: 0xff3b30))
} else {
if let volume = peerEntry.volume, volume != 10000 {
text = .text("\(volume / 100)% \(presentationData.strings.VoiceChat_StatusSpeaking)", .constructive)
} else {
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
}
icon = .microphone(false, UIColor(rgb: 0x34c759))
}
case .invited:
text = .text(presentationData.strings.VoiceChat_StatusInvited, .generic)
icon = .invite(true)
@ -355,7 +372,7 @@ public final class VoiceChatController: ViewController {
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
}, action: {
interaction.openPeer(peer.id)
}, contextAction: peer.id == context.account.peerId || !peerEntry.canManageCall ? nil : { node, gesture in
}, contextAction: peer.id == context.account.peerId ? nil : { node, gesture in
interaction.peerContextAction(peerEntry, node, gesture)
})
}
@ -387,6 +404,7 @@ public final class VoiceChatController: ViewController {
private let topPanelNode: ASDisplayNode
private let topPanelEdgeNode: ASDisplayNode
private let topPanelBackgroundNode: ASDisplayNode
private let recButton: VoiceChatHeaderButton
private let optionsButton: VoiceChatHeaderButton
private let closeButton: VoiceChatHeaderButton
private let topCornersNode: ASImageNode
@ -493,6 +511,9 @@ public final class VoiceChatController: ViewController {
self.topPanelEdgeNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
self.recButton = VoiceChatHeaderButton(rec: true)
self.recButton.setImage(optionsBackgroundImage(dark: false))
self.recButton.isHidden = true
self.optionsButton = VoiceChatHeaderButton()
self.optionsButton.setImage(optionsButtonImage(dark: false))
self.closeButton = VoiceChatHeaderButton()
@ -792,6 +813,16 @@ public final class VoiceChatController: ViewController {
var items: [ContextMenuItem] = []
if peer.id != strongSelf.context.account.peerId {
if let muteState = entry.muteState, muteState.mutedByYou {
} else {
items.append(.custom(VoiceChatVolumeContextItem(value: entry.volume.flatMap { CGFloat($0) / 10000.0 } ?? 1.0, valueChanged: { newValue, finished in
if finished && newValue.isZero {
}
strongSelf.call.setVolume(peerId: peer.id, volume: Int32(newValue * 10000), sync: finished)
}), true))
}
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
if callState.adminIds.contains(peer.id) {
if let _ = entry.muteState {
@ -832,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)) {
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)
@ -873,10 +939,6 @@ public final class VoiceChatController: ViewController {
})
})))
}
items.append(.custom(VoiceChatVolumeContextItem(value: 1.0, valueChanged: { newValue in
strongSelf.call.setVolume(peerId: peer.id, volume: Double(newValue))
})))
}
guard !items.isEmpty else {
@ -896,6 +958,7 @@ public final class VoiceChatController: ViewController {
self.topPanelNode.addSubnode(self.topPanelEdgeNode)
self.topPanelNode.addSubnode(self.topPanelBackgroundNode)
self.topPanelNode.addSubnode(self.titleNode)
self.topPanelNode.addSubnode(self.recButton)
self.topPanelNode.addSubnode(self.optionsButton)
self.topPanelNode.addSubnode(self.closeButton)
self.topPanelNode.addSubnode(self.topCornersNode)
@ -1080,7 +1143,7 @@ public final class VoiceChatController: ViewController {
self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
self.optionsButton.contextAction = { [weak self, weak optionsButton] sourceNode, gesture in
guard let strongSelf = self, let controller = strongSelf.controller, let strongOptionsButton = optionsButton else {
guard let strongSelf = self, let controller = strongSelf.controller else {
return
}
@ -1123,6 +1186,15 @@ public final class VoiceChatController: ViewController {
items.append(.separator)
}
items.append(.custom(VoiceChatRecordingContextItem(timestamp: CFAbsoluteTimeGetCurrent(), action: { (_, f) in
f(.dismissWithoutContent)
}), false))
if !items.isEmpty {
items.append(.separator)
}
if let callState = strongSelf.callState, callState.canManageCall {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
@ -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)
}
self.recButton.contextAction = self.optionsButton.contextAction
self.optionsButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
self.recButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
self.actionButtonColorDisposable = (self.actionButton.outerColor
@ -1566,6 +1648,7 @@ public final class VoiceChatController: ViewController {
}
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: isFullscreen)
self.recButton.setImage(optionsBackgroundImage(dark: isFullscreen), animated: transition.isAnimated)
self.optionsButton.setImage(optionsButtonImage(dark: isFullscreen), animated: transition.isAnimated)
self.closeButton.setImage(closeButtonImage(dark: isFullscreen), animated: transition.isAnimated)
@ -1670,6 +1753,7 @@ public final class VoiceChatController: ViewController {
self.updateTitle(transition: transition)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: CGSize(width: size.width, height: 44.0)))
transition.updateFrame(node: self.recButton, frame: CGRect(origin: CGPoint(x: 20.0, y: 18.0), size: CGSize(width: 58.0, height: 28.0)))
transition.updateFrame(node: self.optionsButton, frame: CGRect(origin: CGPoint(x: 20.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - 20.0 - 28.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
@ -2019,7 +2103,8 @@ public final class VoiceChatController: ViewController {
activityTimestamp: Int32.max - 1 - index,
state: memberState,
muteState: memberMuteState,
canManageCall: callState?.canManageCall ?? false
canManageCall: self.callState?.canManageCall ?? false,
volume: member.volume
)))
index += 1
}
@ -2030,8 +2115,9 @@ public final class VoiceChatController: ViewController {
presence: nil,
activityTimestamp: Int32.max - 1 - index,
state: .listening,
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true),
canManageCall: callState?.canManageCall ?? false
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
canManageCall: self.callState?.canManageCall ?? false,
volume: nil
)), at: 1)
}
@ -2047,7 +2133,8 @@ public final class VoiceChatController: ViewController {
activityTimestamp: Int32.max - 1 - index,
state: .invited,
muteState: nil,
canManageCall: false
canManageCall: false,
volume: nil
)))
index += 1
}

View File

@ -3,6 +3,15 @@ import UIKit
import AsyncDisplayKit
import Display
func optionsBackgroundImage(dark: Bool) -> UIImage? {
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
})?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14)
}
func optionsButtonImage(dark: Bool) -> UIImage? {
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
@ -45,13 +54,30 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
init() {
var textNode: ImmediateTextNode?
var dotNode: ASImageNode?
init(rec: Bool = false) {
self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.isGestureEnabled = false
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.iconNode.contentMode = .scaleToFill
if rec {
self.textNode = ImmediateTextNode()
self.textNode?.attributedText = NSAttributedString(string: "REC", font: Font.regular(12.0), textColor: .white)
if let textNode = self.textNode {
let textSize = textNode.updateLayout(CGSize(width: 58.0, height: 28.0))
textNode.frame = CGRect(origin: CGPoint(), size: textSize)
}
self.dotNode = ASImageNode()
self.dotNode?.displaysAsynchronously = false
self.dotNode?.displayWithoutProcessing = true
self.dotNode?.image = generateFilledCircleImage(diameter: 8.0, color: UIColor(rgb: 0xff3b30))
}
super.init()
@ -60,6 +86,11 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
self.addSubnode(self.containerNode)
if rec, let textNode = self.textNode, let dotNode = self.dotNode {
self.extractedContainerNode.contentNode.addSubnode(textNode)
self.extractedContainerNode.contentNode.addSubnode(dotNode)
}
self.containerNode.shouldBegin = { [weak self] location in
guard let strongSelf = self, let _ = strongSelf.contextAction else {
return false
@ -75,7 +106,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
self.iconNode.image = optionsButtonImage(dark: false)
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: rec ? 58.0 : 28.0, height: 28.0))
self.extractedContainerNode.frame = self.containerNode.bounds
self.extractedContainerNode.contentRect = self.containerNode.bounds
self.iconNode.frame = self.containerNode.bounds
@ -96,10 +127,29 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
override func didLoad() {
super.didLoad()
self.view.isOpaque = false
if let dotNode = self.dotNode {
let animation = CAKeyframeAnimation(keyPath: "opacity")
animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.0 as NSNumber]
animation.keyTimes = [0.0 as NSNumber, 0.4546 as NSNumber, 0.9091 as NSNumber, 1 as NSNumber]
animation.duration = 0.5
animation.autoreverses = true
animation.repeatCount = Float.infinity
dotNode.layer.add(animation, forKey: "recording")
}
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 28.0, height: 28.0)
return CGSize(width: self.dotNode != nil ? 58.0 : 28.0, height: 28.0)
}
override func layout() {
super.layout()
if let dotNode = self.dotNode, let textNode = self.textNode {
dotNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 8.0, height: 8.0))
textNode.frame = CGRect(origin: CGPoint(x: 22.0, y: 7.0), size: textNode.frame.size)
}
}
func onLayout() {

View File

@ -8,9 +8,9 @@ import ContextUI
final class VoiceChatVolumeContextItem: ContextMenuCustomItem {
private let value: CGFloat
private let valueChanged: (CGFloat) -> Void
private let valueChanged: (CGFloat, Bool) -> Void
init(value: CGFloat, valueChanged: @escaping (CGFloat) -> Void) {
init(value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.value = value
self.valueChanged = valueChanged
}
@ -38,11 +38,11 @@ private final class VoiceChatVolumeContextItemNode: ASDisplayNode, ContextMenuCu
}
}
private let valueChanged: (CGFloat) -> Void
private let valueChanged: (CGFloat, Bool) -> Void
private let hapticFeedback = HapticFeedback()
init(presentationData: PresentationData, getController: @escaping () -> ContextController?, value: CGFloat, valueChanged: @escaping (CGFloat) -> Void) {
init(presentationData: PresentationData, getController: @escaping () -> ContextController?, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.presentationData = presentationData
self.value = value
self.valueChanged = valueChanged
@ -169,7 +169,12 @@ private final class VoiceChatVolumeContextItemNode: ASDisplayNode, ContextMenuCu
} else if self.value == 0.0 && previousValue != 0.0 {
self.hapticFeedback.impact(.soft)
}
case .ended, .cancelled:
self.valueChanged(self.value, false)
case .ended:
let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
let delta = translation / self.bounds.width * 2.0
self.value = max(0.0, min(2.0, self.value + delta))
self.valueChanged(self.value, true)
break
default:
break

View File

@ -88,23 +88,28 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
loop: for participant in participants {
switch participant {
case let .groupCallParticipant(flags, userId, date, activeDate, source):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else {
continue loop
}
let muted = (flags & (1 << 0)) != 0
let mutedByYou = (flags & (1 << 9)) != 0
var muteState: GroupCallParticipantsContext.Participant.MuteState?
if (flags & (1 << 0)) != 0 {
if muted {
let canUnmute = (flags & (1 << 2)) != 0
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
} else if mutedByYou {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
}
parsedParticipants.append(GroupCallParticipantsContext.Participant(
peer: peer,
ssrc: ssrc,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState
muteState: muteState,
volume: volume
))
}
}
@ -223,23 +228,28 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
loop: for participant in participants {
switch participant {
case let .groupCallParticipant(flags, userId, date, activeDate, source):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else {
continue loop
}
let muted = (flags & (1 << 0)) != 0
let mutedByYou = (flags & (1 << 9)) != 0
var muteState: GroupCallParticipantsContext.Participant.MuteState?
if (flags & (1 << 0)) != 0 {
if muted {
let canUnmute = (flags & (1 << 2)) != 0
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
} else if mutedByYou {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
}
parsedParticipants.append(GroupCallParticipantsContext.Participant(
peer: peer,
ssrc: ssrc,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState
muteState: muteState,
volume: volume
))
}
}
@ -544,9 +554,11 @@ public final class GroupCallParticipantsContext {
public struct Participant: Equatable, Comparable {
public struct MuteState: Equatable {
public var canUnmute: Bool
public var mutedByYou: Bool
public init(canUnmute: Bool) {
public init(canUnmute: Bool, mutedByYou: Bool) {
self.canUnmute = canUnmute
self.mutedByYou = mutedByYou
}
}
@ -555,19 +567,22 @@ public final class GroupCallParticipantsContext {
public var joinTimestamp: Int32
public var activityTimestamp: Double?
public var muteState: MuteState?
public var volume: Int32?
public init(
peer: Peer,
ssrc: UInt32,
joinTimestamp: Int32,
activityTimestamp: Double?,
muteState: MuteState?
muteState: MuteState?,
volume: Int32?
) {
self.peer = peer
self.ssrc = ssrc
self.joinTimestamp = joinTimestamp
self.activityTimestamp = activityTimestamp
self.muteState = muteState
self.volume = volume
}
public static func ==(lhs: Participant, rhs: Participant) -> Bool {
@ -586,6 +601,9 @@ public final class GroupCallParticipantsContext {
if lhs.muteState != rhs.muteState {
return false
}
if lhs.volume != rhs.volume {
return false
}
return true
}
@ -626,12 +644,16 @@ public final class GroupCallParticipantsContext {
private struct OverlayState: Equatable {
struct MuteStateChange: Equatable {
var state: Participant.MuteState?
var volume: Int32?
var disposable: Disposable
static func ==(lhs: MuteStateChange, rhs: MuteStateChange) -> Bool {
if lhs.state != rhs.state {
return false
}
if lhs.volume != rhs.volume {
return false
}
if lhs.disposable !== rhs.disposable {
return false
}
@ -669,6 +691,7 @@ public final class GroupCallParticipantsContext {
public var activityTimestamp: Double?
public var muteState: Participant.MuteState?
public var participationStatusChange: ParticipationStatusChange
public var volume: Int32?
}
public var participantUpdates: [ParticipantUpdate]
@ -716,6 +739,7 @@ public final class GroupCallParticipantsContext {
for i in 0 ..< publicState.participants.count {
if let pendingMuteState = state.overlayState.pendingMuteStateChanges[publicState.participants[i].peer.id] {
publicState.participants[i].muteState = pendingMuteState.state
publicState.participants[i].volume = pendingMuteState.volume
}
}
return publicState
@ -1009,7 +1033,8 @@ public final class GroupCallParticipantsContext {
ssrc: participantUpdate.ssrc,
joinTimestamp: participantUpdate.joinTimestamp,
activityTimestamp: activityTimestamp,
muteState: participantUpdate.muteState
muteState: participantUpdate.muteState,
volume: participantUpdate.volume
)
updatedParticipants.append(participant)
}
@ -1067,7 +1092,7 @@ public final class GroupCallParticipantsContext {
}))
}
public func updateMuteState(peerId: PeerId, muteState: Participant.MuteState?) {
public func updateMuteState(peerId: PeerId, muteState: Participant.MuteState?, volume: Int32?) {
if let current = self.stateValue.overlayState.pendingMuteStateChanges[peerId] {
if current.state == muteState {
return
@ -1078,7 +1103,7 @@ public final class GroupCallParticipantsContext {
for participant in self.stateValue.state.participants {
if participant.peer.id == peerId {
if participant.muteState == muteState {
if participant.muteState == muteState && participant.volume == volume {
return
}
}
@ -1087,6 +1112,7 @@ public final class GroupCallParticipantsContext {
let disposable = MetaDisposable()
self.stateValue.overlayState.pendingMuteStateChanges[peerId] = OverlayState.MuteStateChange(
state: muteState,
volume: volume,
disposable: disposable
)
@ -1102,11 +1128,13 @@ public final class GroupCallParticipantsContext {
return .single(nil)
}
var flags: Int32 = 0
if let muteState = muteState, (!muteState.canUnmute || peerId == account.peerId) {
if let muteState = muteState, (!muteState.canUnmute || peerId == account.peerId || muteState.mutedByYou) {
flags |= 1 << 0
} else if let _ = volume {
flags |= 1 << 1
}
return account.network.request(Api.functions.phone.editGroupCallMember(flags: flags, call: .inputGroupCall(id: id, accessHash: accessHash), userId: inputUser))
return account.network.request(Api.functions.phone.editGroupCallMember(flags: flags, call: .inputGroupCall(id: id, accessHash: accessHash), userId: inputUser, volume: volume))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
@ -1152,7 +1180,6 @@ public final class GroupCallParticipantsContext {
}
self.stateValue.state.defaultParticipantsAreMuted.isMuted = isMuted
self.updateDefaultMuteDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 0, call: .inputGroupCall(id: self.id, accessHash: self.accessHash), joinMuted: isMuted ? .boolTrue : .boolFalse))
|> deliverOnMainQueue).start(next: { [weak self] updates in
guard let strongSelf = self else {
@ -1166,13 +1193,17 @@ public final class GroupCallParticipantsContext {
extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
init(_ apiParticipant: Api.GroupCallParticipant) {
switch apiParticipant {
case let .groupCallParticipant(flags, userId, date, activeDate, source):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
let muted = (flags & (1 << 0)) != 0
let mutedByYou = (flags & (1 << 9)) != 0
var muteState: GroupCallParticipantsContext.Participant.MuteState?
if (flags & (1 << 0)) != 0 {
if muted {
let canUnmute = (flags & (1 << 2)) != 0
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
} else if mutedByYou {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
}
let isRemoved = (flags & (1 << 1)) != 0
let justJoined = (flags & (1 << 4)) != 0
@ -1192,7 +1223,8 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState,
participationStatusChange: participationStatusChange
participationStatusChange: participationStatusChange,
volume: volume
)
}
}
@ -1203,13 +1235,17 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
var participantUpdates: [GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate] = []
for participant in participants {
switch participant {
case let .groupCallParticipant(flags, userId, date, activeDate, source):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, mutedCnt):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
let muted = (flags & (1 << 0)) != 0
let mutedByYou = (flags & (1 << 9)) != 0
var muteState: GroupCallParticipantsContext.Participant.MuteState?
if (flags & (1 << 0)) != 0 {
if muted {
let canUnmute = (flags & (1 << 2)) != 0
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute, mutedByYou: mutedByYou)
} else if mutedByYou {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
}
let isRemoved = (flags & (1 << 1)) != 0
let justJoined = (flags & (1 << 4)) != 0
@ -1229,7 +1265,8 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState,
participationStatusChange: participationStatusChange
participationStatusChange: participationStatusChange,
volume: volume
))
}
}

View File

@ -25,26 +25,6 @@ public final class FoundStickerItem: Equatable {
}
}
extension MutableCollection {
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
let i = index(firstUnshuffled, offsetBy: d)
swapAt(firstUnshuffled, i)
}
}
}
extension Sequence {
func shuffled() -> [Element] {
var result = Array(self)
result.shuffle()
return result
}
}
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200)
public struct SearchStickersScope: OptionSet {
@ -97,8 +77,8 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) {
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
if !currentItems.contains(file.fileId) {
for case let .Sticker(sticker) in file.attributes {
if sticker.displayText.hasPrefix(query) {
for case let .Sticker(displayText, _, _) in file.attributes {
if displayText.hasPrefix(query) {
matchingRecentItemsIds.insert(file.fileId)
}
recentItemsIds.insert(file.fileId)

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 122
return 123
}
public func parseMessage(_ data: Data!) -> Any! {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -504,7 +504,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break
}
}
case let .groupPhoneCall(callId, accessHash, _), let .inviteToGroupPhoneCall(callId, accessHash, _):
case .groupPhoneCall, .inviteToGroupPhoneCall:
if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall {
strongSelf.context.joinGroupCall(peerId: message.id.peerId, activeCall: CachedChannelData.ActiveCall(id: activeCall.id, accessHash: activeCall.accessHash))
} else {
@ -556,7 +556,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: { [weak self] in
}, completed: {
dismissStatus?()
}))
}
@ -708,7 +708,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: message.text, snapshots: snapshots, transitionCompletion: {
legacyMediaEditor(context: strongSelf.context, peer: peer, media: mediaReference, initialCaption: "", snapshots: snapshots, transitionCompletion: {
transitionCompletion()
}, presentStickers: { [weak self] completion in
if let strongSelf = self {
@ -854,9 +854,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, navigateToMessage: { [weak self] fromId, id in
self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId)
}, navigateToMessageStandalone: { [weak self] id in
guard let strongSelf = self else {
return
}
self?.navigateToMessage(from: nil, to: .id(id), forceInCurrentChat: false)
}, tapMessage: nil, clickThroughMessage: { [weak self] in
self?.chatDisplayNode.dismissInput()

View File

@ -65,6 +65,26 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation
return updates
}
struct StickersSearchConfiguration {
static var defaultValue: StickersSearchConfiguration {
return StickersSearchConfiguration(disableLocalSuggestions: false)
}
public let disableLocalSuggestions: Bool
fileprivate init(disableLocalSuggestions: Bool) {
self.disableLocalSuggestions = disableLocalSuggestions
}
static func with(appConfiguration: AppConfiguration) -> StickersSearchConfiguration {
if let data = appConfiguration.data, let suggestOnlyApi = data["stickers_emoji_suggest_only_api"] as? Bool {
return StickersSearchConfiguration(disableLocalSuggestions: suggestOnlyApi)
} else {
return .defaultValue
}
}
}
private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer, chatLocation: ChatLocation, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?, requestBotLocationStatus: @escaping (PeerId) -> Void) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
switch inputQuery {
case let .emoji(query):
@ -79,18 +99,30 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
} else {
signal = .single({ _ in return .stickers([]) })
}
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in
let stickerConfiguration = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { preferencesView -> StickersSearchConfiguration in
let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
return StickersSearchConfiguration.with(appConfiguration: appConfiguration)
}
let stickerSettings = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in
let stickerSettings: StickerSettings = (transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings) as? StickerSettings) ?? .defaultSettings
return stickerSettings
}
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = combineLatest(stickerConfiguration, stickerSettings)
|> castError(ChatContextQueryError.self)
|> mapToSignal { stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
|> mapToSignal { stickerConfiguration, stickerSettings -> Signal<[FoundStickerItem], ChatContextQueryError> in
let scope: SearchStickersScope
switch stickerSettings.emojiStickerSuggestionMode {
case .none:
scope = []
case .all:
if stickerConfiguration.disableLocalSuggestions {
scope = [.remote]
} else {
scope = [.installed, .remote]
}
case .installed:
scope = [.installed]
}

View File

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

View File

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

View File

@ -936,6 +936,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
var displayLeave = !channel.flags.contains(.isCreator)
var canViewStats = false
var hasDiscussion = false
var hasVoiceChat = false
if let cachedChannelData = cachedData as? CachedChannelData {
canViewStats = cachedChannelData.flags.contains(.canViewStats)
}
@ -952,6 +953,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
result.append(.addMember)
}
if channel.flags.contains(.hasVoiceChat) {
hasVoiceChat = true
}
}
switch channel.participationStatus {
case .member:
@ -963,6 +967,9 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
displayLeave = false
}
result.append(.mute)
if hasVoiceChat {
result.append(.voiceChat)
}
if hasDiscussion {
result.append(.discussion)
}

View File

@ -25,6 +25,7 @@ enum PeerInfoHeaderButtonKey: Hashable {
case discussion
case call
case videoCall
case voiceChat
case mute
case more
case addMember
@ -36,6 +37,7 @@ enum PeerInfoHeaderButtonIcon {
case message
case call
case videoCall
case voiceChat
case mute
case unmute
case more
@ -112,6 +114,8 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
imageName = "Peer Info/ButtonCall"
case .videoCall:
imageName = "Peer Info/ButtonVideo"
case .voiceChat:
imageName = "Peer Info/ButtonVoiceChat"
case .mute:
imageName = "Peer Info/ButtonMute"
case .unmute:
@ -3273,6 +3277,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
case .videoCall:
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
buttonIcon = .videoCall
case .voiceChat:
buttonText = presentationData.strings.PeerInfo_ButtonVoiceChat
buttonIcon = .voiceChat
case .mute:
if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState {
buttonText = presentationData.strings.PeerInfo_ButtonUnmute

View File

@ -2850,6 +2850,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.requestCall(isVideo: false)
case .videoCall:
self.requestCall(isVideo: true)
case .voiceChat:
if let cachedData = self.data?.cachedData as? CachedChannelData, let activeCall = cachedData.activeCall {
self.context.joinGroupCall(peerId: self.peerId, activeCall: activeCall)
}
case .mute:
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start()

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