mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Group chats update
This commit is contained in:
@@ -85,25 +85,28 @@ private final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
private let foregroundNode: ASImageNode
|
||||
|
||||
private var validSize: CGSize?
|
||||
private var isOn: Bool?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.foregroundNode = ASImageNode()
|
||||
|
||||
self.foregroundNode.image = UIImage(bundleImageName: "Call/VoiceChatMicOff")
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.foregroundNode)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
func updateLayout(size: CGSize, isOn: Bool) {
|
||||
if self.validSize != size {
|
||||
self.validSize = size
|
||||
|
||||
self.backgroundNode.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1C1C1E))
|
||||
}
|
||||
if self.isOn != isOn {
|
||||
self.isOn = isOn
|
||||
self.foregroundNode.image = UIImage(bundleImageName: isOn ? "Call/VoiceChatMicOn" : "Call/VoiceChatMicOff")
|
||||
}
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
if let image = self.foregroundNode.image {
|
||||
@@ -128,8 +131,15 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
private struct PeerEntry: Comparable, Identifiable {
|
||||
enum State {
|
||||
case inactive
|
||||
case listening
|
||||
case speaking
|
||||
}
|
||||
|
||||
var participant: RenderedChannelParticipant
|
||||
var activityTimestamp: Int32
|
||||
var state: State
|
||||
|
||||
var stableId: PeerId {
|
||||
return self.participant.peer.id
|
||||
@@ -145,7 +155,19 @@ public final class VoiceChatController: ViewController {
|
||||
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: Interaction) -> ListViewItem {
|
||||
let peer = self.participant.peer
|
||||
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: context, peer: peer, height: .peerList, presence: self.participant.presences[self.participant.peer.id], text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
|
||||
let text: ItemListPeerItemText
|
||||
switch self.state {
|
||||
case .inactive:
|
||||
text = .presence
|
||||
case .listening:
|
||||
//TODO:localize
|
||||
text = .text("listening", .accent)
|
||||
case .speaking:
|
||||
//TODO:localize
|
||||
text = .text("speaking", .constructive)
|
||||
}
|
||||
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: context, peer: peer, height: .peerList, presence: self.participant.presences[self.participant.peer.id], text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
|
||||
//arguments.deleteIncludePeer(peer.peerId)
|
||||
})]), switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
|
||||
//arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })
|
||||
@@ -166,8 +188,9 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
private weak var controller: VoiceChatController?
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let call: PresentationGroupCall
|
||||
private var presentationData: PresentationData
|
||||
private var darkTheme: PresentationTheme
|
||||
|
||||
@@ -181,23 +204,38 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
private var enqueuedTransitions: [ListTransition] = []
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var didSetContentsReady: Bool = false
|
||||
private var didSetDataReady: Bool = false
|
||||
|
||||
private var currentMembers: [RenderedChannelParticipant]?
|
||||
private var currentMemberStates: [PeerId: PresentationGroupCallMemberState]?
|
||||
|
||||
private var currentEntries: [PeerEntry] = []
|
||||
private var peersDisposable: Disposable?
|
||||
|
||||
private var peerViewDisposable: Disposable?
|
||||
private let leaveDisposable = MetaDisposable()
|
||||
|
||||
private var isMutedDisposable: Disposable?
|
||||
private var callStateDisposable: Disposable?
|
||||
|
||||
private var callState: PresentationGroupCallState?
|
||||
|
||||
private var audioOutputStateDisposable: Disposable?
|
||||
private var audioOutputState: ([AudioSessionOutput], AudioSessionOutput?)?
|
||||
|
||||
private var memberStatesDisposable: Disposable?
|
||||
|
||||
private var itemInteraction: Interaction?
|
||||
|
||||
init(controller: VoiceChatController, context: AccountContext, peerId: PeerId) {
|
||||
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.sharedContext = sharedContext
|
||||
self.context = call.accountContext
|
||||
self.call = call
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
self.darkTheme = defaultDarkPresentationTheme
|
||||
|
||||
self.contentContainer = ASDisplayNode()
|
||||
@@ -212,7 +250,6 @@ public final class VoiceChatController: ViewController {
|
||||
self.leaveNode = CallControllerButtonItemNode()
|
||||
self.actionButton = VoiceChatActionButton()
|
||||
self.statusLabel = ImmediateTextNode()
|
||||
self.statusLabel.attributedText = NSAttributedString(string: "Connecting...", font: Font.regular(17.0), textColor: .white)
|
||||
|
||||
self.radialStatus = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
|
||||
@@ -231,9 +268,24 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.addSubnode(self.contentContainer)
|
||||
|
||||
let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, peerId: self.peerId, updated: { [weak self] state in
|
||||
let (disposable, loadMoreControl) = self.context.peerChannelMemberCategoriesContextsManager.recent(postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, peerId: self.call.peerId, updated: { [weak self] state in
|
||||
Queue.mainQueue().async {
|
||||
self?.updateMembers(members: state.list)
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateMembers(isMuted: strongSelf.callState?.isMuted ?? true, members: state.list, memberStates: strongSelf.currentMemberStates ?? [:])
|
||||
}
|
||||
})
|
||||
|
||||
self.memberStatesDisposable = (self.call.members
|
||||
|> deliverOnMainQueue).start(next: { [weak self] memberStates in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let members = strongSelf.currentMembers {
|
||||
strongSelf.updateMembers(isMuted: strongSelf.callState?.isMuted ?? true, members: members, memberStates: memberStates)
|
||||
} else {
|
||||
strongSelf.currentMemberStates = memberStates
|
||||
}
|
||||
})
|
||||
|
||||
@@ -242,13 +294,13 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
if case let .known(value) = offset, value < 40.0 {
|
||||
strongSelf.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: strongSelf.peerId, control: loadMoreControl)
|
||||
strongSelf.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: strongSelf.call.peerId, control: loadMoreControl)
|
||||
}
|
||||
}
|
||||
|
||||
self.peersDisposable = disposable
|
||||
|
||||
self.peerViewDisposable = (self.context.account.viewTracker.peerView(self.peerId)
|
||||
self.peerViewDisposable = (self.context.account.viewTracker.peerView(self.call.peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] view in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@@ -274,21 +326,135 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
})
|
||||
|
||||
self.callStateDisposable = (self.call.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.callState != state {
|
||||
let wasMuted = strongSelf.callState?.isMuted ?? true
|
||||
strongSelf.callState = state
|
||||
|
||||
if wasMuted != state.isMuted, let members = strongSelf.currentMembers {
|
||||
strongSelf.updateMembers(isMuted: state.isMuted, members: members, memberStates: strongSelf.currentMemberStates ?? [:])
|
||||
}
|
||||
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.audioOutputStateDisposable = (call.audioOutputState
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
if let strongSelf = self {
|
||||
strongSelf.audioOutputState = state
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.leaveNode.addTarget(self, action: #selector(self.leavePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.peersDisposable?.dispose()
|
||||
self.peerViewDisposable?.dispose()
|
||||
self.leaveDisposable.dispose()
|
||||
self.isMutedDisposable?.dispose()
|
||||
self.callStateDisposable?.dispose()
|
||||
self.audioOutputStateDisposable?.dispose()
|
||||
self.memberStatesDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func leavePressed() {
|
||||
self.controller?.dismiss()
|
||||
self.leaveDisposable.set((self.call.leave()
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
self?.controller?.dismiss()
|
||||
}))
|
||||
}
|
||||
|
||||
@objc private func actionButtonPressed() {
|
||||
self.call.toggleIsMuted()
|
||||
}
|
||||
|
||||
@objc private func audioOutputPressed() {
|
||||
guard let (availableOutputs, currentOutput) = self.audioOutputState else {
|
||||
return
|
||||
}
|
||||
guard availableOutputs.count >= 2 else {
|
||||
return
|
||||
}
|
||||
let hasMute = false
|
||||
|
||||
if availableOutputs.count == 2 {
|
||||
for output in availableOutputs {
|
||||
if output != currentOutput {
|
||||
self.call.setCurrentAudioOutput(output)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
for output in availableOutputs {
|
||||
if hasMute, case .builtin = output {
|
||||
continue
|
||||
}
|
||||
let title: String
|
||||
var icon: UIImage?
|
||||
switch output {
|
||||
case .builtin:
|
||||
title = UIDevice.current.model
|
||||
case .speaker:
|
||||
title = self.presentationData.strings.Call_AudioRouteSpeaker
|
||||
icon = generateScaledImage(image: UIImage(bundleImageName: "Call/CallSpeakerButton"), size: CGSize(width: 48.0, height: 48.0), opaque: false)
|
||||
case .headphones:
|
||||
title = self.presentationData.strings.Call_AudioRouteHeadphones
|
||||
case let .port(port):
|
||||
title = port.name
|
||||
if port.type == .bluetooth {
|
||||
var image = UIImage(bundleImageName: "Call/CallBluetoothButton")
|
||||
let portName = port.name.lowercased()
|
||||
if portName.contains("airpods pro") {
|
||||
image = UIImage(bundleImageName: "Call/CallAirpodsProButton")
|
||||
} else if portName.contains("airpods") {
|
||||
image = UIImage(bundleImageName: "Call/CallAirpodsButton")
|
||||
}
|
||||
icon = generateScaledImage(image: image, size: CGSize(width: 48.0, height: 48.0), opaque: false)
|
||||
}
|
||||
}
|
||||
items.append(CallRouteActionSheetItem(title: title, icon: icon, selected: output == currentOutput, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
self?.call.setCurrentAudioOutput(output)
|
||||
}))
|
||||
}
|
||||
|
||||
if hasMute {
|
||||
items.append(CallRouteActionSheetItem(title: self.presentationData.strings.Call_AudioRouteMute, icon: generateScaledImage(image: UIImage(bundleImageName: "Call/CallMuteButton"), size: CGSize(width: 48.0, height: 48.0), opaque: false), selected: self.callState?.isMuted ?? true, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
self?.call.toggleIsMuted()
|
||||
}))
|
||||
}
|
||||
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Call_AudioRouteHide, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
self.controller?.present(actionSheet, in: .window(.calls))
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let isFirstTime = self.validLayout == nil
|
||||
self.validLayout = layout
|
||||
self.validLayout = (layout, navigationHeight)
|
||||
|
||||
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
@@ -308,22 +474,93 @@ public final class VoiceChatController: ViewController {
|
||||
let centralButtonSize = CGSize(width: 144.0, height: 144.0)
|
||||
let sideButtonInset: CGFloat = 27.0
|
||||
|
||||
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.grayDimmed), image: .speaker), text: "audio", transition: .immediate)
|
||||
var audioMode: CallControllerButtonsSpeakerMode = .none
|
||||
//var hasAudioRouteMenu: Bool = false
|
||||
if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput {
|
||||
//hasAudioRouteMenu = availableOutputs.count > 2
|
||||
switch currentOutput {
|
||||
case .builtin:
|
||||
audioMode = .builtin
|
||||
case .speaker:
|
||||
audioMode = .speaker
|
||||
case .headphones:
|
||||
audioMode = .headphones
|
||||
case let .port(port):
|
||||
var type: CallControllerButtonsSpeakerMode.BluetoothType = .generic
|
||||
let portName = port.name.lowercased()
|
||||
if portName.contains("airpods pro") {
|
||||
type = .airpodsPro
|
||||
} else if portName.contains("airpods") {
|
||||
type = .airpods
|
||||
}
|
||||
audioMode = .bluetooth(type)
|
||||
}
|
||||
if availableOutputs.count <= 1 {
|
||||
audioMode = .none
|
||||
}
|
||||
}
|
||||
|
||||
let soundImage: CallControllerButtonItemNode.Content.Image
|
||||
var soundAppearance: CallControllerButtonItemNode.Content.Appearance = .color(.grayDimmed)
|
||||
switch audioMode {
|
||||
case .none, .builtin:
|
||||
soundImage = .speaker
|
||||
case .speaker:
|
||||
soundImage = .speaker
|
||||
soundAppearance = .blurred(isFilled: true)
|
||||
case .headphones:
|
||||
soundImage = .bluetooth
|
||||
case let .bluetooth(type):
|
||||
switch type {
|
||||
case .generic:
|
||||
soundImage = .bluetooth
|
||||
case .airpods:
|
||||
soundImage = .airpods
|
||||
case .airpodsPro:
|
||||
soundImage = .airpodsPro
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: "audio", transition: .immediate)
|
||||
|
||||
//TODO:localize
|
||||
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.redDimmed), image: .end), text: "leave", transition: .immediate)
|
||||
|
||||
transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonInset, y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
|
||||
transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideButtonInset - sideButtonSize.width, y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
|
||||
|
||||
self.actionButton.updateLayout(size: centralButtonSize)
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - centralButtonSize.width) / 2.0), y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
||||
|
||||
var isMicOn = false
|
||||
if let callState = callState {
|
||||
isMicOn = !callState.isMuted
|
||||
|
||||
switch callState.networkState {
|
||||
case .connecting:
|
||||
self.radialStatus.isHidden = false
|
||||
|
||||
self.statusLabel.attributedText = NSAttributedString(string: "Connecting...", font: Font.regular(17.0), textColor: .white)
|
||||
case .connected:
|
||||
self.radialStatus.isHidden = true
|
||||
|
||||
if isMicOn {
|
||||
self.statusLabel.attributedText = NSAttributedString(string: "You're Live", font: Font.regular(17.0), textColor: .white)
|
||||
} else {
|
||||
self.statusLabel.attributedText = NSAttributedString(string: "Unmute", font: Font.regular(17.0), textColor: .white)
|
||||
}
|
||||
}
|
||||
|
||||
let statusSize = self.statusLabel.updateLayout(CGSize(width: layout.size.width, height: .greatestFiniteMagnitude))
|
||||
self.statusLabel.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: actionButtonFrame.maxY + 12.0), size: statusSize)
|
||||
|
||||
self.radialStatus.transitionToState(.progress(color: UIColor(rgb: 0x00ACFF), lineWidth: 3.3, value: nil, cancelEnabled: false), animated: false)
|
||||
self.radialStatus.frame = actionButtonFrame.insetBy(dx: -3.3, dy: -3.3)
|
||||
}
|
||||
|
||||
self.actionButton.updateLayout(size: centralButtonSize, isOn: isMicOn)
|
||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||
|
||||
let statusSize = self.statusLabel.updateLayout(CGSize(width: layout.size.width, height: .greatestFiniteMagnitude))
|
||||
self.statusLabel.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: actionButtonFrame.maxY + 12.0), size: statusSize)
|
||||
|
||||
self.radialStatus.transitionToState(.progress(color: UIColor(rgb: 0x00ACFF), lineWidth: 3.3, value: nil, cancelEnabled: false), animated: false)
|
||||
self.radialStatus.frame = actionButtonFrame.insetBy(dx: -3.3, dy: -3.3)
|
||||
|
||||
if isFirstTime {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
@@ -332,6 +569,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.alpha = 1.0
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
|
||||
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
@@ -344,7 +582,8 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)?) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.alpha = 0.0
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { _ in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
@@ -369,6 +608,8 @@ public final class VoiceChatController: ViewController {
|
||||
if transition.crossFade {
|
||||
options.insert(.AnimateCrossfade)
|
||||
}
|
||||
options.insert(.LowLatency)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
@@ -381,15 +622,52 @@ public final class VoiceChatController: ViewController {
|
||||
})
|
||||
}
|
||||
|
||||
private func updateMembers(members: [RenderedChannelParticipant]) {
|
||||
private func updateMembers(isMuted: Bool, members: [RenderedChannelParticipant], memberStates: [PeerId: PresentationGroupCallMemberState]) {
|
||||
var members = members
|
||||
members.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
|
||||
}
|
||||
}
|
||||
return lhs.peer.id < rhs.peer.id
|
||||
})
|
||||
|
||||
self.currentMembers = members
|
||||
self.currentMemberStates = memberStates
|
||||
|
||||
let previousEntries = self.currentEntries
|
||||
var entries: [PeerEntry] = []
|
||||
|
||||
var index: Int32 = 0
|
||||
|
||||
for member in members {
|
||||
let memberState: PeerEntry.State
|
||||
if member.peer.id == self.context.account.peerId {
|
||||
if !isMuted {
|
||||
memberState = .speaking
|
||||
} else {
|
||||
memberState = .listening
|
||||
}
|
||||
} else if let _ = memberStates[member.peer.id] {
|
||||
memberState = .listening
|
||||
} else {
|
||||
memberState = .inactive
|
||||
}
|
||||
|
||||
entries.append(PeerEntry(
|
||||
participant: member,
|
||||
activityTimestamp: Int32.max - 1 - index
|
||||
activityTimestamp: Int32.max - 1 - index,
|
||||
state: memberState
|
||||
))
|
||||
index += 1
|
||||
}
|
||||
@@ -398,13 +676,13 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let presentationData = ItemListPresentationData(theme: self.darkTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings)
|
||||
|
||||
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: context, presentationData: presentationData, interaction: self.itemInteraction!)
|
||||
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!)
|
||||
self.enqueueTransition(transition)
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let sharedContext: SharedAccountContext
|
||||
public let call: PresentationGroupCall
|
||||
private let presentationData: PresentationData
|
||||
|
||||
fileprivate let contentsReady = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
@@ -421,10 +699,10 @@ public final class VoiceChatController: ViewController {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
public init(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall) {
|
||||
self.sharedContext = sharedContext
|
||||
self.call = call
|
||||
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let darkNavigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
||||
|
||||
@@ -461,7 +739,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(controller: self, context: self.context, peerId: self.peerId)
|
||||
self.displayNode = Node(controller: self, sharedContext: self.sharedContext, call: self.call)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
@@ -469,6 +747,8 @@ public final class VoiceChatController: ViewController {
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.isDismissed = false
|
||||
|
||||
if !self.didAppearOnce {
|
||||
self.didAppearOnce = true
|
||||
|
||||
@@ -479,6 +759,7 @@ public final class VoiceChatController: ViewController {
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
self.didAppearOnce = false
|
||||
|
||||
self.controllerNode.animateOut(completion: { [weak self] in
|
||||
completion?()
|
||||
|
||||
Reference in New Issue
Block a user