mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
2c4cb11524
26 changed files with 673 additions and 156 deletions
|
|
@ -1 +1 @@
|
|||
gCh0ST/jBZ+NM8mvcBcsd12A5FMFT4q6fETcWd5elO0=
|
||||
E65Wt9QZyVD8tvGhCJD3My6x57eDORYaiYh6HR7T3fI=
|
||||
|
|
|
|||
|
|
@ -186,17 +186,20 @@ public struct PresentationGroupCallSummaryState: Equatable {
|
|||
public var participantCount: Int
|
||||
public var callState: PresentationGroupCallState
|
||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
public var numberOfActiveSpeakers: Int
|
||||
|
||||
public init(
|
||||
info: GroupCallInfo,
|
||||
participantCount: Int,
|
||||
callState: PresentationGroupCallState,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
numberOfActiveSpeakers: Int
|
||||
) {
|
||||
self.info = info
|
||||
self.participantCount = participantCount
|
||||
self.callState = callState
|
||||
self.topParticipants = topParticipants
|
||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,6 +224,25 @@ public enum PresentationGroupCallMuteAction: Equatable {
|
|||
case unmuted
|
||||
}
|
||||
|
||||
public struct PresentationGroupCallMembers: Equatable {
|
||||
public var participants: [GroupCallParticipantsContext.Participant]
|
||||
public var speakingParticipants: Set<PeerId>
|
||||
public var totalCount: Int
|
||||
public var loadMoreToken: String?
|
||||
|
||||
public init(
|
||||
participants: [GroupCallParticipantsContext.Participant],
|
||||
speakingParticipants: Set<PeerId>,
|
||||
totalCount: Int,
|
||||
loadMoreToken: String?
|
||||
) {
|
||||
self.participants = participants
|
||||
self.speakingParticipants = speakingParticipants
|
||||
self.totalCount = totalCount
|
||||
self.loadMoreToken = loadMoreToken
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PresentationGroupCall: class {
|
||||
var account: Account { get }
|
||||
var accountContext: AccountContext { get }
|
||||
|
|
@ -232,7 +254,7 @@ public protocol PresentationGroupCall: class {
|
|||
var canBeRemoved: Signal<Bool, NoError> { get }
|
||||
var state: Signal<PresentationGroupCallState, NoError> { get }
|
||||
var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> { get }
|
||||
var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> { get }
|
||||
var members: Signal<PresentationGroupCallMembers?, NoError> { get }
|
||||
var audioLevels: Signal<[(PeerId, Float)], NoError> { get }
|
||||
var myAudioLevel: Signal<Float, NoError> { get }
|
||||
var isMuted: Signal<Bool, NoError> { get }
|
||||
|
|
|
|||
|
|
@ -1343,18 +1343,27 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
var online = false
|
||||
var animateOnline = false
|
||||
var onlineIsVoiceChat = false
|
||||
|
||||
let peerRevealOptions: [ItemListRevealOption]
|
||||
let peerLeftRevealOptions: [ItemListRevealOption]
|
||||
switch item.content {
|
||||
case let .peer(_, renderedPeer, _, _, presence, _ ,_ ,_, _, _, displayAsMessage, _):
|
||||
if !displayAsMessage, let peer = renderedPeer.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId {
|
||||
var updatedPresence = TelegramUserPresence(status: presence.status, lastActivity: 0)
|
||||
let relativeStatus = relativeUserPresenceStatus(updatedPresence, relativeTo: timestamp)
|
||||
if case .online = relativeStatus {
|
||||
online = true
|
||||
if !displayAsMessage {
|
||||
if let peer = renderedPeer.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId {
|
||||
var updatedPresence = TelegramUserPresence(status: presence.status, lastActivity: 0)
|
||||
let relativeStatus = relativeUserPresenceStatus(updatedPresence, relativeTo: timestamp)
|
||||
if case .online = relativeStatus {
|
||||
online = true
|
||||
}
|
||||
animateOnline = true
|
||||
} else if let channel = renderedPeer.peer as? TelegramChannel {
|
||||
onlineIsVoiceChat = true
|
||||
if channel.flags.contains(.hasVoiceChat) {
|
||||
online = true
|
||||
animateOnline = true
|
||||
}
|
||||
}
|
||||
animateOnline = true
|
||||
}
|
||||
|
||||
let isPinned = item.index.pinningIndex != nil
|
||||
|
|
@ -1385,7 +1394,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
peerLeftRevealOptions = []
|
||||
}
|
||||
|
||||
let (onlineLayout, onlineApply) = onlineLayout(online)
|
||||
let (onlineLayout, onlineApply) = onlineLayout(online, onlineIsVoiceChat)
|
||||
var animateContent = false
|
||||
if let currentItem = currentItem, currentItem.content.chatLocation == item.content.chatLocation {
|
||||
animateContent = true
|
||||
|
|
|
|||
|
|
@ -993,7 +993,7 @@ public final class ChatListNode: ListView {
|
|||
var cachedResult: [PeerId: [(Peer, PeerInputActivity)]] = [:]
|
||||
previousPeerCache.with { dict -> Void in
|
||||
for (chatPeerId, activities) in activitiesByPeerId {
|
||||
if chatPeerId.threadId != nil {
|
||||
guard case .global = chatPeerId.category else {
|
||||
continue
|
||||
}
|
||||
var cachedChatResult: [(Peer, PeerInputActivity)] = []
|
||||
|
|
@ -1015,7 +1015,7 @@ public final class ChatListNode: ListView {
|
|||
var result: [PeerId: [(Peer, PeerInputActivity)]] = [:]
|
||||
var peerCache: [PeerId: Peer] = [:]
|
||||
for (chatPeerId, activities) in activitiesByPeerId {
|
||||
if chatPeerId.threadId != nil {
|
||||
guard case .global = chatPeerId.category else {
|
||||
continue
|
||||
}
|
||||
var chatResult: [(Peer, PeerInputActivity)] = []
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
|
|||
badgeSize += max(currentBadgeBackgroundImage.size.width, badgeLayout.size.width + 10.0) + 5.0
|
||||
}
|
||||
|
||||
let (onlineLayout, onlineApply) = onlineLayout(online)
|
||||
let (onlineLayout, onlineApply) = onlineLayout(online, false)
|
||||
var animateContent = false
|
||||
if let currentItem = currentItem, currentItem.peer.id == item.peer.id {
|
||||
animateContent = true
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ public final class PeerOnlineMarkerNode: ASDisplayNode {
|
|||
self.iconNode.image = image
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (Bool) -> (CGSize, (Bool) -> Void) {
|
||||
return { [weak self] online in
|
||||
public func asyncLayout() -> (Bool, Bool) -> (CGSize, (Bool) -> Void) {
|
||||
return { [weak self] online, isVoiceChat in
|
||||
return (CGSize(width: 14.0, height: 14.0), { animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.iconNode.frame = CGRect(x: 0.0, y: 0.0, width: 14.0, height: 14.0)
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ public final class SelectablePeerNode: ASDisplayNode {
|
|||
self.avatarNode.setPeer(context: context, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, synchronousLoad: synchronousLoad)
|
||||
|
||||
let onlineLayout = self.onlineNode.asyncLayout()
|
||||
let (onlineSize, onlineApply) = onlineLayout(online)
|
||||
let (onlineSize, onlineApply) = onlineLayout(online, false)
|
||||
let _ = onlineApply(false)
|
||||
|
||||
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(theme, state: .panel))
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ public struct TelegramChannelFlags: OptionSet {
|
|||
public static let isCreator = TelegramChannelFlags(rawValue: 1 << 1)
|
||||
public static let isScam = TelegramChannelFlags(rawValue: 1 << 2)
|
||||
public static let hasGeo = TelegramChannelFlags(rawValue: 1 << 3)
|
||||
public static let hasVoiceChat = TelegramChannelFlags(rawValue: 1 << 4)
|
||||
}
|
||||
|
||||
public final class TelegramChannel: Peer {
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) }
|
||||
dict[411017418] = { return Api.SecureValue.parse_secureValue($0) }
|
||||
dict[-316748368] = { return Api.SecureValueHash.parse_secureValueHash($0) }
|
||||
dict[-1738792825] = { return Api.phone.GroupCall.parse_groupCall($0) }
|
||||
dict[1722485756] = { return Api.phone.GroupCall.parse_groupCall($0) }
|
||||
dict[-398136321] = { return Api.messages.SearchCounter.parse_searchCounter($0) }
|
||||
dict[-2128698738] = { return Api.auth.CheckedPhone.parse_checkedPhone($0) }
|
||||
dict[-1188055347] = { return Api.PageListItem.parse_pageListItemText($0) }
|
||||
|
|
|
|||
|
|
@ -1649,21 +1649,16 @@ public struct photos {
|
|||
public extension Api {
|
||||
public struct phone {
|
||||
public enum GroupCall: TypeConstructorDescription {
|
||||
case groupCall(call: Api.GroupCall, sources: [Int32], participants: [Api.GroupCallParticipant], participantsNextOffset: String, users: [Api.User])
|
||||
case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .groupCall(let call, let sources, let participants, let participantsNextOffset, let users):
|
||||
case .groupCall(let call, let participants, let participantsNextOffset, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1738792825)
|
||||
buffer.appendInt32(1722485756)
|
||||
}
|
||||
call.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sources.count))
|
||||
for item in sources {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(participants.count))
|
||||
for item in participants {
|
||||
item.serialize(buffer, true)
|
||||
|
|
@ -1680,8 +1675,8 @@ public struct phone {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .groupCall(let call, let sources, let participants, let participantsNextOffset, let users):
|
||||
return ("groupCall", [("call", call), ("sources", sources), ("participants", participants), ("participantsNextOffset", participantsNextOffset), ("users", users)])
|
||||
case .groupCall(let call, let participants, let participantsNextOffset, let users):
|
||||
return ("groupCall", [("call", call), ("participants", participants), ("participantsNextOffset", participantsNextOffset), ("users", users)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1690,27 +1685,22 @@ public struct phone {
|
|||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.GroupCall
|
||||
}
|
||||
var _2: [Int32]?
|
||||
var _2: [Api.GroupCallParticipant]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
|
||||
}
|
||||
var _3: [Api.GroupCallParticipant]?
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
|
||||
}
|
||||
var _4: String?
|
||||
_4 = parseString(reader)
|
||||
var _5: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.phone.GroupCall.groupCall(call: _1!, sources: _2!, participants: _3!, participantsNextOffset: _4!, users: _5!)
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, users: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
@ -7372,13 +7362,23 @@ public extension Api {
|
|||
})
|
||||
}
|
||||
|
||||
public static func getGroupParticipants(call: Api.InputGroupCall, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupParticipants>) {
|
||||
public static func getGroupParticipants(call: Api.InputGroupCall, ids: [Int32], sources: [Int32], offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupParticipants>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1374089052)
|
||||
buffer.appendInt32(-906898811)
|
||||
call.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(ids.count))
|
||||
for item in ids {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sources.count))
|
||||
for item in sources {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
serializeString(offset, buffer: buffer, boxed: false)
|
||||
serializeInt32(limit, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", call), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in
|
||||
return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", call), ("ids", ids), ("sources", sources), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.phone.GroupParticipants?
|
||||
if let signature = reader.readInt32() {
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
info: summary.info,
|
||||
topParticipants: summary.topParticipants,
|
||||
participantCount: summary.participantCount,
|
||||
numberOfActiveSpeakers: summary.numberOfActiveSpeakers,
|
||||
groupCall: call
|
||||
)
|
||||
}
|
||||
|
|
@ -304,21 +305,59 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
guard let activeCall = activeCall else {
|
||||
return .single(nil)
|
||||
}
|
||||
return getCurrentGroupCall(account: context.account, callId: activeCall.id, accessHash: activeCall.accessHash)
|
||||
|> `catch` { _ -> Signal<GroupCallSummary?, NoError> in
|
||||
return getGroupCallParticipants(account: context.account, callId: activeCall.id, accessHash: activeCall.accessHash, offset: "", limit: 10)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { summary -> GroupCallPanelData? in
|
||||
guard let summary = summary else {
|
||||
return nil
|
||||
|> mapToSignal { initialState -> Signal<GroupCallPanelData?, NoError> in
|
||||
guard let initialState = initialState else {
|
||||
return .single(nil)
|
||||
}
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: summary.info,
|
||||
topParticipants: summary.topParticipants,
|
||||
participantCount: summary.info.participantCount,
|
||||
groupCall: nil
|
||||
)
|
||||
|
||||
return Signal<GroupCallPanelData?, NoError> { subscriber in
|
||||
let participantsContext = QueueLocalObject<GroupCallParticipantsContext>(queue: .mainQueue(), generate: {
|
||||
return GroupCallParticipantsContext(
|
||||
account: context.account,
|
||||
peerId: peerId,
|
||||
id: activeCall.id,
|
||||
accessHash: activeCall.accessHash,
|
||||
state: initialState
|
||||
)
|
||||
})
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
participantsContext.with { participantsContext in
|
||||
disposable.set(combineLatest(queue: .mainQueue(),
|
||||
participantsContext.state,
|
||||
participantsContext.numberOfActiveSpeakers
|
||||
).start(next: { state, numberOfActiveSpeakers in
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
for participant in state.participants {
|
||||
if topParticipants.count >= 3 {
|
||||
break
|
||||
}
|
||||
topParticipants.append(participant)
|
||||
}
|
||||
let data = GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(id: activeCall.id, accessHash: activeCall.accessHash, participantCount: state.totalCount, clientParams: nil),
|
||||
topParticipants: topParticipants,
|
||||
participantCount: state.totalCount,
|
||||
numberOfActiveSpeakers: numberOfActiveSpeakers,
|
||||
groupCall: nil
|
||||
)
|
||||
subscriber.putNext(data)
|
||||
}))
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
disposable.dispose()
|
||||
participantsContext.with { _ in
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(.mainQueue())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
155
submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift
Normal file
155
submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
|
||||
|
||||
private class CallStatusBarBackgroundNodeDrawingState: NSObject {
|
||||
let amplitude: CGFloat
|
||||
|
||||
let speaking: Bool
|
||||
let transitionArguments: (startTime: Double, duration: Double)?
|
||||
|
||||
init(amplitude: CGFloat, speaking: Bool, transitionArguments: (Double, Double)?) {
|
||||
self.amplitude = amplitude
|
||||
self.speaking = speaking
|
||||
self.transitionArguments = transitionArguments
|
||||
}
|
||||
}
|
||||
|
||||
private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||
var muted = true
|
||||
|
||||
var audioLevel: Float = 0.0
|
||||
var presentationAudioLevel: Float = 0.0
|
||||
|
||||
private var animator: ConstantDisplayLinkAnimator?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.isOpaque = false
|
||||
|
||||
self.updateAnimations()
|
||||
}
|
||||
|
||||
func updateAnimations() {
|
||||
let animator: ConstantDisplayLinkAnimator
|
||||
if let current = self.animator {
|
||||
animator = current
|
||||
} else {
|
||||
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
self?.updateAnimations()
|
||||
})
|
||||
animator.frameInterval = 2
|
||||
self.animator = animator
|
||||
}
|
||||
animator.isPaused = true
|
||||
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
|
||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return CallStatusBarBackgroundNodeDrawingState(amplitude: 1.0, speaking: false, transitionArguments: nil)
|
||||
}
|
||||
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
let drawStart = CACurrentMediaTime()
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fill(bounds)
|
||||
}
|
||||
|
||||
guard let parameters = parameters as? CallStatusBarBackgroundNodeDrawingState else {
|
||||
return
|
||||
}
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colors: [CGColor] = [UIColor(rgb: 0x007fff).cgColor, UIColor(rgb:0x00afff).cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: bounds.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||
}
|
||||
}
|
||||
|
||||
public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
public enum Content {
|
||||
case call(PresentationCall)
|
||||
case groupCall(PresentationGroupCall)
|
||||
}
|
||||
|
||||
private let microphoneNode: VoiceChatMicrophoneNode
|
||||
private let backgroundNode: CallStatusBarBackgroundNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let subtitleNode: ImmediateTextNode
|
||||
|
||||
private let audioLevelDisposable = MetaDisposable()
|
||||
|
||||
private var currentSize: CGSize?
|
||||
private var currentContent: Content?
|
||||
|
||||
public override init() {
|
||||
self.backgroundNode = CallStatusBarBackgroundNode()
|
||||
self.microphoneNode = VoiceChatMicrophoneNode()
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.subtitleNode = ImmediateTextNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
// self.addSubnode(self.microphoneNode)
|
||||
// self.addSubnode(self.titleNode)
|
||||
// self.addSubnode(self.subtitleNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.audioLevelDisposable.dispose()
|
||||
}
|
||||
|
||||
public func update(content: Content) {
|
||||
self.currentContent = content
|
||||
self.update()
|
||||
}
|
||||
|
||||
public override func update(size: CGSize) {
|
||||
self.currentSize = size
|
||||
self.update()
|
||||
}
|
||||
|
||||
private func update() {
|
||||
guard let size = self.currentSize, let content = self.currentContent else {
|
||||
return
|
||||
}
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Voice Chat", font: Font.semibold(13.0), textColor: .white)
|
||||
self.subtitleNode.attributedText = NSAttributedString(string: "2 members", font: Font.regular(13.0), textColor: .white)
|
||||
|
||||
let animationSize: CGFloat = 25.0
|
||||
let iconSpacing: CGFloat = 0.0
|
||||
let spacing: CGFloat = 5.0
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
||||
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
||||
|
||||
let totalWidth = animationSize + iconSpacing + titleSize.width + spacing + subtitleSize.width
|
||||
let horizontalOrigin: CGFloat = floor((size.width - totalWidth) / 2.0)
|
||||
|
||||
let contentHeight: CGFloat = 24.0
|
||||
let verticalOrigin: CGFloat = size.height - contentHeight
|
||||
|
||||
self.microphoneNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin + floor((contentHeight - animationSize) / 2.0)), size: CGSize(width: animationSize, height: animationSize))
|
||||
self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: true, color: UIColor.white), animated: true)
|
||||
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + animationSize + iconSpacing, y: verticalOrigin + floor((contentHeight - titleSize.height) / 2.0)), size: titleSize)
|
||||
self.subtitleNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + animationSize + iconSpacing + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize)
|
||||
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ public final class GroupCallPanelData {
|
|||
public let info: GroupCallInfo
|
||||
public let topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
public let participantCount: Int
|
||||
public let numberOfActiveSpeakers: Int
|
||||
public let groupCall: PresentationGroupCall?
|
||||
|
||||
public init(
|
||||
|
|
@ -33,12 +34,14 @@ public final class GroupCallPanelData {
|
|||
info: GroupCallInfo,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
participantCount: Int,
|
||||
numberOfActiveSpeakers: Int,
|
||||
groupCall: PresentationGroupCall?
|
||||
) {
|
||||
self.peerId = peerId
|
||||
self.info = info
|
||||
self.topParticipants = topParticipants
|
||||
self.participantCount = participantCount
|
||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
||||
self.groupCall = groupCall
|
||||
}
|
||||
}
|
||||
|
|
@ -248,15 +251,28 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||
}
|
||||
|
||||
let membersText: String
|
||||
if summaryState.participantCount == 0 {
|
||||
membersText = strongSelf.strings.PeopleNearby_NoMembers
|
||||
let membersTextIsActive: Bool
|
||||
if summaryState.numberOfActiveSpeakers != 0 {
|
||||
//TODO:localize
|
||||
if summaryState.numberOfActiveSpeakers == 1 {
|
||||
membersText = "1 member speaking"
|
||||
} else {
|
||||
membersText = "\(summaryState.numberOfActiveSpeakers) members speaking"
|
||||
}
|
||||
membersTextIsActive = true
|
||||
} else {
|
||||
membersText = strongSelf.strings.Conversation_StatusMembers(Int32(summaryState.participantCount))
|
||||
if summaryState.participantCount == 0 {
|
||||
membersText = strongSelf.strings.PeopleNearby_NoMembers
|
||||
} else {
|
||||
membersText = strongSelf.strings.Conversation_StatusMembers(Int32(summaryState.participantCount))
|
||||
}
|
||||
membersTextIsActive = false
|
||||
}
|
||||
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? strongSelf.theme.chat.inputPanel.panelControlAccentColor : strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
|
||||
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
||||
if let (size, leftInset, rightInset) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
}
|
||||
|
|
@ -272,15 +288,27 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||
}
|
||||
} else if data.groupCall == nil {
|
||||
let membersText: String
|
||||
if data.participantCount == 0 {
|
||||
membersText = self.strings.PeopleNearby_NoMembers
|
||||
let membersTextIsActive: Bool
|
||||
if data.numberOfActiveSpeakers != 0 {
|
||||
//TODO:localize
|
||||
if data.numberOfActiveSpeakers == 1 {
|
||||
membersText = "1 member speaking"
|
||||
} else {
|
||||
membersText = "\(data.numberOfActiveSpeakers) members speaking"
|
||||
}
|
||||
membersTextIsActive = true
|
||||
} else {
|
||||
membersText = self.strings.Conversation_StatusMembers(Int32(data.participantCount))
|
||||
if data.participantCount == 0 {
|
||||
membersText = self.strings.PeopleNearby_NoMembers
|
||||
} else {
|
||||
membersText = self.strings.Conversation_StatusMembers(Int32(data.participantCount))
|
||||
}
|
||||
membersTextIsActive = false
|
||||
}
|
||||
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? self.theme.chat.inputPanel.panelControlAccentColor : self.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
||||
}
|
||||
|
||||
if let (size, leftInset, rightInset) = self.validLayout {
|
||||
|
|
|
|||
|
|
@ -57,13 +57,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
private struct SummaryParticipantsState: Equatable {
|
||||
public var participantCount: Int
|
||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
public var numberOfActiveSpeakers: Int
|
||||
|
||||
public init(
|
||||
participantCount: Int,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
numberOfActiveSpeakers: Int
|
||||
) {
|
||||
self.participantCount = participantCount
|
||||
self.topParticipants = topParticipants
|
||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -205,6 +208,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
private var audioSessionActiveDisposable: Disposable?
|
||||
private var isAudioSessionActive = false
|
||||
|
||||
private let typingDisposable = MetaDisposable()
|
||||
|
||||
private let _canBeRemoved = Promise<Bool>(false)
|
||||
public var canBeRemoved: Signal<Bool, NoError> {
|
||||
return self._canBeRemoved.get()
|
||||
|
|
@ -222,15 +227,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
return self.statePromise.get()
|
||||
}
|
||||
|
||||
private var membersValue: [PeerId: PresentationGroupCallMemberState] = [:] {
|
||||
private var membersValue: PresentationGroupCallMembers? {
|
||||
didSet {
|
||||
if self.membersValue != oldValue {
|
||||
self.membersPromise.set(self.membersValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
private let membersPromise = ValuePromise<[PeerId: PresentationGroupCallMemberState]>([:])
|
||||
public var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> {
|
||||
private let membersPromise = ValuePromise<PresentationGroupCallMembers?>(nil)
|
||||
public var members: Signal<PresentationGroupCallMembers?, NoError> {
|
||||
return self.membersPromise.get()
|
||||
}
|
||||
|
||||
|
|
@ -256,7 +261,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
|
||||
private var checkCallDisposable: Disposable?
|
||||
private var isCurrentlyConnecting: Bool?
|
||||
|
||||
|
||||
private var myAudioLevelTimer: SwiftSignalKit.Timer?
|
||||
|
||||
public weak var sourcePanel: ASDisplayNode?
|
||||
|
||||
init(
|
||||
|
|
@ -397,7 +404,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
info: infoState.info,
|
||||
participantCount: participantsState.participantCount,
|
||||
callState: callState,
|
||||
topParticipants: participantsState.topParticipants
|
||||
topParticipants: participantsState.topParticipants,
|
||||
numberOfActiveSpeakers: participantsState.numberOfActiveSpeakers
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -419,6 +427,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
self.audioLevelsDisposable.dispose()
|
||||
self.participantsContextStateDisposable.dispose()
|
||||
self.myAudioLevelDisposable.dispose()
|
||||
|
||||
self.myAudioLevelTimer?.invalidate()
|
||||
self.typingDisposable.dispose()
|
||||
}
|
||||
|
||||
private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) {
|
||||
|
|
@ -526,7 +537,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.myAudioLevelPipe.putNext(level)
|
||||
|
||||
let mappedLevel = level * 1.5
|
||||
|
||||
strongSelf.myAudioLevelPipe.putNext(mappedLevel)
|
||||
strongSelf.processMyAudioLevel(level: mappedLevel)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -550,32 +565,39 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
|
||||
let participantsContext = GroupCallParticipantsContext(
|
||||
account: self.accountContext.account,
|
||||
peerId: self.peerId,
|
||||
id: callInfo.id,
|
||||
accessHash: callInfo.accessHash,
|
||||
state: initialState
|
||||
)
|
||||
self.participantsContext = participantsContext
|
||||
self.participantsContextStateDisposable.set((combineLatest(participantsContext.state, self.speakingParticipantsContext.get())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state, speakingParticipants in
|
||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
participantsContext.state,
|
||||
participantsContext.numberOfActiveSpeakers,
|
||||
self.speakingParticipantsContext.get()
|
||||
).start(next: { [weak self] state, numberOfActiveSpeakers, speakingParticipants in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var memberStates: [PeerId: PresentationGroupCallMemberState] = [:]
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
|
||||
var members = PresentationGroupCallMembers(
|
||||
participants: [],
|
||||
speakingParticipants: speakingParticipants,
|
||||
totalCount: 0,
|
||||
loadMoreToken: nil
|
||||
)
|
||||
for participant in state.participants {
|
||||
members.participants.append(participant)
|
||||
|
||||
|
||||
if topParticipants.count < 3 {
|
||||
topParticipants.append(participant)
|
||||
}
|
||||
|
||||
strongSelf.ssrcMapping[participant.ssrc] = participant.peer.id
|
||||
|
||||
memberStates[participant.peer.id] = PresentationGroupCallMemberState(
|
||||
ssrc: participant.ssrc,
|
||||
muteState: participant.muteState,
|
||||
speaking: speakingParticipants.contains(participant.peer.id)
|
||||
)
|
||||
|
||||
if participant.peer.id == strongSelf.accountContext.account.peerId {
|
||||
if let muteState = participant.muteState {
|
||||
strongSelf.stateValue.muteState = muteState
|
||||
|
|
@ -586,13 +608,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
}
|
||||
}
|
||||
}
|
||||
strongSelf.membersValue = memberStates
|
||||
|
||||
members.totalCount = state.totalCount
|
||||
members.loadMoreToken = state.nextParticipantsFetchOffset
|
||||
|
||||
strongSelf.membersValue = members
|
||||
|
||||
strongSelf.stateValue.adminIds = state.adminIds
|
||||
|
||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
participantCount: state.totalCount,
|
||||
topParticipants: topParticipants
|
||||
topParticipants: topParticipants,
|
||||
numberOfActiveSpeakers: numberOfActiveSpeakers
|
||||
)))
|
||||
}))
|
||||
|
||||
|
|
@ -813,4 +840,54 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
|
||||
let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
||||
}
|
||||
|
||||
private var currentMyAudioLevel: Float = 0.0
|
||||
private var currentMyAudioLevelTimestamp: Double = 0.0
|
||||
private var isSendingTyping: Bool = false
|
||||
|
||||
private func restartMyAudioLevelTimer() {
|
||||
self.myAudioLevelTimer?.invalidate()
|
||||
let myAudioLevelTimer = SwiftSignalKit.Timer(timeout: 0.1, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.myAudioLevelTimer = nil
|
||||
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
var shouldBeSendingTyping = false
|
||||
if strongSelf.currentMyAudioLevel > 0.01 && timestamp < strongSelf.currentMyAudioLevelTimestamp + 1.0 {
|
||||
strongSelf.restartMyAudioLevelTimer()
|
||||
shouldBeSendingTyping = true
|
||||
} else {
|
||||
if timestamp < strongSelf.currentMyAudioLevelTimestamp + 1.0 {
|
||||
strongSelf.restartMyAudioLevelTimer()
|
||||
shouldBeSendingTyping = true
|
||||
}
|
||||
}
|
||||
if shouldBeSendingTyping != strongSelf.isSendingTyping {
|
||||
strongSelf.isSendingTyping = shouldBeSendingTyping
|
||||
if shouldBeSendingTyping {
|
||||
strongSelf.typingDisposable.set(strongSelf.accountContext.account.acquireLocalInputActivity(peerId: PeerActivitySpace(peerId: strongSelf.peerId, category: .voiceChat), activity: .speakingInGroupCall))
|
||||
strongSelf.restartMyAudioLevelTimer()
|
||||
} else {
|
||||
strongSelf.typingDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
self.myAudioLevelTimer = myAudioLevelTimer
|
||||
myAudioLevelTimer.start()
|
||||
}
|
||||
|
||||
private func processMyAudioLevel(level: Float) {
|
||||
self.currentMyAudioLevel = level
|
||||
|
||||
if level > 0.01 {
|
||||
self.currentMyAudioLevelTimestamp = CACurrentMediaTime()
|
||||
|
||||
if self.myAudioLevelTimer == nil {
|
||||
self.restartMyAudioLevelTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -523,6 +523,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||
context.interpolationQuality = .low
|
||||
|
||||
var appearanceProgress: CGFloat = 1.0
|
||||
var glowScale: CGFloat = 0.75
|
||||
if let transition = parameters.transition, transition.previousState is VoiceChatActionButtonBackgroundNodeConnectingState {
|
||||
appearanceProgress = transition.progress(time: parameters.timestamp)
|
||||
}
|
||||
|
|
@ -535,6 +536,8 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||
gradientTransition = 1.0 - gradientTransition
|
||||
}
|
||||
}
|
||||
glowScale += gradientTransition * 0.3
|
||||
|
||||
gradientImage = gradientTransition.isZero ? blobsState.blueGradient : blobsState.greenGradient
|
||||
if gradientTransition > 0.0 && gradientTransition < 1.0 {
|
||||
gradientImage = generateImage(CGSize(width: 100.0, height: 100.0), contextGenerator: { size, context in
|
||||
|
|
@ -551,11 +554,10 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
context.saveGState()
|
||||
var maskBounds = bounds
|
||||
if let transition = parameters.transition, transition.previousState is VoiceChatActionButtonBackgroundNodeConnectingState {
|
||||
let progress = 1.0 - appearanceProgress
|
||||
maskBounds = maskBounds.insetBy(dx: bounds.width / 3.0 * progress, dy: bounds.width / 3.0 * progress)
|
||||
}
|
||||
|
||||
let progress = 1.0 - (appearanceProgress * glowScale)
|
||||
let maskBounds = bounds.insetBy(dx: bounds.width / 3.0 * progress, dy: bounds.width / 3.0 * progress)
|
||||
|
||||
context.clip(to: maskBounds, mask: radialMaskImage.cgImage!)
|
||||
|
||||
if let gradient = gradientImage?.cgImage {
|
||||
|
|
@ -569,7 +571,8 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
|||
if let blobsState = parameters.state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
for blob in blobsState.blobs {
|
||||
if let path = blob.currentShape, let uiPath = path.copy() as? UIBezierPath {
|
||||
let toOrigin = CGAffineTransform(translationX: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||
let offset = (bounds.size.width - blob.size.width) / 2.0
|
||||
let toOrigin = CGAffineTransform(translationX: -bounds.size.width / 2.0 + offset, y: -bounds.size.height / 2.0 + offset)
|
||||
let fromOrigin = CGAffineTransform(translationX: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
|
||||
uiPath.apply(toOrigin)
|
||||
|
|
|
|||
|
|
@ -272,8 +272,9 @@ public final class VoiceChatController: ViewController {
|
|||
private var didSetContentsReady: Bool = false
|
||||
private var didSetDataReady: Bool = false
|
||||
|
||||
private var currentMembers: [RenderedChannelParticipant]?
|
||||
private var currentMemberStates: [PeerId: PresentationGroupCallMemberState]?
|
||||
private var currentGroupMembers: [RenderedChannelParticipant]?
|
||||
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
|
||||
private var currentSpeakingPeers: Set<PeerId>?
|
||||
private var currentInvitedPeers: Set<PeerId>?
|
||||
|
||||
private var currentEntries: [PeerEntry] = []
|
||||
|
|
@ -470,19 +471,19 @@ public final class VoiceChatController: ViewController {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, members: state.list, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: strongSelf.currentInvitedPeers ?? Set())
|
||||
strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, groupMembers: state.list, callMembers: strongSelf.currentCallMembers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), invitedPeers: strongSelf.currentInvitedPeers ?? Set())
|
||||
}
|
||||
})
|
||||
|
||||
self.memberStatesDisposable = (self.call.members
|
||||
|> deliverOnMainQueue).start(next: { [weak self] memberStates in
|
||||
guard let strongSelf = self else {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] callMembers in
|
||||
guard let strongSelf = self, let callMembers = callMembers else {
|
||||
return
|
||||
}
|
||||
if let members = strongSelf.currentMembers {
|
||||
strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, members: members, memberStates: memberStates, invitedPeers: strongSelf.currentInvitedPeers ?? Set())
|
||||
if let groupMembers = strongSelf.currentGroupMembers {
|
||||
strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, groupMembers: groupMembers, callMembers: callMembers.participants, speakingPeers: callMembers.speakingParticipants ?? Set(), invitedPeers: strongSelf.currentInvitedPeers ?? Set())
|
||||
} else {
|
||||
strongSelf.currentMemberStates = memberStates
|
||||
strongSelf.currentCallMembers = callMembers.participants
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -491,8 +492,8 @@ public final class VoiceChatController: ViewController {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let members = strongSelf.currentMembers {
|
||||
strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, members: members, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: invitedPeers)
|
||||
if let groupMembers = strongSelf.currentGroupMembers {
|
||||
strongSelf.updateMembers(muteState: strongSelf.callState?.muteState, groupMembers: groupMembers, callMembers: strongSelf.currentCallMembers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), invitedPeers: invitedPeers)
|
||||
} else {
|
||||
strongSelf.currentInvitedPeers = invitedPeers
|
||||
}
|
||||
|
|
@ -554,8 +555,8 @@ public final class VoiceChatController: ViewController {
|
|||
}
|
||||
}
|
||||
|
||||
if wasMuted != (state.muteState != nil), let members = strongSelf.currentMembers {
|
||||
strongSelf.updateMembers(muteState: state.muteState, members: members, memberStates: strongSelf.currentMemberStates ?? [:], invitedPeers: strongSelf.currentInvitedPeers ?? Set())
|
||||
if wasMuted != (state.muteState != nil), let groupMembers = strongSelf.currentGroupMembers {
|
||||
strongSelf.updateMembers(muteState: state.muteState, groupMembers: groupMembers, callMembers: strongSelf.currentCallMembers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), invitedPeers: strongSelf.currentInvitedPeers ?? Set())
|
||||
}
|
||||
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
|
|
@ -1094,28 +1095,43 @@ public final class VoiceChatController: ViewController {
|
|||
})
|
||||
}
|
||||
|
||||
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, members: [RenderedChannelParticipant], memberStates: [PeerId: PresentationGroupCallMemberState], invitedPeers: Set<PeerId>) {
|
||||
var members = members
|
||||
members.sort(by: { lhs, rhs in
|
||||
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, groupMembers: [RenderedChannelParticipant], callMembers: [GroupCallParticipantsContext.Participant], speakingPeers: Set<PeerId>, invitedPeers: Set<PeerId>) {
|
||||
var groupMembers = groupMembers
|
||||
groupMembers.sort(by: { lhs, rhs in
|
||||
if lhs.peer.id == self.context.account.peerId {
|
||||
return true
|
||||
} else if rhs.peer.id == self.context.account.peerId {
|
||||
return false
|
||||
}
|
||||
let lhsHasState = memberStates[lhs.peer.id] != nil
|
||||
let rhsHasState = memberStates[rhs.peer.id] != nil
|
||||
if lhsHasState != rhsHasState {
|
||||
if lhsHasState {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
let lhsPresence = lhs.presences[lhs.peer.id]
|
||||
let rhsPresence = lhs.presences[lhs.peer.id]
|
||||
|
||||
if let lhsPresence = lhsPresence as? TelegramUserPresence, let rhsPresence = rhsPresence as? TelegramUserPresence {
|
||||
return lhsPresence.status > rhsPresence.status
|
||||
} else if let _ = lhsPresence as? TelegramUserPresence {
|
||||
return true
|
||||
} else if let _ = rhsPresence as? TelegramUserPresence {
|
||||
return false
|
||||
}
|
||||
|
||||
return lhs.peer.id < rhs.peer.id
|
||||
})
|
||||
|
||||
self.currentMembers = members
|
||||
self.currentMemberStates = memberStates
|
||||
var callMembers = callMembers
|
||||
|
||||
for i in 0 ..< callMembers.count {
|
||||
if callMembers[i].peer.id == self.context.account.peerId {
|
||||
let member = callMembers[i]
|
||||
callMembers.remove(at: i)
|
||||
callMembers.insert(member, at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.currentGroupMembers = groupMembers
|
||||
self.currentCallMembers = callMembers
|
||||
self.currentSpeakingPeers = speakingPeers
|
||||
self.currentInvitedPeers = invitedPeers
|
||||
|
||||
let previousEntries = self.currentEntries
|
||||
|
|
@ -1123,7 +1139,44 @@ public final class VoiceChatController: ViewController {
|
|||
|
||||
var index: Int32 = 0
|
||||
|
||||
for member in members {
|
||||
var processedPeerIds = Set<PeerId>()
|
||||
|
||||
for member in callMembers {
|
||||
if processedPeerIds.contains(member.peer.id) {
|
||||
continue
|
||||
}
|
||||
processedPeerIds.insert(member.peer.id)
|
||||
|
||||
let memberState: PeerEntry.State
|
||||
var memberMuteState: GroupCallParticipantsContext.Participant.MuteState?
|
||||
if member.peer.id == self.context.account.peerId {
|
||||
if muteState == nil {
|
||||
memberState = .speaking
|
||||
} else {
|
||||
memberState = .listening
|
||||
}
|
||||
} else {
|
||||
memberState = speakingPeers.contains(member.peer.id) ? .speaking : .listening
|
||||
memberMuteState = member.muteState
|
||||
}
|
||||
|
||||
entries.append(PeerEntry(
|
||||
peer: member.peer,
|
||||
presence: nil,
|
||||
activityTimestamp: Int32.max - 1 - index,
|
||||
state: memberState,
|
||||
muteState: memberMuteState,
|
||||
invited: false
|
||||
))
|
||||
index += 1
|
||||
}
|
||||
|
||||
for member in groupMembers {
|
||||
if processedPeerIds.contains(member.peer.id) {
|
||||
continue
|
||||
}
|
||||
processedPeerIds.insert(member.peer.id)
|
||||
|
||||
if let user = member.peer as? TelegramUser, user.botInfo != nil || user.isDeleted {
|
||||
continue
|
||||
}
|
||||
|
|
@ -1136,9 +1189,6 @@ public final class VoiceChatController: ViewController {
|
|||
} else {
|
||||
memberState = .listening
|
||||
}
|
||||
} else if let state = memberStates[member.peer.id] {
|
||||
memberState = state.speaking ? .speaking : .listening
|
||||
memberMuteState = state.muteState
|
||||
} else {
|
||||
memberState = .inactive
|
||||
}
|
||||
|
|
|
|||
|
|
@ -624,6 +624,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||
strongSelf.actionButtonNode.addSubnode(animationNode)
|
||||
}
|
||||
animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, color: color), animated: true)
|
||||
strongSelf.actionButtonNode.isUserInteractionEnabled = false
|
||||
} else if let animationNode = strongSelf.animationNode {
|
||||
strongSelf.animationNode = nil
|
||||
animationNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false, completion: { [weak animationNode] _ in
|
||||
|
|
@ -647,6 +648,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||
} else {
|
||||
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: item.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
strongSelf.actionButtonNode.isUserInteractionEnabled = !invited
|
||||
} else if let iconNode = strongSelf.iconNode {
|
||||
strongSelf.iconNode = nil
|
||||
iconNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false, completion: { [weak iconNode] _ in
|
||||
|
|
|
|||
|
|
@ -1221,21 +1221,42 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||
updatedState.readSecretOutbox(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), timestamp: maxDate, actionTimestamp: date)
|
||||
case let .updateUserTyping(userId, type):
|
||||
if let date = updatesDate, date + 60 > serverTime {
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), threadId: nil), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type))
|
||||
let activity = PeerInputActivity(apiType: type)
|
||||
var category: PeerActivitySpace.Category = .global
|
||||
if case .speakingInGroupCall = activity {
|
||||
category = .voiceChat
|
||||
}
|
||||
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), category: category), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: activity)
|
||||
}
|
||||
case let .updateChatUserTyping(chatId, userId, type):
|
||||
if let date = updatesDate, date + 60 > serverTime {
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId), threadId: nil), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type))
|
||||
let activity = PeerInputActivity(apiType: type)
|
||||
var category: PeerActivitySpace.Category = .global
|
||||
if case .speakingInGroupCall = activity {
|
||||
category = .voiceChat
|
||||
}
|
||||
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId), category: category), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: activity)
|
||||
}
|
||||
case let .updateChannelUserTyping(_, channelId, topMsgId, userId, type):
|
||||
if let date = updatesDate, date + 60 > serverTime {
|
||||
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
let threadId = topMsgId.flatMap { makeMessageThreadId(MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: $0)) }
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: channelPeerId, threadId: threadId), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: PeerInputActivity(apiType: type))
|
||||
|
||||
let activity = PeerInputActivity(apiType: type)
|
||||
var category: PeerActivitySpace.Category = .global
|
||||
if case .speakingInGroupCall = activity {
|
||||
category = .voiceChat
|
||||
} else if let threadId = threadId {
|
||||
category = .thread(threadId)
|
||||
}
|
||||
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: channelPeerId, category: category), peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), activity: activity)
|
||||
}
|
||||
case let .updateEncryptedChatTyping(chatId):
|
||||
if let date = updatesDate, date + 60 > serverTime {
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), threadId: nil), peerId: nil, activity: .typingText)
|
||||
updatedState.addPeerInputActivity(chatPeerId: PeerActivitySpace(peerId: PeerId(namespace: Namespaces.Peer.SecretChat, id: chatId), category: .global), peerId: nil, activity: .typingText)
|
||||
}
|
||||
case let .updateDialogPinned(flags, folderId, peer):
|
||||
let groupId: PeerGroupId = folderId.flatMap(PeerGroupId.init(rawValue:)) ?? .root
|
||||
|
|
@ -2342,16 +2363,16 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||
let chatPeerId = message.id.peerId
|
||||
if let authorId = message.authorId {
|
||||
let activityValue: PeerInputActivity? = nil
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] = [authorId: activityValue]
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)] = [authorId: activityValue]
|
||||
} else {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)]![authorId] = activityValue
|
||||
}
|
||||
if let threadId = message.threadId {
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: threadId)] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: threadId)] = [authorId: activityValue]
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .thread(threadId))] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .thread(threadId))] = [authorId: activityValue]
|
||||
} else {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: threadId)]![authorId] = activityValue
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .thread(threadId))]![authorId] = activityValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3230,10 +3251,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||
if let peer = transaction.getPeer(chatPeerId) as? TelegramSecretChat {
|
||||
let authorId = peer.regularPeerId
|
||||
let activityValue: PeerInputActivity? = .typingText
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] = [authorId: activityValue]
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)] = [authorId: activityValue]
|
||||
} else {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)]![authorId] = activityValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3278,10 +3299,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||
|
||||
for (chatPeerId, authorId) in addedSecretMessageAuthorIds {
|
||||
let activityValue: PeerInputActivity? = nil
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)] = [authorId: activityValue]
|
||||
if updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)] == nil {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)] = [authorId: activityValue]
|
||||
} else {
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, threadId: nil)]![authorId] = activityValue
|
||||
updatedTypingActivities[PeerActivitySpace(peerId: chatPeerId, category: .global)]![authorId] = activityValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
|
|||
if (flags & Int32(1 << 21)) != 0 {
|
||||
channelFlags.insert(.hasGeo)
|
||||
}
|
||||
if (flags & Int32(1 << 23)) != 0 {
|
||||
channelFlags.insert(.hasVoiceChat)
|
||||
}
|
||||
|
||||
let restrictionInfo: PeerAccessRestrictionInfo?
|
||||
if let restrictionReason = restrictionReason {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,18 @@ public struct GroupCallInfo: Equatable {
|
|||
public var accessHash: Int64
|
||||
public var participantCount: Int
|
||||
public var clientParams: String?
|
||||
|
||||
public init(
|
||||
id: Int64,
|
||||
accessHash: Int64,
|
||||
participantCount: Int,
|
||||
clientParams: String?
|
||||
) {
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
self.participantCount = participantCount
|
||||
self.clientParams = clientParams
|
||||
}
|
||||
}
|
||||
|
||||
public struct GroupCallSummary: Equatable {
|
||||
|
|
@ -50,7 +62,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
|
|||
}
|
||||
|> mapToSignal { result -> Signal<GroupCallSummary?, GetCurrentGroupCallError> in
|
||||
switch result {
|
||||
case let .groupCall(call, _, participants, _, users):
|
||||
case let .groupCall(call, participants, _, users):
|
||||
return account.postbox.transaction { transaction -> GroupCallSummary? in
|
||||
guard let info = GroupCallInfo(call) else {
|
||||
return nil
|
||||
|
|
@ -165,7 +177,7 @@ public enum GetGroupCallParticipantsError {
|
|||
}
|
||||
|
||||
public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, limit: Int32) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), offset: offset, limit: limit))
|
||||
return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), ids: [], sources: [], offset: offset, limit: limit))
|
||||
|> mapError { _ -> GetGroupCallParticipantsError in
|
||||
return .generic
|
||||
}
|
||||
|
|
@ -341,7 +353,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
|||
state.adminIds = adminIds
|
||||
|
||||
switch result {
|
||||
case let .groupCall(call, sources, _, _, users):
|
||||
case let .groupCall(call, _, _, users):
|
||||
guard let _ = GroupCallInfo(call) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
|
@ -385,7 +397,11 @@ public func leaveGroupCall(account: Account, callId: Int64, accessHash: Int64, s
|
|||
|> mapError { _ -> LeaveGroupCallError in
|
||||
return .generic
|
||||
}
|
||||
|> ignoreValues
|
||||
|> mapToSignal { result -> Signal<Never, LeaveGroupCallError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
public enum StopGroupCallError {
|
||||
|
|
@ -569,20 +585,63 @@ public final class GroupCallParticipantsContext {
|
|||
}
|
||||
}
|
||||
|
||||
private var numberOfActiveSpeakersValue: Int = 0 {
|
||||
didSet {
|
||||
if self.numberOfActiveSpeakersValue != oldValue {
|
||||
self.numberOfActiveSpeakersPromise.set(self.numberOfActiveSpeakersValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
private let numberOfActiveSpeakersPromise = ValuePromise<Int>(0)
|
||||
public var numberOfActiveSpeakers: Signal<Int, NoError> {
|
||||
return self.numberOfActiveSpeakersPromise.get()
|
||||
}
|
||||
|
||||
private var updateQueue: [StateUpdate] = []
|
||||
private var isProcessingUpdate: Bool = false
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
public init(account: Account, id: Int64, accessHash: Int64, state: State) {
|
||||
private let updatesDisposable = MetaDisposable()
|
||||
private var activitiesDisposable: Disposable?
|
||||
|
||||
public init(account: Account, peerId: PeerId, id: Int64, accessHash: Int64, state: State) {
|
||||
self.account = account
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
self.stateValue = InternalState(state: state, overlayState: OverlayState())
|
||||
self.statePromise = ValuePromise<InternalState>(self.stateValue)
|
||||
|
||||
self.updatesDisposable.set((self.account.stateManager.groupCallParticipantUpdates
|
||||
|> deliverOnMainQueue).start(next: { [weak self] updates in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var filteredUpdates: [StateUpdate] = []
|
||||
for (callId, update) in updates {
|
||||
if callId == id {
|
||||
filteredUpdates.append(update)
|
||||
}
|
||||
}
|
||||
if !filteredUpdates.isEmpty {
|
||||
strongSelf.addUpdates(updates: filteredUpdates)
|
||||
}
|
||||
}))
|
||||
|
||||
let activityCategory: PeerActivitySpace.Category = .voiceChat
|
||||
self.activitiesDisposable = (self.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, category: activityCategory))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] activities in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.numberOfActiveSpeakersValue = activities.count
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.updatesDisposable.dispose()
|
||||
self.activitiesDisposable?.dispose()
|
||||
}
|
||||
|
||||
public func addUpdates(updates: [StateUpdate]) {
|
||||
|
|
@ -625,6 +684,8 @@ public final class GroupCallParticipantsContext {
|
|||
return
|
||||
}
|
||||
|
||||
let isVersionUpdate = update.version != self.stateValue.state.version
|
||||
|
||||
let _ = (self.account.postbox.transaction { transaction -> [PeerId: Peer] in
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
|
||||
|
|
@ -648,7 +709,9 @@ public final class GroupCallParticipantsContext {
|
|||
if participantUpdate.isRemoved {
|
||||
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
|
||||
updatedParticipants.remove(at: index)
|
||||
updatedTotalCount -= 1
|
||||
updatedTotalCount = max(0, updatedTotalCount - 1)
|
||||
} else if isVersionUpdate {
|
||||
updatedTotalCount = max(0, updatedTotalCount - 1)
|
||||
}
|
||||
} else {
|
||||
guard let peer = peers[participantUpdate.peerId] else {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,18 @@ import MtProtoKit
|
|||
import SyncCore
|
||||
|
||||
public struct PeerActivitySpace: Hashable {
|
||||
public var peerId: PeerId
|
||||
public var threadId: Int64?
|
||||
public enum Category: Equatable, Hashable {
|
||||
case global
|
||||
case thread(Int64)
|
||||
case voiceChat
|
||||
}
|
||||
|
||||
public init(peerId: PeerId, threadId: Int64?) {
|
||||
public var peerId: PeerId
|
||||
public var category: Category
|
||||
|
||||
public init(peerId: PeerId, category: Category) {
|
||||
self.peerId = peerId
|
||||
self.threadId = threadId
|
||||
self.category = category
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +89,14 @@ func managedLocalTypingActivities(activities: Signal<[PeerActivitySpace: [PeerId
|
|||
}
|
||||
|
||||
for (peerId, activity, disposable) in start {
|
||||
disposable.set(requestActivity(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId.peerId, threadId: peerId.threadId, activity: activity?.activity).start())
|
||||
var threadId: Int64?
|
||||
switch peerId.category {
|
||||
case let .thread(id):
|
||||
threadId = id
|
||||
default:
|
||||
break
|
||||
}
|
||||
disposable.set(requestActivity(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId.peerId, threadId: threadId, activity: activity?.activity).start())
|
||||
}
|
||||
})
|
||||
return ActionDisposable {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ private final class PeerInputActivityContext {
|
|||
record.timer.invalidate()
|
||||
var updateId = record.updateId
|
||||
var recordTimestamp = record.timestamp
|
||||
if record.activity != activity || record.timestamp + 4.0 < timestamp {
|
||||
if record.activity != activity || record.timestamp + 1.0 < timestamp {
|
||||
updated = true
|
||||
updateId = nextUpdateId
|
||||
recordTimestamp = timestamp
|
||||
|
|
@ -329,7 +329,16 @@ final class PeerInputActivityManager {
|
|||
})
|
||||
self.contexts[chatPeerId] = context
|
||||
}
|
||||
context.addActivity(peerId: peerId, activity: activity, timeout: 8.0, episodeId: episodeId, nextUpdateId: &self.nextUpdateId)
|
||||
|
||||
let timeout: Double
|
||||
switch activity {
|
||||
case .speakingInGroupCall:
|
||||
timeout = 3.0
|
||||
default:
|
||||
timeout = 8.0
|
||||
}
|
||||
|
||||
context.addActivity(peerId: peerId, activity: activity, timeout: timeout, episodeId: episodeId, nextUpdateId: &self.nextUpdateId)
|
||||
|
||||
if let globalContext = self.globalContext {
|
||||
let activities = self.collectActivities()
|
||||
|
|
@ -381,7 +390,15 @@ final class PeerInputActivityManager {
|
|||
self?.addActivity(chatPeerId: chatPeerId, peerId: peerId, activity: activity, episodeId: episodeId)
|
||||
}
|
||||
|
||||
let timer = SignalKitTimer(timeout: 5.0, repeat: true, completion: {
|
||||
let timeout: Double
|
||||
switch activity {
|
||||
case .speakingInGroupCall:
|
||||
timeout = 2.0
|
||||
default:
|
||||
timeout = 5.0
|
||||
}
|
||||
|
||||
let timer = SignalKitTimer(timeout: timeout, repeat: true, completion: {
|
||||
update()
|
||||
}, queue: queue)
|
||||
timer.start()
|
||||
|
|
|
|||
|
|
@ -554,7 +554,13 @@ public final class PendingMessageManager {
|
|||
for subscriber in messageContext.statusSubscribers.copyItems() {
|
||||
subscriber(messageContext.status, messageContext.error)
|
||||
}
|
||||
self.addContextActivityIfNeeded(messageContext, peerId: PeerActivitySpace(peerId: id.peerId, threadId: threadId))
|
||||
let activityCategory: PeerActivitySpace.Category
|
||||
if let threadId = threadId {
|
||||
activityCategory = .thread(threadId)
|
||||
} else {
|
||||
activityCategory = .global
|
||||
}
|
||||
self.addContextActivityIfNeeded(messageContext, peerId: PeerActivitySpace(peerId: id.peerId, category: activityCategory))
|
||||
|
||||
let queue = self.queue
|
||||
|
||||
|
|
@ -624,7 +630,15 @@ public final class PendingMessageManager {
|
|||
for subscriber in context.statusSubscribers.copyItems() {
|
||||
subscriber(context.status, context.error)
|
||||
}
|
||||
self.addContextActivityIfNeeded(context, peerId: PeerActivitySpace(peerId: peerId, threadId: context.threadId))
|
||||
|
||||
let activityCategory: PeerActivitySpace.Category
|
||||
if let threadId = context.threadId {
|
||||
activityCategory = .thread(threadId)
|
||||
} else {
|
||||
activityCategory = .global
|
||||
}
|
||||
|
||||
self.addContextActivityIfNeeded(context, peerId: PeerActivitySpace(peerId: peerId, category: activityCategory))
|
||||
context.uploadDisposable.set((uploadSignal
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] next in
|
||||
if let strongSelf = self {
|
||||
|
|
|
|||
|
|
@ -3167,9 +3167,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
let activitySpace: PeerActivitySpace
|
||||
switch self.chatLocation {
|
||||
case let .peer(peerId):
|
||||
activitySpace = PeerActivitySpace(peerId: peerId, threadId: nil)
|
||||
activitySpace = PeerActivitySpace(peerId: peerId, category: .global)
|
||||
case let .replyThread(replyThreadMessage):
|
||||
activitySpace = PeerActivitySpace(peerId: replyThreadMessage.messageId.peerId, threadId: makeMessageThreadId(replyThreadMessage.messageId))
|
||||
activitySpace = PeerActivitySpace(peerId: replyThreadMessage.messageId.peerId, category: .thread(makeMessageThreadId(replyThreadMessage.messageId)))
|
||||
}
|
||||
|
||||
self.inputActivityDisposable = (self.typingActivityPromise.get()
|
||||
|
|
@ -6074,11 +6074,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
|
||||
let postbox = self.context.account.postbox
|
||||
let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:])
|
||||
var activityThreadId: Int64?
|
||||
var activityCategory: PeerActivitySpace.Category = .global
|
||||
if case let .replyThread(replyThreadMessage) = self.chatLocation {
|
||||
activityThreadId = makeMessageThreadId(replyThreadMessage.messageId)
|
||||
activityCategory = .thread(makeMessageThreadId(replyThreadMessage.messageId))
|
||||
}
|
||||
self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, threadId: activityThreadId))
|
||||
self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, category: activityCategory))
|
||||
|> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in
|
||||
var foundAllPeers = true
|
||||
var cachedResult: [(Peer, PeerInputActivity)] = []
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 1a3df12fbe943fe66df86607a5e6347a0db4cf4f
|
||||
Subproject commit 353184e3e5c5e560a59cb836ca8de496c0cc95bb
|
||||
2
third-party/webrtc/webrtc-ios
vendored
2
third-party/webrtc/webrtc-ios
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 7198385cd356994e366ea325c84b34e974c9117e
|
||||
Subproject commit 178f6fb9253851f3c363b2deed0cba433973862b
|
||||
Loading…
Add table
Add a link
Reference in a new issue