mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
7038ecbd14
@ -5919,6 +5919,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"VoiceChat.StatusSpeaking" = "speaking";
|
||||
"VoiceChat.StatusListening" = "listening";
|
||||
"VoiceChat.StatusInvited" = "invited";
|
||||
|
||||
"VoiceChat.Connecting" = "Connecting...";
|
||||
"VoiceChat.Reconnecting" = "Reconnecting...";
|
||||
@ -5949,6 +5950,7 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.PanelJoin" = "Join";
|
||||
"VoiceChat.Title" = "Voice Chat";
|
||||
|
||||
"VoiceChat.InviteMember" = "Invite Member";
|
||||
"VoiceChat.UserInvited" = "You invited **%@** to the voice chat";
|
||||
|
||||
"Notification.VoiceChatInvitation" = "%1$@ invited %2$@ to the voice chat";
|
||||
@ -5974,6 +5976,7 @@ Sorry for the inconvenience.";
|
||||
"ChannelInfo.CreateVoiceChat" = "Start Voice Chat";
|
||||
|
||||
"VoiceChat.AnonymousDisabledAlertText" = "Sorry, you can't join voice chat as an anonymous admin.";
|
||||
"VoiceChat.ChatFullAlertText" = "Sorry, this voice chat has too many participants at the moment.";
|
||||
|
||||
"VoiceChat.EndConfirmationTitle" = "End voice chat";
|
||||
"VoiceChat.EndConfirmationText" = "Are you sure you want to end this voice chat?";
|
||||
|
@ -12,10 +12,6 @@ import Display
|
||||
import DeviceLocationManager
|
||||
import TemporaryCachedPeerDataManager
|
||||
|
||||
#if ENABLE_WALLET
|
||||
import WalletCore
|
||||
#endif
|
||||
|
||||
public final class TelegramApplicationOpenUrlCompletion {
|
||||
public let completion: (Bool) -> Void
|
||||
|
||||
@ -647,14 +643,16 @@ public final class TonContext {
|
||||
public protocol ChatLocationContextHolder: class {
|
||||
}
|
||||
|
||||
public protocol AccountGroupCallContext: class {
|
||||
}
|
||||
|
||||
public protocol AccountGroupCallContextCache: class {
|
||||
}
|
||||
|
||||
public protocol AccountContext: class {
|
||||
var sharedContext: SharedAccountContext { get }
|
||||
var account: Account { get }
|
||||
|
||||
#if ENABLE_WALLET
|
||||
var tonContext: StoredTonContext? { get }
|
||||
#endif
|
||||
|
||||
var liveLocationManager: LiveLocationManager? { get }
|
||||
var peersNearbyManager: PeersNearbyManager? { get }
|
||||
var fetchManager: FetchManager { get }
|
||||
@ -663,15 +661,12 @@ public protocol AccountContext: class {
|
||||
var wallpaperUploadManager: WallpaperUploadManager? { get }
|
||||
var watchManager: WatchManager? { get }
|
||||
|
||||
#if ENABLE_WALLET
|
||||
var hasWallets: Signal<Bool, NoError> { get }
|
||||
var hasWalletAccess: Signal<Bool, NoError> { get }
|
||||
#endif
|
||||
|
||||
var currentLimitsConfiguration: Atomic<LimitsConfiguration> { get }
|
||||
var currentContentSettings: Atomic<ContentSettings> { get }
|
||||
var currentAppConfiguration: Atomic<AppConfiguration> { get }
|
||||
|
||||
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
||||
|
||||
func storeSecureIdPassword(password: String)
|
||||
func getStoredSecureIdPassword() -> String?
|
||||
|
||||
|
@ -194,20 +194,20 @@ public struct PresentationGroupCallSummaryState: Equatable {
|
||||
public var participantCount: Int
|
||||
public var callState: PresentationGroupCallState
|
||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
public var numberOfActiveSpeakers: Int
|
||||
public var activeSpeakers: Set<PeerId>
|
||||
|
||||
public init(
|
||||
info: GroupCallInfo,
|
||||
participantCount: Int,
|
||||
callState: PresentationGroupCallState,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
numberOfActiveSpeakers: Int
|
||||
activeSpeakers: Set<PeerId>
|
||||
) {
|
||||
self.info = info
|
||||
self.participantCount = participantCount
|
||||
self.callState = callState
|
||||
self.topParticipants = topParticipants
|
||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
||||
self.activeSpeakers = activeSpeakers
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,7 +286,7 @@ public protocol PresentationGroupCall: class {
|
||||
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
||||
|
||||
func invitePeer(_ peerId: PeerId)
|
||||
var invitedPeers: Signal<Set<PeerId>, NoError> { get }
|
||||
var invitedPeers: Signal<[PeerId], NoError> { get }
|
||||
|
||||
var sourcePanel: ASDisplayNode? { get set }
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ swift_library(
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/AudioBlob:AudioBlob",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -8,6 +8,7 @@ import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import AccountContext
|
||||
import AudioBlob
|
||||
|
||||
public final class AnimatedAvatarSetContext {
|
||||
public final class Content {
|
||||
@ -45,10 +46,6 @@ public final class AnimatedAvatarSetContext {
|
||||
}
|
||||
|
||||
public func update(peers: [Peer], animated: Bool) -> Content {
|
||||
for peer in peers {
|
||||
|
||||
}
|
||||
|
||||
var items: [(Content.Item.Key, Content.Item)] = []
|
||||
for peer in peers {
|
||||
items.append((Content.Item.Key(peerId: peer.id), Content.Item(peer: peer)))
|
||||
@ -60,6 +57,7 @@ public final class AnimatedAvatarSetContext {
|
||||
private let avatarFont = avatarPlaceholderFont(size: 12.0)
|
||||
|
||||
private final class ContentNode: ASDisplayNode {
|
||||
private var audioLevelView: VoiceBlobView?
|
||||
private let unclippedNode: ASImageNode
|
||||
private let clippedNode: ASImageNode
|
||||
|
||||
@ -137,6 +135,51 @@ private final class ContentNode: ASDisplayNode {
|
||||
self.clippedNode.alpha = isClipped ? 1.0 : 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func updateAudioLevel(color: UIColor, value: Float) {
|
||||
if self.audioLevelView == nil, value > 0.0 {
|
||||
let blobFrame = self.unclippedNode.bounds.insetBy(dx: -8.0, dy: -8.0)
|
||||
|
||||
let audioLevelView = VoiceBlobView(
|
||||
frame: blobFrame,
|
||||
maxLevel: 0.3,
|
||||
smallBlobRange: (0, 0),
|
||||
mediumBlobRange: (0.7, 0.8),
|
||||
bigBlobRange: (0.8, 0.9)
|
||||
)
|
||||
|
||||
let maskRect = CGRect(origin: .zero, size: blobFrame.size)
|
||||
let playbackMaskLayer = CAShapeLayer()
|
||||
playbackMaskLayer.frame = maskRect
|
||||
playbackMaskLayer.fillRule = .evenOdd
|
||||
let maskPath = UIBezierPath()
|
||||
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22))
|
||||
maskPath.append(UIBezierPath(rect: maskRect))
|
||||
playbackMaskLayer.path = maskPath.cgPath
|
||||
audioLevelView.layer.mask = playbackMaskLayer
|
||||
|
||||
audioLevelView.setColor(color)
|
||||
self.audioLevelView = audioLevelView
|
||||
self.view.insertSubview(audioLevelView, at: 0)
|
||||
}
|
||||
|
||||
let level = min(1.0, max(0.0, CGFloat(value)))
|
||||
if let audioLevelView = self.audioLevelView {
|
||||
audioLevelView.updateLevel(CGFloat(value) * 2.0)
|
||||
|
||||
let avatarScale: CGFloat
|
||||
if value > 0.0 {
|
||||
audioLevelView.startAnimating()
|
||||
avatarScale = 1.03 + level * 0.07
|
||||
} else {
|
||||
audioLevelView.stopAnimating(duration: 0.5)
|
||||
avatarScale = 1.0
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateSublayerTransformScale(node: self, scale: CGPoint(x: avatarScale, y: avatarScale), beginWithCurrentState: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class AnimatedAvatarSetNode: ASDisplayNode {
|
||||
@ -159,7 +202,9 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
||||
|
||||
var validKeys: [AnimatedAvatarSetContext.Content.Item.Key] = []
|
||||
var index = 0
|
||||
for (key, item) in content.items {
|
||||
for i in 0 ..< content.items.count {
|
||||
let (key, item) = content.items[i]
|
||||
|
||||
validKeys.append(key)
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: 0.0), size: itemSize)
|
||||
@ -180,6 +225,7 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
||||
itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||
}
|
||||
}
|
||||
itemNode.zPosition = CGFloat(100 - i)
|
||||
contentWidth += itemSize.width - 10.0
|
||||
index += 1
|
||||
}
|
||||
@ -201,4 +247,14 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
||||
|
||||
return CGSize(width: contentWidth, height: contentHeight)
|
||||
}
|
||||
|
||||
public func updateAudioLevels(color: UIColor, levels: [PeerId: Float]) {
|
||||
for (key, itemNode) in self.contentNodes {
|
||||
if let value = levels[key.peerId] {
|
||||
itemNode.updateAudioLevel(color: color, value: value)
|
||||
} else {
|
||||
itemNode.updateAudioLevel(color: color, value: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void) {
|
||||
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
|
||||
self.presentationData = presentationData
|
||||
self.feedbackTap = feedbackTap
|
||||
|
||||
@ -95,6 +95,9 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
self.cornerRadius = 14.0
|
||||
|
||||
self.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
||||
if !blurBackground {
|
||||
self.backgroundColor = self.backgroundColor?.withAlphaComponent(1.0)
|
||||
}
|
||||
|
||||
self.itemNodes.forEach({ itemNode in
|
||||
switch itemNode {
|
||||
@ -402,8 +405,8 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, displayTextSelectionTip: Bool) {
|
||||
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap)
|
||||
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, displayTextSelectionTip: Bool, blurBackground: Bool) {
|
||||
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
||||
if displayTextSelectionTip {
|
||||
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData)
|
||||
textSelectionTipNode.isUserInteractionEnabled = false
|
||||
|
@ -137,6 +137,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
private let itemsDisposable = MetaDisposable()
|
||||
|
||||
private let blurBackground: Bool
|
||||
|
||||
init(account: Account, controller: ContextController, presentationData: PresentationData, source: ContextContentSource, items: Signal<[ContextMenuItem], NoError>, reactionItems: [ReactionContextItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, gesture: ContextGesture?, reactionSelected: @escaping (ReactionContextItem.Reaction) -> Void, beganAnimatingOut: @escaping () -> Void, attemptTransitionControllerIntoNavigation: @escaping () -> Void, displayTextSelectionTip: Bool) {
|
||||
self.presentationData = presentationData
|
||||
self.source = source
|
||||
@ -188,13 +190,19 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
var feedbackTap: (() -> Void)?
|
||||
|
||||
var blurBackground = true
|
||||
if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
|
||||
blurBackground = false
|
||||
}
|
||||
self.blurBackground = blurBackground
|
||||
|
||||
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: [], getController: { [weak controller] in
|
||||
return controller
|
||||
}, actionSelected: { result in
|
||||
beginDismiss(result)
|
||||
}, feedbackTap: {
|
||||
feedbackTap?()
|
||||
}, displayTextSelectionTip: self.displayTextSelectionTip)
|
||||
}, displayTextSelectionTip: self.displayTextSelectionTip, blurBackground: blurBackground)
|
||||
|
||||
if !reactionItems.isEmpty {
|
||||
let reactionContextNode = ReactionContextNode(account: account, theme: presentationData.theme, items: reactionItems)
|
||||
@ -211,8 +219,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
|
||||
} else {
|
||||
if blurBackground {
|
||||
self.view.addSubview(self.effectView)
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.withoutBlurDimNode)
|
||||
@ -1031,7 +1038,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self?.beginDismiss(result)
|
||||
}, feedbackTap: { [weak self] in
|
||||
self?.hapticFeedback.tap()
|
||||
}, displayTextSelectionTip: self.displayTextSelectionTip)
|
||||
}, displayTextSelectionTip: self.displayTextSelectionTip, blurBackground: self.blurBackground)
|
||||
self.scrollNode.insertSubnode(self.actionsContainerNode, aboveSubnode: previousActionsContainerNode)
|
||||
|
||||
if let layout = self.validLayout {
|
||||
|
@ -1100,7 +1100,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
}
|
||||
|
||||
print("off \(offset)")
|
||||
if abs(offset) > CGFloat.ulpOfOne {
|
||||
self.didScrollWithOffset?(-offset, .immediate, nil)
|
||||
|
||||
|
@ -405,7 +405,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
inCallStatusBar.callStatusBarNode?.update(size: inCallStatusBarFrame.size)
|
||||
inCallStatusBar.callStatusBarNode?.frame = inCallStatusBarFrame
|
||||
layout.statusBarHeight = inCallStatusBarFrame.height
|
||||
self.inCallStatusBar?.frame = inCallStatusBarFrame
|
||||
inCallStatusBar.frame = inCallStatusBarFrame
|
||||
}
|
||||
|
||||
if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
||||
@ -555,7 +555,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalScrollToTopNode)
|
||||
} else if let globalOverlayContainerParent = self.globalOverlayContainerParent {
|
||||
self.displayNode.insertSubnode(overlayContainer, belowSubnode: globalOverlayContainerParent)
|
||||
}else {
|
||||
} else {
|
||||
self.displayNode.addSubnode(overlayContainer)
|
||||
}
|
||||
overlayContainer.transitionIn()
|
||||
@ -608,7 +608,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
let effectiveModalTransition: CGFloat
|
||||
if visibleModalCount == 0 {
|
||||
if visibleModalCount == 0 || navigationLayout.modal[i].isFlat {
|
||||
effectiveModalTransition = 0.0
|
||||
} else if visibleModalCount == 1 {
|
||||
effectiveModalTransition = 1.0 - topModalDismissProgress
|
||||
@ -635,7 +635,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
if modalContainer.supernode != nil {
|
||||
if !hasVisibleStandaloneModal && !isStandaloneModal {
|
||||
if !hasVisibleStandaloneModal && !isStandaloneModal && !modalContainer.isFlat {
|
||||
visibleModalCount += 1
|
||||
}
|
||||
if isStandaloneModal {
|
||||
@ -1357,7 +1357,6 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
inCallStatusBar.inCallNavigate = { [weak self] in
|
||||
self?.scrollToTop(.master)
|
||||
}
|
||||
inCallStatusBar.alpha = 0.0
|
||||
self.inCallStatusBar = inCallStatusBar
|
||||
|
||||
var bottomOverlayContainer: NavigationOverlayContainer?
|
||||
@ -1375,15 +1374,17 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
} else {
|
||||
self.displayNode.addSubnode(inCallStatusBar)
|
||||
}
|
||||
transition.updateAlpha(node: inCallStatusBar, alpha: 1.0)
|
||||
if case let .animated(duration, _) = transition {
|
||||
inCallStatusBar.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: CGPoint(), duration: duration, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, additive: true)
|
||||
}
|
||||
}
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: transition)
|
||||
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallNode: forceInCallStatusBar, animated: false)
|
||||
self.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
} else if let inCallStatusBar = self.inCallStatusBar {
|
||||
self.inCallStatusBar = nil
|
||||
transition.updateAlpha(node: inCallStatusBar, alpha: 0.0, completion: { [weak inCallStatusBar] _ in
|
||||
transition.updatePosition(node: inCallStatusBar, position: CGPoint(x: inCallStatusBar.position.x, y: -64.0), completion: { [weak inCallStatusBar] _ in
|
||||
inCallStatusBar?.removeFromSupernode()
|
||||
})
|
||||
if let layout = self.validLayout {
|
||||
|
@ -109,10 +109,11 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
panRecognizer.delegate = self
|
||||
panRecognizer.delaysTouchesBegan = false
|
||||
panRecognizer.cancelsTouchesInView = true
|
||||
if !self.isFlat {
|
||||
self.view.addGestureRecognizer(panRecognizer)
|
||||
|
||||
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
}
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
@ -335,9 +336,13 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
switch layout.metrics.widthClass {
|
||||
case .compact:
|
||||
self.panRecognizer?.isEnabled = true
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
||||
self.container.clipsToBounds = true
|
||||
if isStandaloneModal || isLandscape {
|
||||
if self.isFlat {
|
||||
self.dim.backgroundColor = .clear
|
||||
} else {
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
||||
}
|
||||
if isStandaloneModal || isLandscape || self.isFlat {
|
||||
self.container.cornerRadius = 0.0
|
||||
} else {
|
||||
self.container.cornerRadius = 10.0
|
||||
@ -361,10 +366,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
containerFrame = unscaledFrame
|
||||
} else {
|
||||
topInset = 10.0
|
||||
if self.isFlat, let preferredSize = controllers.last?.preferredContentSizeForLayout(layout) {
|
||||
topInset = layout.size.height - preferredSize.height
|
||||
}
|
||||
if let statusBarHeight = layout.statusBarHeight {
|
||||
if self.isFlat {
|
||||
topInset = 0.0
|
||||
} else if let statusBarHeight = layout.statusBarHeight {
|
||||
topInset += statusBarHeight
|
||||
}
|
||||
|
||||
@ -378,12 +382,21 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
}
|
||||
case .regular:
|
||||
self.panRecognizer?.isEnabled = false
|
||||
if self.isFlat {
|
||||
self.dim.backgroundColor = .clear
|
||||
self.container.clipsToBounds = true
|
||||
self.container.cornerRadius = 0.0
|
||||
if #available(iOS 11.0, *) {
|
||||
self.container.layer.maskedCorners = []
|
||||
}
|
||||
} else {
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||
self.container.clipsToBounds = true
|
||||
self.container.cornerRadius = 10.0
|
||||
if #available(iOS 11.0, *) {
|
||||
self.container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
}
|
||||
}
|
||||
|
||||
let verticalInset: CGFloat = 44.0
|
||||
|
||||
@ -408,6 +421,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
|
||||
func animateIn(transition: ContainedViewLayoutTransition) {
|
||||
if let controller = self.container.controllers.first, case .standaloneModal = controller.navigationPresentation {
|
||||
} else if self.isFlat {
|
||||
} else {
|
||||
transition.updateAlpha(node: self.dim, alpha: 1.0)
|
||||
transition.animatePositionAdditive(node: self.container, offset: CGPoint(x: 0.0, y: self.bounds.height + self.container.bounds.height / 2.0 - (self.container.position.y - self.bounds.height)))
|
||||
@ -429,7 +443,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
completion()
|
||||
return transition
|
||||
} else {
|
||||
if transition.isAnimated {
|
||||
if transition.isAnimated && !self.isFlat {
|
||||
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0, beginWithCurrentState: true)
|
||||
|
@ -918,9 +918,10 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
} else {
|
||||
self.backButtonNode.alpha = 1.0
|
||||
self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize)
|
||||
transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize))
|
||||
|
||||
self.backButtonArrow.alpha = 1.0
|
||||
self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0))
|
||||
transition.updateFrame(node: self.backButtonArrow, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)))
|
||||
self.badgeNode.alpha = 1.0
|
||||
}
|
||||
} else if self.leftButtonNode.supernode != nil {
|
||||
@ -928,18 +929,18 @@ open class NavigationBar: ASDisplayNode {
|
||||
leftTitleInset += leftButtonSize.width + leftButtonInset + 1.0
|
||||
|
||||
self.leftButtonNode.alpha = 1.0
|
||||
self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize)
|
||||
transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize))
|
||||
}
|
||||
|
||||
let badgeSize = self.badgeNode.measure(CGSize(width: 200.0, height: 100.0))
|
||||
let backButtonArrowFrame = self.backButtonArrow.frame
|
||||
self.badgeNode.frame = CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 7.0, dy: -9.0), size: badgeSize)
|
||||
transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 7.0, dy: -9.0), size: badgeSize))
|
||||
|
||||
if self.rightButtonNode.supernode != nil {
|
||||
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)))
|
||||
rightTitleInset += rightButtonSize.width + leftButtonInset + 1.0
|
||||
self.rightButtonNode.alpha = 1.0
|
||||
self.rightButtonNode.frame = CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)
|
||||
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
|
||||
}
|
||||
|
||||
if let transitionState = self.transitionState {
|
||||
@ -1004,14 +1005,14 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
} else {
|
||||
self.titleNode.alpha = 1.0
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
|
||||
}
|
||||
}
|
||||
|
||||
if let titleView = self.titleView {
|
||||
let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
||||
titleView.frame = titleFrame
|
||||
transition.updateFrame(view: titleView, frame: titleFrame)
|
||||
|
||||
if let titleView = titleView as? NavigationBarTitleView {
|
||||
let titleWidth = size.width - (leftTitleInset > 0.0 ? leftTitleInset : rightTitleInset) - (rightTitleInset > 0.0 ? rightTitleInset : leftTitleInset)
|
||||
@ -1048,7 +1049,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
titleView.alpha = 1.0
|
||||
titleView.frame = titleFrame
|
||||
transition.updateFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,31 +31,6 @@ open class CallStatusBarNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private func addInCallAnimation(_ layer: CALayer) {
|
||||
let animation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
animation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 0.5 as NSNumber, 0.9 as NSNumber, 1.0 as NSNumber]
|
||||
animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.0 as NSNumber, 1.0 as NSNumber, 1.0 as NSNumber]
|
||||
animation.duration = 1.8
|
||||
animation.autoreverses = true
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
layer.add(animation, forKey: "blink")
|
||||
}
|
||||
|
||||
private final class StatusBarLabelNode: ImmediateTextNode {
|
||||
override func willEnterHierarchy() {
|
||||
super.willEnterHierarchy()
|
||||
|
||||
addInCallAnimation(self.layer)
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.layer.removeAnimation(forKey: "blink")
|
||||
}
|
||||
}
|
||||
|
||||
private final class StatusBarView: UITracingLayerView {
|
||||
weak var node: StatusBar?
|
||||
|
||||
@ -178,21 +153,11 @@ public final class StatusBar: ASDisplayNode {
|
||||
if (resolvedCallStatusBarNode != nil) != (self.callStatusBarNode != nil) {
|
||||
if let resolvedCallStatusBarNode = resolvedCallStatusBarNode {
|
||||
self.addSubnode(resolvedCallStatusBarNode)
|
||||
if animated {
|
||||
resolvedCallStatusBarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
}
|
||||
} else if let callStatusBarNode = self.callStatusBarNode {
|
||||
self.callStatusBarNode = nil
|
||||
|
||||
if animated {
|
||||
callStatusBarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak callStatusBarNode] _ in
|
||||
callStatusBarNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
callStatusBarNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.callStatusBarNode = resolvedCallStatusBarNode
|
||||
}
|
||||
|
@ -303,6 +303,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
private let emptyQueryDisposable = MetaDisposable()
|
||||
private let searchDisposable = MetaDisposable()
|
||||
|
||||
private let forceTheme: PresentationTheme?
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
@ -310,12 +311,16 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
|
||||
private let presentationDataPromise: Promise<PresentationData>
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
|
||||
public init(context: AccountContext, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
|
||||
self.context = context
|
||||
self.openPeer = openPeer
|
||||
self.mode = mode
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.forceTheme = forceTheme
|
||||
if let forceTheme = self.forceTheme {
|
||||
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
self.presentationDataPromise = Promise(self.presentationData)
|
||||
|
||||
self.emptyQueryListNode = ListView()
|
||||
@ -622,9 +627,20 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError>
|
||||
switch mode {
|
||||
case .inviteActions, .banAndPromoteActions:
|
||||
if filters.contains(where: { filter in
|
||||
if case .excludeNonMembers = filter {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
foundContacts = .single(([], [:]))
|
||||
foundRemotePeers = .single(([], []))
|
||||
} else {
|
||||
foundContacts = context.account.postbox.searchContacts(query: query.lowercased())
|
||||
foundRemotePeers = .single(([], [])) |> then(searchPeers(account: context.account, query: query)
|
||||
|> delay(0.2, queue: Queue.concurrentDefaultQueue()))
|
||||
}
|
||||
case .searchMembers, .searchBanned, .searchKicked, .searchAdmins:
|
||||
foundContacts = .single(([], [:]))
|
||||
foundRemotePeers = .single(([], []))
|
||||
@ -639,7 +655,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
switch filter {
|
||||
case let .exclude(ids):
|
||||
existingPeerIds = existingPeerIds.union(ids)
|
||||
case .disable:
|
||||
case .disable, .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -927,7 +943,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
switch filter {
|
||||
case let .exclude(ids):
|
||||
existingPeerIds = existingPeerIds.union(ids)
|
||||
case .disable:
|
||||
case .disable, .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -1157,9 +1173,15 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
var presentationData = presentationData
|
||||
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
if let forceTheme = strongSelf.forceTheme {
|
||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
|
@ -9,17 +9,19 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import SearchUI
|
||||
|
||||
enum ChannelMembersSearchControllerMode {
|
||||
public enum ChannelMembersSearchControllerMode {
|
||||
case promote
|
||||
case ban
|
||||
case inviteToCall
|
||||
}
|
||||
|
||||
public enum ChannelMembersSearchFilter {
|
||||
case exclude([PeerId])
|
||||
case disable([PeerId])
|
||||
case excludeNonMembers
|
||||
}
|
||||
|
||||
final class ChannelMembersSearchController: ViewController {
|
||||
public final class ChannelMembersSearchController: ViewController {
|
||||
private let queue = Queue()
|
||||
|
||||
private let context: AccountContext
|
||||
@ -28,6 +30,7 @@ final class ChannelMembersSearchController: ViewController {
|
||||
private let filters: [ChannelMembersSearchFilter]
|
||||
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
||||
|
||||
private let forceTheme: PresentationTheme?
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private var didPlayPresentationAnimation = false
|
||||
@ -38,13 +41,17 @@ final class ChannelMembersSearchController: ViewController {
|
||||
|
||||
private var searchContentNode: NavigationBarSearchContentNode?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
|
||||
public init(context: AccountContext, peerId: PeerId, forceTheme: PresentationTheme? = nil, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.mode = mode
|
||||
self.openPeer = openPeer
|
||||
self.filters = filters
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.forceTheme = forceTheme
|
||||
if let forceTheme = forceTheme {
|
||||
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
@ -74,8 +81,8 @@ final class ChannelMembersSearchController: ViewController {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
self.displayNode = ChannelMembersSearchControllerNode(context: self.context, presentationData: self.presentationData, peerId: self.peerId, mode: self.mode, filters: self.filters)
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ChannelMembersSearchControllerNode(context: self.context, presentationData: self.presentationData, forceTheme: self.forceTheme, peerId: self.peerId, mode: self.mode, filters: self.filters)
|
||||
self.controllerNode.navigationBar = self.navigationBar
|
||||
self.controllerNode.requestActivateSearch = { [weak self] in
|
||||
self?.activateSearch()
|
||||
@ -105,7 +112,7 @@ final class ChannelMembersSearchController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments, !self.didPlayPresentationAnimation {
|
||||
@ -143,7 +150,7 @@ final class ChannelMembersSearchController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func cancelPressed() {
|
||||
@objc private func cancelPressed() {
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
|
@ -112,18 +112,23 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)?
|
||||
var pushController: ((ViewController) -> Void)?
|
||||
|
||||
private let forceTheme: PresentationTheme?
|
||||
var presentationData: PresentationData
|
||||
|
||||
private var disposable: Disposable?
|
||||
private var listControl: PeerChannelMemberCategoryControl?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) {
|
||||
init(context: AccountContext, presentationData: PresentationData, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) {
|
||||
self.context = context
|
||||
self.listNode = ListView()
|
||||
self.peerId = peerId
|
||||
self.mode = mode
|
||||
self.filters = filters
|
||||
self.presentationData = presentationData
|
||||
self.forceTheme = forceTheme
|
||||
if let forceTheme = forceTheme {
|
||||
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
@ -190,7 +195,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
if ids.contains(peer.id) {
|
||||
continue
|
||||
}
|
||||
case .disable:
|
||||
case let .disable(ids):
|
||||
if ids.contains(peer.id) {
|
||||
enabled = false
|
||||
}
|
||||
case .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -204,7 +213,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
if ids.contains(peer.id) {
|
||||
continue
|
||||
}
|
||||
case .disable:
|
||||
case let .disable(ids):
|
||||
if ids.contains(peer.id) {
|
||||
enabled = false
|
||||
}
|
||||
case .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -212,6 +225,24 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
case .inviteToCall:
|
||||
if peer.id == context.account.peerId {
|
||||
continue
|
||||
}
|
||||
for filter in filters {
|
||||
switch filter {
|
||||
case let .exclude(ids):
|
||||
if ids.contains(peer.id) {
|
||||
continue
|
||||
}
|
||||
case let .disable(ids):
|
||||
if ids.contains(peer.id) {
|
||||
enabled = false
|
||||
}
|
||||
case .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let renderedParticipant: RenderedChannelParticipant
|
||||
switch participant {
|
||||
@ -262,7 +293,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
if ids.contains(participant.peer.id) {
|
||||
continue
|
||||
}
|
||||
case .disable:
|
||||
case let .disable(ids):
|
||||
if ids.contains(participant.peer.id) {
|
||||
enabled = false
|
||||
}
|
||||
case .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -276,7 +311,11 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
if ids.contains(participant.peer.id) {
|
||||
continue
|
||||
}
|
||||
case .disable:
|
||||
case let .disable(ids):
|
||||
if ids.contains(participant.peer.id) {
|
||||
enabled = false
|
||||
}
|
||||
case .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -284,6 +323,24 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
label = strongSelf.presentationData.strings.Channel_Management_LabelOwner
|
||||
enabled = false
|
||||
}
|
||||
case .inviteToCall:
|
||||
if participant.peer.id == context.account.peerId {
|
||||
continue
|
||||
}
|
||||
for filter in filters {
|
||||
switch filter {
|
||||
case let .exclude(ids):
|
||||
if ids.contains(participant.peer.id) {
|
||||
continue
|
||||
}
|
||||
case let .disable(ids):
|
||||
if ids.contains(participant.peer.id) {
|
||||
enabled = false
|
||||
}
|
||||
case .excludeNonMembers:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.append(.peer(index, participant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled))
|
||||
index += 1
|
||||
@ -316,7 +373,10 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
self.searchDisplayController?.updatePresentationData(presentationData)
|
||||
if let forceTheme = forceTheme {
|
||||
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
self.searchDisplayController?.updatePresentationData(self.presentationData)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
@ -350,7 +410,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChannelMembersSearchContainerNode(context: self.context, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, searchContext: nil, openPeer: { [weak self] peer, participant in
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: self.forceTheme, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, searchContext: nil, openPeer: { [weak self] peer, participant in
|
||||
self?.requestOpenPeerFromSearch?(peer, participant)
|
||||
}, updateActivity: { value in
|
||||
|
||||
|
@ -87,7 +87,7 @@ private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode {
|
||||
private let containerNode: ChannelMembersSearchContainerNode
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, searchMode: ChannelMembersSearchMode, searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) {
|
||||
self.containerNode = ChannelMembersSearchContainerNode(context: context, peerId: peerId, mode: searchMode, filters: [], searchContext: searchContext, openPeer: { peer, participant in
|
||||
self.containerNode = ChannelMembersSearchContainerNode(context: context, forceTheme: nil, peerId: peerId, mode: searchMode, filters: [], searchContext: searchContext, openPeer: { peer, participant in
|
||||
openPeer(peer, participant)
|
||||
}, updateActivity: updateActivity, pushController: pushController)
|
||||
self.containerNode.cancel = {
|
||||
|
@ -15,7 +15,7 @@ import SearchBarNode
|
||||
import SearchUI
|
||||
import ChatListSearchItemHeader
|
||||
|
||||
extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationContentNode {
|
||||
/*extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationContentNode {
|
||||
public func activate() {
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ extension NavigationBarSearchContentNode: ItemListControllerSearchNavigationCont
|
||||
|
||||
public func setQueryUpdated(_ f: @escaping (String) -> Void) {
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
extension SettingsSearchableItemIcon {
|
||||
func image() -> UIImage? {
|
||||
|
@ -26,6 +26,155 @@ public enum LocationBroadcastPanelSource {
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
final class Proxy {
|
||||
let context: AccountGroupCallContextImpl
|
||||
let removed: () -> Void
|
||||
|
||||
init(context: AccountGroupCallContextImpl, removed: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.removed = removed
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.removed()
|
||||
}
|
||||
|
||||
func keep() {
|
||||
}
|
||||
}
|
||||
|
||||
var disposable: Disposable?
|
||||
var participantsContext: GroupCallParticipantsContext?
|
||||
|
||||
private let panelDataPromise = Promise<GroupCallPanelData>()
|
||||
var panelData: Signal<GroupCallPanelData, NoError> {
|
||||
return self.panelDataPromise.get()
|
||||
}
|
||||
|
||||
init(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) {
|
||||
self.panelDataPromise.set(.single(GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
participantCount: 0,
|
||||
clientParams: nil
|
||||
),
|
||||
topParticipants: [],
|
||||
participantCount: 0,
|
||||
activeSpeakers: Set(),
|
||||
groupCall: nil
|
||||
)))
|
||||
|
||||
self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", limit: 100)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self, let state = state else {
|
||||
return
|
||||
}
|
||||
let context = GroupCallParticipantsContext(
|
||||
account: account,
|
||||
peerId: peerId,
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
state: state
|
||||
)
|
||||
strongSelf.participantsContext = context
|
||||
strongSelf.panelDataPromise.set(combineLatest(queue: .mainQueue(),
|
||||
context.state,
|
||||
context.activeSpeakers
|
||||
)
|
||||
|> map { state, activeSpeakers -> GroupCallPanelData in
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
for participant in state.participants {
|
||||
if topParticipants.count >= 3 {
|
||||
break
|
||||
}
|
||||
topParticipants.append(participant)
|
||||
}
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, clientParams: nil),
|
||||
topParticipants: topParticipants,
|
||||
participantCount: state.totalCount,
|
||||
activeSpeakers: activeSpeakers,
|
||||
groupCall: nil
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCache {
|
||||
class Impl {
|
||||
private class Record {
|
||||
let context: AccountGroupCallContextImpl
|
||||
let subscribers = Bag<Void>()
|
||||
var removeTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(context: AccountGroupCallContextImpl) {
|
||||
self.context = context
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private var contexts: [Int64: Record] = [:]
|
||||
|
||||
init(queue: Queue) {
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
func get(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) -> AccountGroupCallContextImpl.Proxy {
|
||||
let result: Record
|
||||
if let current = self.contexts[call.id] {
|
||||
result = current
|
||||
} else {
|
||||
let context = AccountGroupCallContextImpl(account: account, peerId: peerId, call: call)
|
||||
result = Record(context: context)
|
||||
self.contexts[call.id] = result
|
||||
}
|
||||
|
||||
let index = result.subscribers.add(Void())
|
||||
result.removeTimer?.invalidate()
|
||||
result.removeTimer = nil
|
||||
return AccountGroupCallContextImpl.Proxy(context: result.context, removed: { [weak self, weak result] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongResult = result, let strongSelf = self, strongSelf.contexts[call.id] === strongResult {
|
||||
strongResult.subscribers.remove(index)
|
||||
if strongResult.subscribers.isEmpty {
|
||||
let removeTimer = SwiftSignalKit.Timer(timeout: 30, repeat: false, completion: {
|
||||
if let result = result, let strongSelf = self, strongSelf.contexts[call.id] === result, result.subscribers.isEmpty {
|
||||
strongSelf.contexts.removeValue(forKey: call.id)
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
strongResult.removeTimer = removeTimer
|
||||
removeTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let queue: Queue = .mainQueue()
|
||||
let impl: QueueLocalObject<Impl>
|
||||
|
||||
public init() {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
||||
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
||||
if let message = message, let strongController = controller {
|
||||
@ -290,7 +439,30 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
guard let activeCall = activeCall else {
|
||||
return .single(nil)
|
||||
}
|
||||
return getGroupCallParticipants(account: context.account, callId: activeCall.id, accessHash: activeCall.accessHash, offset: "", limit: 10)
|
||||
|
||||
return Signal { [weak context] subscriber in
|
||||
guard let context = context, let callContextCache = context.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl else {
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
callContextCache.impl.syncWith { impl in
|
||||
let callContext = impl.get(account: context.account, peerId: peerId, call: activeCall)
|
||||
disposable.set((callContext.context.panelData
|
||||
|> deliverOnMainQueue).start(next: { panelData in
|
||||
callContext.keep()
|
||||
subscriber.putNext(panelData)
|
||||
}))
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|> runOn(.mainQueue())
|
||||
|
||||
/*let updatedData: Signal<GroupCallPanelData?, NoError> = 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)
|
||||
@ -344,25 +516,34 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
}
|
||||
|> runOn(.mainQueue())
|
||||
}
|
||||
|
||||
let initialData: Signal<GroupCallPanelData?, NoError> = .single(GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(
|
||||
id: activeCall.id,
|
||||
accessHash: activeCall.accessHash,
|
||||
participantCount: 0,
|
||||
clientParams: nil
|
||||
),
|
||||
topParticipants: [],
|
||||
participantCount: 0,
|
||||
numberOfActiveSpeakers: 0,
|
||||
groupCall: nil
|
||||
))
|
||||
|
||||
return initialData
|
||||
|> then(updatedData)*/
|
||||
}
|
||||
} else {
|
||||
availableGroupCall = .single(nil)
|
||||
}
|
||||
|
||||
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(),
|
||||
currentGroupCall,
|
||||
availableGroupCall
|
||||
).start(next: { [weak self] currentGroupCall, availableState in
|
||||
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(), availableGroupCall, currentGroupCall).start(next: { [weak self] availableState, currentGroupCall in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let panelData: GroupCallPanelData?
|
||||
if let _ = currentGroupCall {
|
||||
panelData = nil
|
||||
} else {
|
||||
panelData = availableState
|
||||
}
|
||||
let panelData = currentGroupCall != nil ? nil : availableState
|
||||
|
||||
let wasEmpty = strongSelf.groupCallPanelData == nil
|
||||
strongSelf.groupCallPanelData = panelData
|
||||
|
@ -37,6 +37,7 @@ swift_library(
|
||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
|
||||
"//submodules/PeerInfoUI:PeerInfoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -27,7 +27,7 @@ public final class GroupCallPanelData {
|
||||
public let info: GroupCallInfo
|
||||
public let topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
public let participantCount: Int
|
||||
public let numberOfActiveSpeakers: Int
|
||||
public let activeSpeakers: Set<PeerId>
|
||||
public let groupCall: PresentationGroupCall?
|
||||
|
||||
public init(
|
||||
@ -35,18 +35,44 @@ public final class GroupCallPanelData {
|
||||
info: GroupCallInfo,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
participantCount: Int,
|
||||
numberOfActiveSpeakers: Int,
|
||||
activeSpeakers: Set<PeerId>,
|
||||
groupCall: PresentationGroupCall?
|
||||
) {
|
||||
self.peerId = peerId
|
||||
self.info = info
|
||||
self.topParticipants = topParticipants
|
||||
self.participantCount = participantCount
|
||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
||||
self.activeSpeakers = activeSpeakers
|
||||
self.groupCall = groupCall
|
||||
}
|
||||
}
|
||||
|
||||
private final class FakeAudioLevelGenerator {
|
||||
private var previousTarget: Float = 0.0
|
||||
private var nextTarget: Float = 0.0
|
||||
private var nextTargetProgress: Float = 1.0
|
||||
private var nextTargetProgressNorm: Float = 1.0
|
||||
|
||||
func get() -> Float {
|
||||
self.nextTargetProgress *= 0.82
|
||||
if self.nextTargetProgress <= 0.01 {
|
||||
self.previousTarget = self.nextTarget
|
||||
if Int.random(in: 0 ... 4) <= 1 {
|
||||
self.nextTarget = 0.0
|
||||
self.nextTargetProgressNorm = Float.random(in: 0.1 ..< 0.3)
|
||||
} else {
|
||||
self.nextTarget = Float.random(in: 0.0 ..< 20.0)
|
||||
self.nextTargetProgressNorm = Float.random(in: 0.2 ..< 0.7)
|
||||
}
|
||||
self.nextTargetProgress = self.nextTargetProgressNorm
|
||||
return self.nextTarget
|
||||
} else {
|
||||
let value = self.nextTarget * max(0.0, self.nextTargetProgress / self.nextTargetProgressNorm)
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private var theme: PresentationTheme
|
||||
@ -77,6 +103,8 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
private let avatarsContext: AnimatedAvatarSetContext
|
||||
private var avatarsContent: AnimatedAvatarSetContext.Content?
|
||||
private let avatarsNode: AnimatedAvatarSetNode
|
||||
private var audioLevelGenerators: [PeerId: FakeAudioLevelGenerator] = [:]
|
||||
private var audioLevelGeneratorTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
@ -167,6 +195,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
deinit {
|
||||
self.membersDisposable.dispose()
|
||||
self.isMutedDisposable.dispose()
|
||||
self.audioLevelGeneratorTimer?.invalidate()
|
||||
}
|
||||
|
||||
public override func didLoad() {
|
||||
@ -255,6 +284,8 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
let previousData = self.currentData
|
||||
self.currentData = data
|
||||
|
||||
var updateAudioLevels = false
|
||||
|
||||
if previousData?.groupCall !== data.groupCall {
|
||||
let membersText: String
|
||||
if data.participantCount == 0 {
|
||||
@ -263,7 +294,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
||||
}
|
||||
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
|
||||
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }.filter { $0.id != self.context.account.peerId }, animated: false)
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
@ -288,7 +319,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
|
||||
|
||||
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
|
||||
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }.filter { $0.id != strongSelf.context.account.peerId }, animated: false)
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
@ -368,11 +399,50 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
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 }.filter { $0.id != self.context.account.peerId }, animated: false)
|
||||
|
||||
updateAudioLevels = true
|
||||
}
|
||||
|
||||
if let (size, leftInset, rightInset) = self.validLayout {
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
}
|
||||
|
||||
if updateAudioLevels {
|
||||
for peerId in data.activeSpeakers {
|
||||
if self.audioLevelGenerators[peerId] == nil {
|
||||
self.audioLevelGenerators[peerId] = FakeAudioLevelGenerator()
|
||||
}
|
||||
}
|
||||
var removeGenerators: [PeerId] = []
|
||||
for peerId in self.audioLevelGenerators.keys {
|
||||
if !data.activeSpeakers.contains(peerId) {
|
||||
removeGenerators.append(peerId)
|
||||
}
|
||||
}
|
||||
for peerId in removeGenerators {
|
||||
self.audioLevelGenerators.removeValue(forKey: peerId)
|
||||
}
|
||||
|
||||
if self.audioLevelGenerators.isEmpty {
|
||||
self.audioLevelGeneratorTimer?.invalidate()
|
||||
self.audioLevelGeneratorTimer = nil
|
||||
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, levels: [:])
|
||||
} else if self.audioLevelGeneratorTimer == nil {
|
||||
let audioLevelGeneratorTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
|
||||
self?.sampleAudioGenerators()
|
||||
}, queue: .mainQueue())
|
||||
self.audioLevelGeneratorTimer = audioLevelGeneratorTimer
|
||||
audioLevelGeneratorTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sampleAudioGenerators() {
|
||||
var levels: [PeerId: Float] = [:]
|
||||
for (peerId, generator) in self.audioLevelGenerators {
|
||||
levels[peerId] = generator.get()
|
||||
}
|
||||
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, levels: levels)
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -58,16 +58,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private struct SummaryParticipantsState: Equatable {
|
||||
public var participantCount: Int
|
||||
public var topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
public var numberOfActiveSpeakers: Int
|
||||
public var activeSpeakers: Set<PeerId>
|
||||
|
||||
public init(
|
||||
participantCount: Int,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
numberOfActiveSpeakers: Int
|
||||
activeSpeakers: Set<PeerId>
|
||||
) {
|
||||
self.participantCount = participantCount
|
||||
self.topParticipants = topParticipants
|
||||
self.numberOfActiveSpeakers = numberOfActiveSpeakers
|
||||
self.activeSpeakers = activeSpeakers
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,15 +251,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
return self.membersPromise.get()
|
||||
}
|
||||
|
||||
private var invitedPeersValue: Set<PeerId> = Set() {
|
||||
private var invitedPeersValue: [PeerId] = [] {
|
||||
didSet {
|
||||
if self.invitedPeersValue != oldValue {
|
||||
self.inivitedPeersPromise.set(self.invitedPeersValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
private let inivitedPeersPromise = ValuePromise<Set<PeerId>>(Set())
|
||||
public var invitedPeers: Signal<Set<PeerId>, NoError> {
|
||||
private let inivitedPeersPromise = ValuePromise<[PeerId]>([])
|
||||
public var invitedPeers: Signal<[PeerId], NoError> {
|
||||
return self.inivitedPeersPromise.get()
|
||||
}
|
||||
|
||||
@ -433,7 +433,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
participantCount: participantsState.participantCount,
|
||||
callState: callState,
|
||||
topParticipants: participantsState.topParticipants,
|
||||
numberOfActiveSpeakers: participantsState.numberOfActiveSpeakers
|
||||
activeSpeakers: participantsState.activeSpeakers
|
||||
)
|
||||
})
|
||||
|
||||
@ -520,6 +520,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.VoiceChat_AnonymousDisabledAlertText, actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
|
||||
]), on: .root, blockInteraction: false, completion: {})
|
||||
} else if case .tooManyParticipants = error {
|
||||
let presentationData = strongSelf.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.VoiceChat_ChatFullAlertText, actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
|
||||
]), on: .root, blockInteraction: false, completion: {})
|
||||
}
|
||||
strongSelf._canBeRemoved.set(.single(true))
|
||||
}))
|
||||
@ -615,9 +620,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.participantsContext = participantsContext
|
||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
participantsContext.state,
|
||||
participantsContext.numberOfActiveSpeakers |> deliverOnMainQueue,
|
||||
participantsContext.activeSpeakers |> deliverOnMainQueue,
|
||||
self.speakingParticipantsContext.get() |> deliverOnMainQueue
|
||||
).start(next: { [weak self] state, numberOfActiveSpeakers, speakingParticipants in
|
||||
).start(next: { [weak self] state, activeSpeakers, speakingParticipants in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -685,7 +690,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
participantCount: state.totalCount,
|
||||
topParticipants: topParticipants,
|
||||
numberOfActiveSpeakers: numberOfActiveSpeakers
|
||||
activeSpeakers: activeSpeakers
|
||||
)))
|
||||
}))
|
||||
|
||||
@ -911,7 +916,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
var updatedInvitedPeers = self.invitedPeersValue
|
||||
updatedInvitedPeers.insert(peerId)
|
||||
updatedInvitedPeers.insert(peerId, at: 0)
|
||||
self.invitedPeersValue = updatedInvitedPeers
|
||||
|
||||
let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
||||
|
@ -21,6 +21,7 @@ import UndoUI
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import DirectionalPanGesture
|
||||
import PeerInfoUI
|
||||
|
||||
private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||
@ -53,6 +54,7 @@ private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
||||
})?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25)
|
||||
}
|
||||
|
||||
|
||||
private final class VoiceChatControllerTitleView: UIView {
|
||||
private var theme: PresentationTheme
|
||||
|
||||
@ -190,6 +192,7 @@ public final class VoiceChatController: ViewController {
|
||||
enum State {
|
||||
case listening
|
||||
case speaking
|
||||
case invited
|
||||
}
|
||||
|
||||
var peer: Peer
|
||||
@ -314,7 +317,7 @@ public final class VoiceChatController: ViewController {
|
||||
switch self {
|
||||
case let .invite(_, _, text):
|
||||
return VoiceChatActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .generic(UIImage(bundleImageName: "Chat/Context Menu/AddUser")!), action: {
|
||||
|
||||
interaction.openInvite()
|
||||
})
|
||||
case let .peer(peerEntry):
|
||||
let peer = peerEntry.peer
|
||||
@ -334,6 +337,9 @@ public final class VoiceChatController: ViewController {
|
||||
case .speaking:
|
||||
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
|
||||
icon = .microphone(false, UIColor(rgb: 0x34c759))
|
||||
case .invited:
|
||||
text = .text(presentationData.strings.VoiceChat_StatusInvited, .generic)
|
||||
icon = .invite(true)
|
||||
}
|
||||
|
||||
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
|
||||
@ -393,6 +399,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
private var currentGroupMembers: [RenderedChannelParticipant]?
|
||||
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
|
||||
private var currentInvitedPeers: [Peer]?
|
||||
private var currentSpeakingPeers: Set<PeerId>?
|
||||
private var accountPeer: Peer?
|
||||
|
||||
@ -426,6 +433,8 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
private var itemInteraction: Interaction?
|
||||
|
||||
private let inviteDisposable = MetaDisposable()
|
||||
|
||||
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
||||
self.controller = controller
|
||||
self.sharedContext = sharedContext
|
||||
@ -447,7 +456,6 @@ public final class VoiceChatController: ViewController {
|
||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
||||
self.listNode.clipsToBounds = true
|
||||
self.listNode.stackFromBottom = true
|
||||
self.listNode.keepMinimalScrollHeightWithTopInset = 0
|
||||
|
||||
self.topPanelNode = ASDisplayNode()
|
||||
self.topPanelNode.backgroundColor = panelBackgroundColor
|
||||
@ -502,11 +510,124 @@ public final class VoiceChatController: ViewController {
|
||||
self?.call.updateMuteState(peerId: peerId, isMuted: isMuted)
|
||||
}, openPeer: { [weak self] peerId in
|
||||
if let strongSelf = self, let navigationController = strongSelf.controller?.parentNavigationController {
|
||||
strongSelf.controller?.dismiss()
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
let context = strongSelf.context
|
||||
strongSelf.controller?.dismiss(completion: {
|
||||
Queue.mainQueue().justDispatch {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
})
|
||||
}
|
||||
}, openInvite: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
}, openInvite: {
|
||||
|
||||
let groupPeerId = strongSelf.call.peerId
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(groupPeerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { groupPeer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let groupPeer = groupPeer as? TelegramChannel else {
|
||||
return
|
||||
}
|
||||
|
||||
var filters: [ChannelMembersSearchFilter] = []
|
||||
if let currentCallMembers = strongSelf.currentCallMembers {
|
||||
filters.append(.disable(Array(currentCallMembers.map { $0.peer.id })))
|
||||
}
|
||||
if !groupPeer.hasPermission(.inviteMembers) {
|
||||
filters.append(.excludeNonMembers)
|
||||
}
|
||||
|
||||
var dismissController: (() -> Void)?
|
||||
let controller = ChannelMembersSearchController(context: strongSelf.context, peerId: groupPeer.id, forceTheme: strongSelf.darkTheme, mode: .inviteToCall, filters: filters, openPeer: { peer, participant in
|
||||
guard let strongSelf = self else {
|
||||
dismissController?()
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if peer.id == strongSelf.context.account.peerId {
|
||||
return
|
||||
}
|
||||
if let participant = participant {
|
||||
strongSelf.call.invitePeer(participant.peer.id)
|
||||
dismissController?()
|
||||
} else {
|
||||
let selfController = strongSelf.controller
|
||||
let inviteDisposable = strongSelf.inviteDisposable
|
||||
var inviteSignal = strongSelf.context.peerChannelMemberCategoriesContextsManager.addMembers(account: strongSelf.context.account, peerId: groupPeer.id, memberIds: [peer.id])
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { [weak selfController] subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
selfController?.present(controller, in: .window(.root))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
|
||||
inviteSignal = inviteSignal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
cancelImpl = {
|
||||
inviteDisposable.set(nil)
|
||||
}
|
||||
|
||||
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(next: { _ in
|
||||
guard let strongSelf = self else {
|
||||
dismissController?()
|
||||
return
|
||||
}
|
||||
strongSelf.call.invitePeer(peer.id)
|
||||
dismissController?()
|
||||
}, error: { error in
|
||||
dismissController?()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.Channel_ErrorAddTooMuch
|
||||
case .tooMuchJoined:
|
||||
text = presentationData.strings.Invite_ChannelsTooMuch
|
||||
case .generic:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
case .restricted:
|
||||
text = presentationData.strings.Channel_ErrorAddBlocked
|
||||
case .notMutualContact:
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
case .botDoesntSupportGroups:
|
||||
text = presentationData.strings.Channel_BotDoesntSupportGroups
|
||||
case .tooMuchBots:
|
||||
text = presentationData.strings.Channel_TooMuchBots
|
||||
case .bot:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}))
|
||||
}
|
||||
})
|
||||
dismissController = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
strongSelf.controller?.push(controller)
|
||||
})
|
||||
}, peerContextAction: { [weak self] entry, sourceNode, gesture in
|
||||
guard let strongSelf = self, let controller = strongSelf.controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
|
||||
return
|
||||
@ -604,21 +725,34 @@ public final class VoiceChatController: ViewController {
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.contentContainer)
|
||||
self.contentContainer.addSubnode(self.backgroundNode)
|
||||
|
||||
self.contentContainer.addSubnode(self.listNode)
|
||||
self.contentContainer.addSubnode(self.topPanelNode)
|
||||
self.contentContainer.addSubnode(self.leftBorderNode)
|
||||
self.contentContainer.addSubnode(self.rightBorderNode)
|
||||
self.contentContainer.addSubnode(self.bottomPanelNode)
|
||||
|
||||
self.memberStatesDisposable = (self.call.members
|
||||
|> deliverOnMainQueue).start(next: { [weak self] callMembers in
|
||||
|
||||
let context = self.context
|
||||
let invitedPeers: Signal<[Peer], NoError> = self.call.invitedPeers
|
||||
|> mapToSignal { ids -> Signal<[Peer], NoError> in
|
||||
return context.account.postbox.transaction { transaction -> [Peer] in
|
||||
return ids.compactMap(transaction.getPeer)
|
||||
}
|
||||
}
|
||||
|
||||
self.memberStatesDisposable = combineLatest(queue: .mainQueue(),
|
||||
self.call.members,
|
||||
invitedPeers
|
||||
).start(next: { [weak self] callMembers, invitedPeers in
|
||||
guard let strongSelf = self, let callMembers = callMembers else {
|
||||
return
|
||||
}
|
||||
if let groupMembers = strongSelf.currentGroupMembers {
|
||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: callMembers.participants, speakingPeers: callMembers.speakingParticipants)
|
||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: callMembers.participants, invitedPeers: invitedPeers, speakingPeers: callMembers.speakingParticipants)
|
||||
} else {
|
||||
strongSelf.currentCallMembers = callMembers.participants
|
||||
strongSelf.currentInvitedPeers = invitedPeers
|
||||
}
|
||||
|
||||
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers.totalCount)))
|
||||
@ -633,7 +767,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
if !strongSelf.didSetDataReady {
|
||||
strongSelf.accountPeer = accountPeer
|
||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: [], callMembers: strongSelf.currentCallMembers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: [], callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
||||
|
||||
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
|
||||
let addressName = channel.addressName ?? ""
|
||||
@ -671,7 +805,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
if wasMuted != (state.muteState != nil), let groupMembers = strongSelf.currentGroupMembers {
|
||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: strongSelf.currentCallMembers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
||||
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, groupMembers: groupMembers, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
|
||||
}
|
||||
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
@ -858,6 +992,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.memberStatesDisposable?.dispose()
|
||||
self.audioLevelsDisposable?.dispose()
|
||||
self.myAudioLevelDisposable?.dispose()
|
||||
self.inviteDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -936,7 +1071,7 @@ public final class VoiceChatController: ViewController {
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||
case .ended, .cancelled:
|
||||
self.hapticFeedback.impact(.light)
|
||||
|
||||
@ -951,7 +1086,7 @@ public final class VoiceChatController: ViewController {
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||
self.updateMembers(muteState: self.effectiveMuteState, groupMembers: self.currentGroupMembers ?? [], callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1299,6 +1434,10 @@ public final class VoiceChatController: ViewController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.layer.removeAllAnimations()
|
||||
strongSelf.dimNode.layer.removeAllAnimations()
|
||||
|
||||
var bounds = strongSelf.bounds
|
||||
bounds.origin.y = 0.0
|
||||
strongSelf.contentContainer.bounds = bounds
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
@ -1361,7 +1500,7 @@ public final class VoiceChatController: ViewController {
|
||||
})
|
||||
}
|
||||
|
||||
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, groupMembers: [RenderedChannelParticipant], callMembers: [GroupCallParticipantsContext.Participant], speakingPeers: Set<PeerId>) {
|
||||
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, groupMembers: [RenderedChannelParticipant], callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>) {
|
||||
var sortedCallMembers = callMembers
|
||||
sortedCallMembers.sort()
|
||||
|
||||
@ -1379,6 +1518,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.currentGroupMembers = groupMembers
|
||||
self.currentCallMembers = callMembers
|
||||
self.currentSpeakingPeers = speakingPeers
|
||||
self.currentInvitedPeers = invitedPeers
|
||||
|
||||
let previousEntries = self.currentEntries
|
||||
var entries: [ListEntry] = []
|
||||
@ -1387,7 +1527,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
var processedPeerIds = Set<PeerId>()
|
||||
|
||||
entries.append(.invite(self.presentationData.theme, self.presentationData.strings, "Invite Member"))
|
||||
entries.append(.invite(self.presentationData.theme, self.presentationData.strings, self.presentationData.strings.VoiceChat_InviteMember))
|
||||
|
||||
for member in callMembers {
|
||||
if processedPeerIds.contains(member.peer.id) {
|
||||
@ -1431,6 +1571,23 @@ public final class VoiceChatController: ViewController {
|
||||
)))
|
||||
}
|
||||
|
||||
for peer in invitedPeers {
|
||||
if processedPeerIds.contains(peer.id) {
|
||||
continue
|
||||
}
|
||||
processedPeerIds.insert(peer.id)
|
||||
|
||||
entries.append(.peer(PeerEntry(
|
||||
peer: peer,
|
||||
presence: nil,
|
||||
activityTimestamp: Int32.max - 1 - index,
|
||||
state: .invited,
|
||||
muteState: nil,
|
||||
canManageCall: false
|
||||
)))
|
||||
index += 1
|
||||
}
|
||||
|
||||
self.currentEntries = entries
|
||||
|
||||
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
|
||||
@ -1470,11 +1627,6 @@ public final class VoiceChatController: ViewController {
|
||||
var bounds = self.contentContainer.bounds
|
||||
bounds.origin.y = -translation.y
|
||||
bounds.origin.y = min(0.0, bounds.origin.y)
|
||||
if bounds.origin.y < 0.0 {
|
||||
//let delta = -bounds.origin.y
|
||||
//bounds.origin.y = -((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0)
|
||||
}
|
||||
|
||||
self.contentContainer.bounds = bounds
|
||||
case .ended:
|
||||
let translation = recognizer.translation(in: self.contentContainer.view)
|
||||
@ -1533,6 +1685,9 @@ public final class VoiceChatController: ViewController {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public var onViewDidAppear: (() -> Void)?
|
||||
public var onViewDidDisappear: (() -> Void)?
|
||||
|
||||
private var didAppearOnce: Bool = false
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
@ -1594,6 +1749,10 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.idleTimerExtensionDisposable.set(self.sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.onViewDidAppear?()
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewDidDisappear(_ animated: Bool) {
|
||||
@ -1608,7 +1767,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.didAppearOnce = false
|
||||
|
||||
completion?()
|
||||
self.presentingViewController?.dismiss(animated: false)
|
||||
self.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1619,8 +1778,12 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.controllerNode.animateOut(completion: { [weak self] in
|
||||
completion?()
|
||||
self?.presentingViewController?.dismiss(animated: false)
|
||||
self?.dismiss(animated: false)
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.onViewDidDisappear?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,6 +256,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
||||
public enum JoinGroupCallError {
|
||||
case generic
|
||||
case anonymousNotAllowed
|
||||
case tooManyParticipants
|
||||
}
|
||||
|
||||
public struct JoinGroupCallResult {
|
||||
@ -272,6 +273,8 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
||||
|> mapError { error -> JoinGroupCallError in
|
||||
if error.errorDescription == "GROUP_CALL_ANONYMOUS_FORBIDDEN" {
|
||||
return .anonymousNotAllowed
|
||||
} else if error.errorDescription == "GROUPCALL_PARTICIPANTS_TOO_MUCH" {
|
||||
return .tooManyParticipants
|
||||
}
|
||||
return .generic
|
||||
}
|
||||
@ -633,16 +636,16 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
}
|
||||
|
||||
private var numberOfActiveSpeakersValue: Int = 0 {
|
||||
private var activeSpeakersValue: Set<PeerId> = Set() {
|
||||
didSet {
|
||||
if self.numberOfActiveSpeakersValue != oldValue {
|
||||
self.numberOfActiveSpeakersPromise.set(self.numberOfActiveSpeakersValue)
|
||||
if self.activeSpeakersValue != oldValue {
|
||||
self.activeSpeakersPromise.set(self.activeSpeakersValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
private let numberOfActiveSpeakersPromise = ValuePromise<Int>(0)
|
||||
public var numberOfActiveSpeakers: Signal<Int, NoError> {
|
||||
return self.numberOfActiveSpeakersPromise.get()
|
||||
private let activeSpeakersPromise = ValuePromise<Set<PeerId>>(Set())
|
||||
public var activeSpeakers: Signal<Set<PeerId>, NoError> {
|
||||
return self.activeSpeakersPromise.get()
|
||||
}
|
||||
|
||||
private var updateQueue: [Update.StateUpdate] = []
|
||||
@ -684,7 +687,9 @@ public final class GroupCallParticipantsContext {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.numberOfActiveSpeakersValue = activities.count
|
||||
strongSelf.activeSpeakersValue = Set(activities.map { item -> PeerId in
|
||||
item.0
|
||||
})
|
||||
|
||||
if !strongSelf.hasReceivedSpeackingParticipantsReport {
|
||||
var updatedParticipants = strongSelf.stateValue.state.participants
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -10,14 +10,11 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import LiveLocationManager
|
||||
import TemporaryCachedPeerDataManager
|
||||
#if ENABLE_WALLET
|
||||
import WalletCore
|
||||
import WalletUI
|
||||
#endif
|
||||
import PhoneNumberFormat
|
||||
import TelegramUIPreferences
|
||||
import TelegramVoip
|
||||
import TelegramCallsUI
|
||||
import TelegramBaseController
|
||||
|
||||
private final class DeviceSpecificContactImportContext {
|
||||
let disposable = MetaDisposable()
|
||||
@ -111,10 +108,6 @@ public final class AccountContextImpl: AccountContext {
|
||||
}
|
||||
public let account: Account
|
||||
|
||||
#if ENABLE_WALLET
|
||||
public let tonContext: StoredTonContext?
|
||||
#endif
|
||||
|
||||
public let fetchManager: FetchManager
|
||||
private let prefetchManager: PrefetchManager?
|
||||
|
||||
@ -159,38 +152,12 @@ public final class AccountContextImpl: AccountContext {
|
||||
|
||||
private var experimentalUISettingsDisposable: Disposable?
|
||||
|
||||
#if ENABLE_WALLET
|
||||
public var hasWallets: Signal<Bool, NoError> {
|
||||
return WalletStorageInterfaceImpl(postbox: self.account.postbox).getWalletRecords()
|
||||
|> map { records in
|
||||
return !records.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
public var hasWalletAccess: Signal<Bool, NoError> {
|
||||
return self.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> map { view -> Bool in
|
||||
guard let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration else {
|
||||
return false
|
||||
}
|
||||
let walletConfiguration = WalletConfiguration.with(appConfiguration: appConfiguration)
|
||||
if walletConfiguration.config != nil && walletConfiguration.blockchainName != nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
#endif
|
||||
public let cachedGroupCallContexts: AccountGroupCallContextCache
|
||||
|
||||
public init(sharedContext: SharedAccountContextImpl, account: Account, /*tonContext: StoredTonContext?, */limitsConfiguration: LimitsConfiguration, contentSettings: ContentSettings, appConfiguration: AppConfiguration, temp: Bool = false)
|
||||
{
|
||||
self.sharedContextImpl = sharedContext
|
||||
self.account = account
|
||||
#if ENABLE_WALLET
|
||||
self.tonContext = tonContext
|
||||
#endif
|
||||
|
||||
self.downloadedMediaStoreManager = DownloadedMediaStoreManagerImpl(postbox: account.postbox, accountManager: sharedContext.accountManager)
|
||||
|
||||
@ -216,6 +183,8 @@ public final class AccountContextImpl: AccountContext {
|
||||
self.peersNearbyManager = nil
|
||||
}
|
||||
|
||||
self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl()
|
||||
|
||||
let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration])
|
||||
|> map { preferences -> LimitsConfiguration in
|
||||
return preferences.values[PreferencesKeys.limitsConfiguration] as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
|
||||
|
@ -402,11 +402,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
switch chatLocation {
|
||||
case let .peer(peerId):
|
||||
locationBroadcastPanelSource = .peer(peerId)
|
||||
switch subject {
|
||||
case .message, .none:
|
||||
groupCallPanelSource = .peer(peerId)
|
||||
default:
|
||||
groupCallPanelSource = .none
|
||||
}
|
||||
self.chatLocationInfoData = .peer(Promise())
|
||||
case let .replyThread(replyThreadMessage):
|
||||
locationBroadcastPanelSource = .none
|
||||
groupCallPanelSource = .all
|
||||
groupCallPanelSource = .none
|
||||
let promise = Promise<Message?>()
|
||||
let key = PostboxViewKey.messages([replyThreadMessage.messageId])
|
||||
promise.set(context.account.postbox.combinedView(keys: [key])
|
||||
|
@ -5011,7 +5011,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
})
|
||||
}
|
||||
} else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .members = currentPaneKey {
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChannelMembersSearchContainerNode(context: self.context, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: self.groupMembersSearchContext, openPeer: { [weak self] peer, participant in
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: nil, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: self.groupMembersSearchContext, openPeer: { [weak self] peer, participant in
|
||||
self?.openPeer(peerId: peer.id, navigation: .info)
|
||||
}, updateActivity: { _ in
|
||||
}, pushController: { [weak self] c in
|
||||
|
@ -103,6 +103,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
private let callState = Promise<PresentationCallState?>(nil)
|
||||
|
||||
private var groupCallController: VoiceChatController?
|
||||
private let hasGroupCallOnScreen = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
|
||||
private var immediateHasOngoingCallValue = Atomic<Bool>(value: false)
|
||||
public var immediateHasOngoingCall: Bool {
|
||||
@ -637,12 +638,24 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
strongSelf.groupCallController = nil
|
||||
strongSelf.hasOngoingCall.set(false)
|
||||
|
||||
if let call = call {
|
||||
if let call = call, let navigationController = mainWindow.viewController as? NavigationController {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
strongSelf.hasGroupCallOnScreen.set(true)
|
||||
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
||||
groupCallController.parentNavigationController = mainWindow.viewController as? NavigationController
|
||||
groupCallController.onViewDidAppear = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.hasGroupCallOnScreen.set(true)
|
||||
}
|
||||
}
|
||||
groupCallController.onViewDidDisappear = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.hasGroupCallOnScreen.set(false)
|
||||
}
|
||||
}
|
||||
groupCallController.navigationPresentation = .flatModal
|
||||
groupCallController.parentNavigationController = navigationController
|
||||
strongSelf.groupCallController = groupCallController
|
||||
strongSelf.mainWindow?.present(groupCallController, on: .calls)
|
||||
navigationController.pushViewController(groupCallController)
|
||||
strongSelf.hasOngoingCall.set(true)
|
||||
} else {
|
||||
strongSelf.hasOngoingCall.set(false)
|
||||
@ -662,21 +675,20 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|
||||
self.callStateDisposable = combineLatest(queue: .mainQueue(),
|
||||
callSignal,
|
||||
groupCallSignal
|
||||
).start(next: { [weak self] call, groupCall in
|
||||
groupCallSignal,
|
||||
self.hasGroupCallOnScreen.get()
|
||||
).start(next: { [weak self] call, groupCall, hasGroupCallOnScreen in
|
||||
if let strongSelf = self {
|
||||
let statusBarContent: CallStatusBarNodeImpl.Content?
|
||||
|
||||
if let call = call {
|
||||
statusBarContent = .call(strongSelf, call.account, call)
|
||||
} else if let groupCall = groupCall {
|
||||
} else if let groupCall = groupCall, !hasGroupCallOnScreen {
|
||||
statusBarContent = .groupCall(strongSelf, groupCall.account, groupCall)
|
||||
} else {
|
||||
statusBarContent = nil
|
||||
}
|
||||
|
||||
var resolvedCallStatusBarNode: CallStatusBarNodeImpl?
|
||||
|
||||
if let statusBarContent = statusBarContent {
|
||||
if let current = strongSelf.currentCallStatusBarNode {
|
||||
resolvedCallStatusBarNode = current
|
||||
@ -712,7 +724,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
if groupCallController.isNodeLoaded {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if groupCallController.view.superview == nil {
|
||||
mainWindow.present(groupCallController, on: .calls)
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -987,7 +999,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
} else if let groupCallController = self.groupCallController {
|
||||
if groupCallController.isNodeLoaded && groupCallController.view.superview == nil {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
mainWindow.present(groupCallController, on: .calls)
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user