mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Voice Chat UI improvements
This commit is contained in:
parent
2d890dd1e2
commit
2368dc4917
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
@ -568,7 +569,7 @@ public protocol SharedAccountContext: class {
|
||||
|
||||
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController
|
||||
|
||||
func navigateToCurrentCall()
|
||||
func navigateToCurrentCall(sourcePanel: ASDisplayNode?)
|
||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||
var immediateHasOngoingCall: Bool { get }
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
@ -246,6 +247,8 @@ public protocol PresentationGroupCall: class {
|
||||
|
||||
func invitePeer(_ peerId: PeerId)
|
||||
var invitedPeers: Signal<Set<PeerId>, NoError> { get }
|
||||
|
||||
var sourcePanel: ASDisplayNode? { get set }
|
||||
}
|
||||
|
||||
public protocol PresentationCallManager: class {
|
||||
@ -253,5 +256,5 @@ public protocol PresentationCallManager: class {
|
||||
var currentGroupCallSignal: Signal<PresentationGroupCall?, NoError> { get }
|
||||
|
||||
func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult
|
||||
func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool) -> RequestOrJoinGroupCallResult
|
||||
func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool, sourcePanel: ASDisplayNode?) -> RequestOrJoinGroupCallResult
|
||||
}
|
||||
|
@ -281,7 +281,7 @@ public final class CallListController: ViewController {
|
||||
if case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peerId {
|
||||
began?()
|
||||
strongSelf.context.sharedContext.navigateToCurrentCall()
|
||||
strongSelf.context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
|
||||
} else {
|
||||
let presentationData = strongSelf.presentationData
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
|
@ -126,7 +126,7 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|
||||
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: false, endCurrentIfAny: false)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peerId {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
|
||||
} else {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
|
@ -391,12 +391,17 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
if let inCallStatusBar = self.inCallStatusBar {
|
||||
let inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(layout.statusBarHeight ?? 0.0, max(40.0, layout.safeInsets.top))))
|
||||
var inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(layout.statusBarHeight ?? 0.0, max(40.0, layout.safeInsets.top))))
|
||||
if layout.deviceMetrics.hasTopNotch {
|
||||
inCallStatusBarFrame.size.height += 12.0
|
||||
}
|
||||
if inCallStatusBar.frame.isEmpty {
|
||||
inCallStatusBar.frame = inCallStatusBarFrame
|
||||
} else {
|
||||
transition.updateFrame(node: inCallStatusBar, frame: inCallStatusBarFrame)
|
||||
}
|
||||
inCallStatusBar.callStatusBarNode?.update(size: inCallStatusBarFrame.size)
|
||||
inCallStatusBar.callStatusBarNode?.frame = inCallStatusBarFrame
|
||||
layout.statusBarHeight = inCallStatusBarFrame.height
|
||||
self.inCallStatusBar?.frame = inCallStatusBarFrame
|
||||
}
|
||||
@ -1339,13 +1344,14 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
}
|
||||
|
||||
public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) {
|
||||
if let forceInCallStatusBarText = forceInCallStatusBarText {
|
||||
public func setForceInCallStatusBar(_ forceInCallStatusBar: CallStatusBarNode?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) {
|
||||
if let forceInCallStatusBar = forceInCallStatusBar {
|
||||
let inCallStatusBar: StatusBar
|
||||
if let current = self.inCallStatusBar {
|
||||
inCallStatusBar = current
|
||||
} else {
|
||||
inCallStatusBar = StatusBar()
|
||||
inCallStatusBar.clipsToBounds = false
|
||||
inCallStatusBar.inCallNavigate = { [weak self] in
|
||||
self?.scrollToTop(.master)
|
||||
}
|
||||
@ -1371,7 +1377,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: transition)
|
||||
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallText: forceInCallStatusBarText, animated: false)
|
||||
inCallStatusBar.updateState(statusBar: nil, withSafeInsets: !layout.safeInsets.top.isZero, inCallNode: forceInCallStatusBar, animated: false)
|
||||
}
|
||||
} else if let inCallStatusBar = self.inCallStatusBar {
|
||||
self.inCallStatusBar = nil
|
||||
|
@ -269,7 +269,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
private var title: String? {
|
||||
didSet {
|
||||
if let title = self.title {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.accessibilityLabel = title
|
||||
if self.titleNode.supernode == nil {
|
||||
self.buttonsContainerNode.addSubnode(self.titleNode)
|
||||
|
@ -25,7 +25,11 @@ public class StatusBarSurface {
|
||||
}
|
||||
}
|
||||
|
||||
private let inCallBackgroundColor = UIColor(rgb: 0x43d551)
|
||||
open class CallStatusBarNode: ASDisplayNode {
|
||||
open func update(size: CGSize) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func addInCallAnimation(_ layer: CALayer) {
|
||||
let animation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
@ -92,10 +96,7 @@ public final class StatusBar: ASDisplayNode {
|
||||
private var removeProxyNodeScheduled = false
|
||||
|
||||
let offsetNode = ASDisplayNode()
|
||||
private let inCallBackgroundNode = ASDisplayNode()
|
||||
private let inCallLabel: StatusBarLabelNode
|
||||
|
||||
private var inCallText: String? = nil
|
||||
var callStatusBarNode: CallStatusBarNode? = nil
|
||||
|
||||
public var verticalOffset: CGFloat = 0.0 {
|
||||
didSet {
|
||||
@ -113,14 +114,8 @@ public final class StatusBar: ASDisplayNode {
|
||||
}
|
||||
|
||||
public override init() {
|
||||
self.inCallLabel = StatusBarLabelNode()
|
||||
self.inCallLabel.isUserInteractionEnabled = false
|
||||
|
||||
self.offsetNode.isUserInteractionEnabled = false
|
||||
|
||||
let labelSize = self.inCallLabel.updateLayout(CGSize(width: 300.0, height: 300.0))
|
||||
self.inCallLabel.frame = CGRect(origin: CGPoint(x: 10.0, y: 20.0 + 4.0), size: labelSize)
|
||||
|
||||
super.init()
|
||||
|
||||
self.setViewBlock({
|
||||
@ -130,18 +125,17 @@ public final class StatusBar: ASDisplayNode {
|
||||
(self.view as! StatusBarView).node = self
|
||||
|
||||
self.addSubnode(self.offsetNode)
|
||||
self.addSubnode(self.inCallBackgroundNode)
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
func updateState(statusBar: UIView?, withSafeInsets: Bool, inCallText: String?, animated: Bool) {
|
||||
func updateState(statusBar: UIView?, withSafeInsets: Bool, inCallNode: CallStatusBarNode?, animated: Bool) {
|
||||
if let statusBar = statusBar {
|
||||
self.removeProxyNodeScheduled = false
|
||||
let resolvedStyle: StatusBarStyle
|
||||
if inCallText != nil && !self.ignoreInCall {
|
||||
if inCallNode != nil && !self.ignoreInCall {
|
||||
resolvedStyle = .White
|
||||
} else {
|
||||
resolvedStyle = self.statusBarStyle
|
||||
@ -176,46 +170,35 @@ public final class StatusBar: ASDisplayNode {
|
||||
ignoreInCall = true
|
||||
}
|
||||
|
||||
var resolvedInCallText: String? = inCallText
|
||||
var resolvedCallStatusBarNode: CallStatusBarNode? = inCallNode
|
||||
if ignoreInCall {
|
||||
resolvedInCallText = nil
|
||||
resolvedCallStatusBarNode = nil
|
||||
}
|
||||
|
||||
if (resolvedInCallText != nil) != (self.inCallText != nil) {
|
||||
if let _ = resolvedInCallText {
|
||||
if !withSafeInsets {
|
||||
self.addSubnode(self.inCallLabel)
|
||||
}
|
||||
addInCallAnimation(self.inCallLabel.layer)
|
||||
|
||||
self.inCallBackgroundNode.layer.backgroundColor = inCallBackgroundColor.cgColor
|
||||
if (resolvedCallStatusBarNode != nil) != (self.callStatusBarNode != nil) {
|
||||
if let resolvedCallStatusBarNode = resolvedCallStatusBarNode {
|
||||
self.addSubnode(resolvedCallStatusBarNode)
|
||||
if animated {
|
||||
self.inCallBackgroundNode.layer.animate(from: UIColor.clear.cgColor, to: inCallBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3)
|
||||
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 {
|
||||
self.inCallLabel.removeFromSupernode()
|
||||
|
||||
self.inCallBackgroundNode.layer.backgroundColor = UIColor.clear.cgColor
|
||||
if animated {
|
||||
self.inCallBackgroundNode.layer.animate(from: inCallBackgroundColor.cgColor, to: UIColor.clear.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3)
|
||||
callStatusBarNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let resolvedInCallText = resolvedInCallText {
|
||||
if self.inCallText != resolvedInCallText {
|
||||
self.inCallLabel.attributedText = NSAttributedString(string: resolvedInCallText, font: Font.regular(14.0), textColor: .white)
|
||||
}
|
||||
|
||||
self.layoutInCallLabel()
|
||||
}
|
||||
|
||||
self.inCallText = resolvedInCallText
|
||||
self.callStatusBarNode = resolvedCallStatusBarNode
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.bounds.contains(point) && self.inCallText != nil {
|
||||
if self.bounds.contains(point) && self.callStatusBarNode != nil {
|
||||
return self.view
|
||||
} else {
|
||||
return nil
|
||||
@ -223,42 +206,8 @@ public final class StatusBar: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state, self.inCallText != nil {
|
||||
if case .ended = recognizer.state, self.callStatusBarNode != nil {
|
||||
self.inCallNavigate?()
|
||||
}
|
||||
}
|
||||
|
||||
override public func layout() {
|
||||
super.layout()
|
||||
|
||||
self.layoutInCallLabel()
|
||||
}
|
||||
|
||||
override public var frame: CGRect {
|
||||
didSet {
|
||||
if oldValue.size != self.frame.size {
|
||||
let bounds = self.bounds
|
||||
self.inCallBackgroundNode.frame = CGRect(origin: CGPoint(), size: bounds.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public var bounds: CGRect {
|
||||
didSet {
|
||||
if oldValue.size != self.bounds.size {
|
||||
let bounds = self.bounds
|
||||
self.inCallBackgroundNode.frame = CGRect(origin: CGPoint(), size: bounds.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func layoutInCallLabel() {
|
||||
if self.inCallLabel.supernode != nil {
|
||||
let size = self.bounds.size
|
||||
if !size.width.isZero && !size.height.isZero {
|
||||
let labelSize = self.inCallLabel.updateLayout(size)
|
||||
self.inCallLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: 20.0 + floor((20.0 - labelSize.height) / 2.0)), size: labelSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -880,7 +880,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: user.id, isVideo: false, endCurrentIfAny: false)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == user.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
|
||||
} else {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
|
@ -876,7 +876,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peer.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
|
||||
} else {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
|
@ -21,7 +21,6 @@ swift_library(
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/TelegramCallsUI:TelegramCallsUI",
|
||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -12,6 +12,7 @@ import UniversalMediaPlayer
|
||||
import AccountContext
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
import TelegramCallsUI
|
||||
|
||||
public enum MediaAccessoryPanelVisibility {
|
||||
case none
|
||||
@ -25,34 +26,6 @@ public enum LocationBroadcastPanelSource {
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
public enum GroupCallPanelSource {
|
||||
case none
|
||||
case all
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
final class GroupCallPanelData {
|
||||
let peerId: PeerId
|
||||
let info: GroupCallInfo
|
||||
let topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
let participantCount: Int
|
||||
let groupCall: PresentationGroupCall?
|
||||
|
||||
init(
|
||||
peerId: PeerId,
|
||||
info: GroupCallInfo,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
participantCount: Int,
|
||||
groupCall: PresentationGroupCall?
|
||||
) {
|
||||
self.peerId = peerId
|
||||
self.info = info
|
||||
self.topParticipants = topParticipants
|
||||
self.participantCount = participantCount
|
||||
self.groupCall = groupCall
|
||||
}
|
||||
}
|
||||
|
||||
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
||||
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
||||
if let message = message, let strongController = controller {
|
||||
@ -430,7 +403,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
}
|
||||
strongSelf.joinGroupCall(
|
||||
peerId: groupCallPanelData.peerId,
|
||||
info: groupCallPanelData.info
|
||||
info: groupCallPanelData.info,
|
||||
sourcePanel: strongSelf.groupCallAccessoryPanel
|
||||
)
|
||||
})
|
||||
if let navigationBar = self.navigationBar {
|
||||
@ -847,11 +821,11 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
})]
|
||||
}
|
||||
|
||||
private func joinGroupCall(peerId: PeerId, info: GroupCallInfo) {
|
||||
let callResult = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: false)
|
||||
private func joinGroupCall(peerId: PeerId, info: GroupCallInfo, sourcePanel: GroupCallNavigationAccessoryPanel?) {
|
||||
let callResult = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: false, sourcePanel: sourcePanel)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peerId {
|
||||
self.context.sharedContext.navigateToCurrentCall()
|
||||
self.context.sharedContext.navigateToCurrentCall(sourcePanel: sourcePanel)
|
||||
} else {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
@ -867,7 +841,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
if let current = current {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: true)
|
||||
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peerId, initialCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash), endCurrentIfAny: true, sourcePanel: sourcePanel)
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
} else {
|
||||
|
@ -34,6 +34,7 @@ swift_library(
|
||||
"//submodules/AnimationUI:AnimationUI",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/AudioBlob:AudioBlob",
|
||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1238,7 +1238,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
if !self.shouldStayHiddenUntilConnection || self.containerNode.alpha > 0.0 {
|
||||
self.containerNode.layer.allowsGroupOpacity = true
|
||||
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.containerNode.layer.allowsGroupOpacity = true
|
||||
self?.containerNode.layer.allowsGroupOpacity = false
|
||||
})
|
||||
self.containerNode.layer.animateScale(from: 1.0, to: 1.04, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
|
@ -15,7 +15,35 @@ import AnimatedAvatarSetNode
|
||||
private let titleFont = Font.semibold(15.0)
|
||||
private let subtitleFont = Font.regular(13.0)
|
||||
|
||||
final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
public enum GroupCallPanelSource {
|
||||
case none
|
||||
case all
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
public final class GroupCallPanelData {
|
||||
public let peerId: PeerId
|
||||
public let info: GroupCallInfo
|
||||
public let topParticipants: [GroupCallParticipantsContext.Participant]
|
||||
public let participantCount: Int
|
||||
public let groupCall: PresentationGroupCall?
|
||||
|
||||
public init(
|
||||
peerId: PeerId,
|
||||
info: GroupCallInfo,
|
||||
topParticipants: [GroupCallParticipantsContext.Participant],
|
||||
participantCount: Int,
|
||||
groupCall: PresentationGroupCall?
|
||||
) {
|
||||
self.peerId = peerId
|
||||
self.info = info
|
||||
self.topParticipants = topParticipants
|
||||
self.participantCount = participantCount
|
||||
self.groupCall = groupCall
|
||||
}
|
||||
}
|
||||
|
||||
public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
@ -31,12 +59,11 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
private let joinButtonBackgroundNode: ASImageNode
|
||||
|
||||
private let micButton: HighlightTrackingButtonNode
|
||||
private let micButtonForegroundMutedNode: ASImageNode
|
||||
private let micButtonForegroundUnmutedNode: ASImageNode
|
||||
private let micButtonForegroundNode: VoiceChatMicrophoneNode
|
||||
private let micButtonBackgroundNode: ASImageNode
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
let titleNode: ImmediateTextNode
|
||||
let textNode: ImmediateTextNode
|
||||
private let muteIconNode: ASImageNode
|
||||
|
||||
private let avatarsContext: AnimatedAvatarSetContext
|
||||
@ -51,7 +78,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
private var currentData: GroupCallPanelData?
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, tapAction: @escaping () -> Void) {
|
||||
public init(context: AccountContext, presentationData: PresentationData, tapAction: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.theme = presentationData.theme
|
||||
self.strings = presentationData.strings
|
||||
@ -67,8 +94,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
self.joinButtonBackgroundNode = ASImageNode()
|
||||
|
||||
self.micButton = HighlightTrackingButtonNode()
|
||||
self.micButtonForegroundMutedNode = ASImageNode()
|
||||
self.micButtonForegroundUnmutedNode = ASImageNode()
|
||||
self.micButtonForegroundNode = VoiceChatMicrophoneNode()
|
||||
self.micButtonBackgroundNode = ASImageNode()
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
@ -117,8 +143,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
self.joinButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
|
||||
|
||||
self.micButton.addSubnode(self.micButtonBackgroundNode)
|
||||
self.micButton.addSubnode(self.micButtonForegroundMutedNode)
|
||||
self.micButton.addSubnode(self.micButtonForegroundUnmutedNode)
|
||||
self.micButton.addSubnode(self.micButtonForegroundNode)
|
||||
self.contentNode.addSubnode(self.micButton)
|
||||
self.micButton.addTarget(self, action: #selector(self.micTapped), forControlEvents: [.touchUpInside])
|
||||
|
||||
@ -132,7 +157,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
self.isMutedDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.micButtonPressGesture(_:)))
|
||||
@ -187,9 +212,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
self.joinButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: presentationData.theme.chat.inputPanel.actionControlFillColor)
|
||||
|
||||
//TODO:localize
|
||||
self.micButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: UIColor(rgb: 0x30B251))
|
||||
self.micButtonForegroundMutedNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: .white)
|
||||
self.micButtonForegroundUnmutedNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: .white)
|
||||
self.micButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: UIColor(rgb: 0x30b251))
|
||||
|
||||
//TODO:localize
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Voice Chat", font: Font.semibold(15.0), textColor: presentationData.theme.chat.inputPanel.primaryTextColor)
|
||||
@ -198,7 +221,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
self.muteIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(presentationData.theme)
|
||||
}
|
||||
|
||||
func update(data: GroupCallPanelData) {
|
||||
public func update(data: GroupCallPanelData) {
|
||||
let previousData = self.currentData
|
||||
self.currentData = data
|
||||
|
||||
@ -244,8 +267,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.micButtonForegroundMutedNode.isHidden = !isMuted
|
||||
strongSelf.micButtonForegroundUnmutedNode.isHidden = isMuted
|
||||
strongSelf.micButtonForegroundNode.update(state: VoiceChatMicrophoneNode.State(muted: isMuted, color: UIColor.white), animated: true)
|
||||
}))
|
||||
}
|
||||
} else if data.groupCall == nil {
|
||||
@ -266,7 +288,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, leftInset, rightInset)
|
||||
|
||||
let panelHeight = size.height
|
||||
@ -291,17 +313,14 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
let micButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 7.0 - micButtonSize.width, y: floor((panelHeight - micButtonSize.height) / 2.0)), size: micButtonSize)
|
||||
transition.updateFrame(node: self.micButton, frame: micButtonFrame)
|
||||
transition.updateFrame(node: self.micButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: micButtonFrame.size))
|
||||
if let image = self.micButtonForegroundMutedNode.image {
|
||||
transition.updateFrame(node: self.micButtonForegroundMutedNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - image.size.width) / 2.0), y: floor((micButtonFrame.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
if let image = self.micButtonForegroundUnmutedNode.image {
|
||||
transition.updateFrame(node: self.micButtonForegroundUnmutedNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - image.size.width) / 2.0), y: floor((micButtonFrame.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
|
||||
let animationSize = CGSize(width: 36.0, height: 36.0)
|
||||
transition.updateFrame(node: self.micButtonForegroundNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - animationSize.width) / 2.0), y: floor((micButtonFrame.height - animationSize.height) / 2.0)), size: animationSize))
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: 10.0), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: 9.0), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: textSize))
|
||||
|
||||
@ -315,7 +334,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
}
|
||||
|
||||
func animateIn(_ transition: ContainedViewLayoutTransition) {
|
||||
public func animateIn(_ transition: ContainedViewLayoutTransition) {
|
||||
self.clipsToBounds = true
|
||||
let contentPosition = self.contentNode.layer.position
|
||||
transition.animatePosition(node: self.contentNode, from: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0), completion: { [weak self] _ in
|
||||
@ -323,7 +342,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
func animateOut(_ transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
public func animateOut(_ transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
self.clipsToBounds = true
|
||||
let contentPosition = self.contentNode.layer.position
|
||||
transition.animatePosition(node: self.contentNode, to: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0), removeOnCompletion: false, completion: { [weak self] _ in
|
||||
@ -331,4 +350,36 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func rightButtonSnapshotViews() -> (background: UIView, foreground: UIView)? {
|
||||
if !self.joinButton.isHidden {
|
||||
if let foregroundView = self.joinButtonTitleNode.view.snapshotContentTree() {
|
||||
let backgroundFrame = self.joinButtonBackgroundNode.view.convert(self.joinButtonBackgroundNode.bounds, to: nil)
|
||||
let foregroundFrame = self.joinButtonTitleNode.view.convert(self.joinButtonTitleNode.bounds, to: nil)
|
||||
|
||||
let backgroundView = UIView()
|
||||
backgroundView.backgroundColor = self.theme.chat.inputPanel.actionControlFillColor
|
||||
backgroundView.frame = backgroundFrame
|
||||
backgroundView.layer.cornerRadius = backgroundFrame.height / 2.0
|
||||
|
||||
foregroundView.frame = foregroundFrame
|
||||
return (backgroundView, foregroundView)
|
||||
}
|
||||
} else if !self.micButton.isHidden {
|
||||
if let foregroundView = self.micButtonForegroundNode.view.snapshotContentTree() {
|
||||
let backgroundFrame = self.micButtonBackgroundNode.view.convert(self.micButtonBackgroundNode.bounds, to: nil)
|
||||
let foregroundFrame = self.micButtonForegroundNode.view.convert(self.micButtonForegroundNode.bounds, to: nil)
|
||||
|
||||
let backgroundView = UIView()
|
||||
backgroundView.backgroundColor = UIColor(rgb: 0x30b251)
|
||||
backgroundView.frame = backgroundFrame
|
||||
backgroundView.layer.cornerRadius = backgroundFrame.height / 2.0
|
||||
|
||||
foregroundView.frame = foregroundFrame
|
||||
return (backgroundView, foregroundView)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
@ -592,9 +593,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}
|
||||
}
|
||||
|
||||
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool) -> RequestOrJoinGroupCallResult {
|
||||
public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId, initialCall: CachedChannelData.ActiveCall?, endCurrentIfAny: Bool, sourcePanel: ASDisplayNode?) -> RequestOrJoinGroupCallResult {
|
||||
let begin: () -> Void = { [weak self] in
|
||||
let _ = self?.startGroupCall(accountContext: context, peerId: peerId, initialCall: initialCall).start()
|
||||
let _ = self?.startGroupCall(accountContext: context, peerId: peerId, initialCall: initialCall, sourcePanel: sourcePanel).start()
|
||||
}
|
||||
if let currentGroupCall = self.currentGroupCallValue {
|
||||
if endCurrentIfAny {
|
||||
@ -618,7 +619,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
accountContext: AccountContext,
|
||||
peerId: PeerId,
|
||||
initialCall: CachedChannelData.ActiveCall?,
|
||||
internalId: CallSessionInternalId = CallSessionInternalId()
|
||||
internalId: CallSessionInternalId = CallSessionInternalId(),
|
||||
sourcePanel: ASDisplayNode?
|
||||
) -> Signal<Bool, NoError> {
|
||||
let (presentationData, present, openSettings) = self.getDeviceAccessData()
|
||||
|
||||
@ -669,6 +671,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
peerId: peerId,
|
||||
peer: nil
|
||||
)
|
||||
call.sourcePanel = sourcePanel
|
||||
strongSelf.updateCurrentGroupCall(call)
|
||||
strongSelf.currentGroupCallPromise.set(.single(call))
|
||||
strongSelf.hasActiveCallsPromise.set(true)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
@ -256,6 +257,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private var checkCallDisposable: Disposable?
|
||||
private var isCurrentlyConnecting: Bool?
|
||||
|
||||
public weak var sourcePanel: ASDisplayNode?
|
||||
|
||||
init(
|
||||
accountContext: AccountContext,
|
||||
audioSession: ManagedAudioSession,
|
||||
|
@ -6,6 +6,23 @@ import Display
|
||||
private let titleFont = Font.regular(17.0)
|
||||
private let subtitleFont = Font.regular(13.0)
|
||||
|
||||
private let blue = UIColor(rgb: 0x0078ff)
|
||||
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
||||
private let green = UIColor(rgb: 0x33c659)
|
||||
|
||||
private let deviceScale = UIScreen.main.scale
|
||||
|
||||
private let radialMaskImage = generateImage(CGSize(width: 100.0, height: 100.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let maskColors: [CGColor] = [UIColor(rgb: 0xffffff, alpha: 0.75).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
|
||||
let maskGradient = CGGradient(colorsSpace: colorSpace, colors: maskColors as CFArray, locations: &locations)!
|
||||
let maskGradientCenter = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
context.drawRadialGradient(maskGradient, startCenter: maskGradientCenter, startRadius: 0.0, endCenter: maskGradientCenter, endRadius: size.width / 2.0, options: .drawsAfterEndLocation)
|
||||
}, opaque: false, scale: deviceScale)!
|
||||
|
||||
enum VoiceChatActionButtonState {
|
||||
enum ActiveState {
|
||||
case cantSpeak
|
||||
@ -24,29 +41,51 @@ private enum VoiceChatActionButtonBackgroundNodeType {
|
||||
}
|
||||
|
||||
private protocol VoiceChatActionButtonBackgroundNodeState: NSObjectProtocol {
|
||||
var blueGradient: UIImage? { get set }
|
||||
var greenGradient: UIImage? { get set }
|
||||
|
||||
var frameInterval: Int { get }
|
||||
var isAnimating: Bool { get }
|
||||
var type: VoiceChatActionButtonBackgroundNodeType { get }
|
||||
func updateAnimations()
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeConnectingState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
var isAnimating: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var frameInterval: Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
var type: VoiceChatActionButtonBackgroundNodeType {
|
||||
return .connecting
|
||||
}
|
||||
|
||||
func updateAnimations() {
|
||||
}
|
||||
|
||||
init(blueGradient: UIImage?) {
|
||||
self.blueGradient = blueGradient
|
||||
}
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeDisabledState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
var isAnimating: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var frameInterval: Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
var type: VoiceChatActionButtonBackgroundNodeType {
|
||||
return .disabled
|
||||
}
|
||||
@ -105,7 +144,7 @@ private final class Blob {
|
||||
|
||||
private var transitionArguments: (startTime: Double, duration: Double)?
|
||||
|
||||
var loop: Bool = false {
|
||||
var loop: Bool = true {
|
||||
didSet {
|
||||
if let _ = transitionArguments {
|
||||
} else {
|
||||
@ -267,10 +306,17 @@ private final class Blob {
|
||||
}
|
||||
|
||||
private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, VoiceChatActionButtonBackgroundNodeState {
|
||||
var blueGradient: UIImage?
|
||||
var greenGradient: UIImage?
|
||||
|
||||
var isAnimating: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var frameInterval: Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
var type: VoiceChatActionButtonBackgroundNodeType {
|
||||
return .blob
|
||||
}
|
||||
@ -281,8 +327,10 @@ private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, Voic
|
||||
var active: Bool
|
||||
var activeTransitionArguments: (startTime: Double, duration: Double)?
|
||||
|
||||
init(size: CGSize, active: Bool) {
|
||||
init(size: CGSize, active: Bool, blueGradient: UIImage, greenGradient: UIImage) {
|
||||
self.active = active
|
||||
self.blueGradient = blueGradient
|
||||
self.greenGradient = greenGradient
|
||||
|
||||
let mediumBlobRange: BlobRange = (0.69, 0.87)
|
||||
let bigBlobRange: BlobRange = (0.71, 1.00)
|
||||
@ -302,6 +350,16 @@ private final class VoiceChatActionButtonBackgroundNodeBlobState: NSObject, Voic
|
||||
}
|
||||
|
||||
func updateAnimations() {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
if let (startTime, duration) = self.activeTransitionArguments, duration > 0.0 {
|
||||
let transition = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
|
||||
if transition < 1.0 {
|
||||
} else {
|
||||
self.activeTransitionArguments = nil
|
||||
}
|
||||
}
|
||||
|
||||
for blob in self.blobs {
|
||||
blob.updateAnimations()
|
||||
}
|
||||
@ -360,7 +418,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
private var animator: ConstantDisplayLinkAnimator?
|
||||
|
||||
override init() {
|
||||
self.state = VoiceChatActionButtonBackgroundNodeConnectingState()
|
||||
self.state = VoiceChatActionButtonBackgroundNodeConnectingState(blueGradient: nil)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -370,16 +428,19 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
func update(state: VoiceChatActionButtonBackgroundNodeState, animated: Bool) {
|
||||
var animated = animated
|
||||
var hadState = true
|
||||
if !self.hasState {
|
||||
hadState = false
|
||||
self.hasState = true
|
||||
animated = false
|
||||
}
|
||||
|
||||
if state.type != self.state.type {
|
||||
if state.type != self.state.type || !hadState {
|
||||
if animated {
|
||||
self.transition = VoiceChatActionButtonBackgroundNodeTransition(startTime: CACurrentMediaTime(), duration: 0.3, previousState: self.state)
|
||||
}
|
||||
self.state = state
|
||||
self.animator?.frameInterval = state.frameInterval
|
||||
} else if let blobState = self.state as? VoiceChatActionButtonBackgroundNodeBlobState, let nextState = state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
blobState.update(with: nextState)
|
||||
}
|
||||
@ -419,6 +480,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
self?.updateAnimations()
|
||||
})
|
||||
animator.frameInterval = 2
|
||||
self.animator = animator
|
||||
}
|
||||
animator.isPaused = false
|
||||
@ -436,6 +498,8 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
let drawStart = CACurrentMediaTime()
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
@ -450,64 +514,58 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
let buttonSize = CGSize(width: 144.0, height: 144.0)
|
||||
let radius = buttonSize.width / 2.0
|
||||
|
||||
let blue = UIColor(rgb: 0x0078ff)
|
||||
let lightBlue = UIColor(rgb: 0x59c7f8)
|
||||
let green = UIColor(rgb: 0x33c659)
|
||||
|
||||
var firstColor = lightBlue
|
||||
var secondColor = blue
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
|
||||
var gradientCenter = CGPoint(x: bounds.size.width - 30.0, y: 50.0)
|
||||
let gradientStartRadius: CGFloat = 0.0
|
||||
let gradientEndRadius: CGFloat = 260.0
|
||||
|
||||
var gradientTransition: CGFloat = 0.0
|
||||
var gradientImage: UIImage? = parameters.state.blueGradient
|
||||
let gradientSize: CGFloat = bounds.width * 2.0
|
||||
|
||||
context.interpolationQuality = .low
|
||||
|
||||
var appearanceProgress: CGFloat = 1.0
|
||||
if let transition = parameters.transition, transition.previousState is VoiceChatActionButtonBackgroundNodeConnectingState {
|
||||
appearanceProgress = transition.progress(time: parameters.timestamp)
|
||||
}
|
||||
|
||||
if let blobsState = parameters.state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
var gradientTransition: CGFloat = blobsState.active ? 1.0 : 0.0
|
||||
gradientTransition = blobsState.active ? 1.0 : 0.0
|
||||
if let transition = blobsState.activeTransitionArguments {
|
||||
gradientTransition = CGFloat((parameters.timestamp - transition.startTime) / transition.duration)
|
||||
if !blobsState.active {
|
||||
gradientTransition = 1.0 - gradientTransition
|
||||
}
|
||||
}
|
||||
|
||||
firstColor = firstColor.interpolateTo(blue, fraction: gradientTransition)!
|
||||
secondColor = secondColor.interpolateTo(green, fraction: gradientTransition)!
|
||||
|
||||
let maskGradientStartRadius: CGFloat = 0.0
|
||||
var maskGradientEndRadius: CGFloat = bounds.size.width / 2.0
|
||||
if let transition = parameters.transition, transition.previousState is VoiceChatActionButtonBackgroundNodeConnectingState {
|
||||
maskGradientEndRadius *= transition.progress(time: parameters.timestamp)
|
||||
gradientImage = gradientTransition.isZero ? blobsState.blueGradient : blobsState.greenGradient
|
||||
if gradientTransition > 0.0 && gradientTransition < 1.0 {
|
||||
gradientImage = generateImage(CGSize(width: 100.0, height: 100.0), contextGenerator: { size, context in
|
||||
context.interpolationQuality = .low
|
||||
if let image = blobsState.blueGradient?.cgImage {
|
||||
context.draw(image, in: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 100.0)))
|
||||
}
|
||||
|
||||
let maskGradientCenter = CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
let colors: [CGColor] = [secondColor.withAlphaComponent(0.5).cgColor, secondColor.withAlphaComponent(0.0).cgColor]
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
context.drawRadialGradient(gradient, startCenter: maskGradientCenter, startRadius: maskGradientStartRadius, endCenter: maskGradientCenter, endRadius: maskGradientEndRadius, options: .drawsAfterEndLocation)
|
||||
|
||||
// context.setBlendMode(.clear)
|
||||
//
|
||||
//
|
||||
// let maskColors: [CGColor] = [UIColor(rgb: 0xffffff, alpha: 0.0).cgColor, UIColor(rgb: 0xffffff, alpha: 1.0).cgColor]
|
||||
// let maskGradient = CGGradient(colorsSpace: colorSpace, colors: maskColors as CFArray, locations: &locations)!
|
||||
//
|
||||
// let maskGradientStartRadius: CGFloat = 0.0
|
||||
// let maskGradientEndRadius: CGFloat = bounds.size.width / 2.0
|
||||
//// context.drawRadialGradient(maskGradient, startCenter: maskGradientCenter, startRadius: maskGradientStartRadius, endCenter: maskGradientCenter, endRadius: maskGradientEndRadius, options: .drawsAfterEndLocation)
|
||||
//
|
||||
// context.setBlendMode(.normal)
|
||||
context.setAlpha(gradientTransition)
|
||||
if let image = blobsState.greenGradient?.cgImage {
|
||||
context.draw(image, in: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 100.0)))
|
||||
}
|
||||
}, opaque: true, scale: deviceScale)!
|
||||
}
|
||||
|
||||
|
||||
let colors: [CGColor] = [firstColor.cgColor, secondColor.cgColor]
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
// center.x -= parameters.gradientMovement * 60.0
|
||||
// center.y += parameters.gradientMovement * 200.0
|
||||
|
||||
|
||||
context.saveGState()
|
||||
var maskBounds = bounds
|
||||
if let transition = parameters.transition, transition.previousState is VoiceChatActionButtonBackgroundNodeConnectingState {
|
||||
let progress = 1.0 - appearanceProgress
|
||||
maskBounds = maskBounds.insetBy(dx: bounds.width / 3.0 * progress, dy: bounds.width / 3.0 * progress)
|
||||
}
|
||||
context.clip(to: maskBounds, mask: radialMaskImage.cgImage!)
|
||||
|
||||
if let gradient = gradientImage?.cgImage {
|
||||
context.draw(gradient, in: CGRect(origin: CGPoint(x: gradientCenter.x - gradientSize / 2.0, y: gradientCenter.y - gradientSize / 2.0), size: CGSize(width: gradientSize, height: gradientSize)))
|
||||
}
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
context.saveGState()
|
||||
|
||||
if let blobsState = parameters.state as? VoiceChatActionButtonBackgroundNodeBlobState {
|
||||
for blob in blobsState.blobs {
|
||||
if let path = blob.currentShape, let uiPath = path.copy() as? UIBezierPath {
|
||||
@ -515,7 +573,7 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
let fromOrigin = CGAffineTransform(translationX: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
|
||||
uiPath.apply(toOrigin)
|
||||
uiPath.apply(CGAffineTransform(scaleX: blob.currentScale, y: blob.currentScale))
|
||||
uiPath.apply(CGAffineTransform(scaleX: blob.currentScale * appearanceProgress, y: blob.currentScale * appearanceProgress))
|
||||
uiPath.apply(fromOrigin)
|
||||
|
||||
context.addPath(uiPath.cgPath)
|
||||
@ -523,7 +581,9 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
|
||||
context.setAlpha(blob.alpha)
|
||||
|
||||
context.drawRadialGradient(gradient, startCenter: gradientCenter, startRadius: gradientStartRadius, endCenter: gradientCenter, endRadius: gradientEndRadius, options: .drawsAfterEndLocation)
|
||||
if let gradient = gradientImage?.cgImage {
|
||||
context.draw(gradient, in: CGRect(origin: CGPoint(x: gradientCenter.x - gradientSize / 2.0, y: gradientCenter.y - gradientSize / 2.0), size: CGSize(width: gradientSize, height: gradientSize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -598,8 +658,8 @@ private class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
drawGradient = true
|
||||
}
|
||||
|
||||
if drawGradient {
|
||||
context.drawRadialGradient(gradient, startCenter: gradientCenter, startRadius: gradientStartRadius, endCenter: gradientCenter, endRadius: gradientEndRadius, options: .drawsAfterEndLocation)
|
||||
if drawGradient, let gradient = gradientImage?.cgImage {
|
||||
context.draw(gradient, in: CGRect(origin: CGPoint(x: gradientCenter.x - gradientSize / 2.0, y: gradientCenter.y - gradientSize / 2.0), size: CGSize(width: gradientSize, height: gradientSize)))
|
||||
}
|
||||
|
||||
if let clearInside = clearInside {
|
||||
@ -616,6 +676,9 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
let titleLabel: ImmediateTextNode
|
||||
let subtitleLabel: ImmediateTextNode
|
||||
|
||||
let blueGradient: UIImage
|
||||
let greenGradient: UIImage
|
||||
|
||||
private var currentParams: (size: CGSize, buttonSize: CGSize, state: VoiceChatActionButtonState, title: String, subtitle: String)?
|
||||
|
||||
var pressing: Bool = false {
|
||||
@ -638,6 +701,37 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
self.titleLabel = ImmediateTextNode()
|
||||
self.subtitleLabel = ImmediateTextNode()
|
||||
|
||||
self.blueGradient = generateImage(CGSize(width: 180.0, height: 180.0), contextGenerator: { size, context in
|
||||
let firstColor = lightBlue
|
||||
let secondColor = blue
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
|
||||
let gradientCenter = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
let gradientStartRadius: CGFloat = 0.0
|
||||
let gradientEndRadius: CGFloat = 85.0
|
||||
|
||||
let colors: [CGColor] = [firstColor.cgColor, secondColor.cgColor]
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
context.drawRadialGradient(gradient, startCenter: gradientCenter, startRadius: gradientStartRadius, endCenter: gradientCenter, endRadius: gradientEndRadius, options: .drawsAfterEndLocation)
|
||||
}, opaque: true, scale: min(2.0, deviceScale))!
|
||||
|
||||
self.greenGradient = generateImage(CGSize(width: 180.0, height: 180.0), contextGenerator: { size, context in
|
||||
let firstColor = blue
|
||||
let secondColor = green
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
|
||||
let gradientCenter = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
let gradientStartRadius: CGFloat = 0.0
|
||||
let gradientEndRadius: CGFloat = 85.0
|
||||
|
||||
let colors: [CGColor] = [firstColor.cgColor, secondColor.cgColor]
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
context.drawRadialGradient(gradient, startCenter: gradientCenter, startRadius: gradientStartRadius, endCenter: gradientCenter, endRadius: gradientEndRadius, options: .drawsAfterEndLocation)
|
||||
}, opaque: true, scale: min(2.0, deviceScale))!
|
||||
|
||||
super.init()
|
||||
|
||||
@ -677,6 +771,8 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
self.titleLabel.attributedText = NSAttributedString(string: title, font: titleFont, textColor: .white)
|
||||
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, font: subtitleFont, textColor: .white)
|
||||
|
||||
let blobSize: CGSize = CGSize(width: 244.0, height: 244.0)
|
||||
|
||||
var iconMuted = true
|
||||
var iconColor: UIColor = .white
|
||||
var backgroundState: VoiceChatActionButtonBackgroundNodeState
|
||||
@ -685,17 +781,15 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
switch state {
|
||||
case .on:
|
||||
iconMuted = false
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobState(size: size, active: true)
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobState(size: blobSize, active: true, blueGradient: self.blueGradient, greenGradient: self.greenGradient)
|
||||
case .muted:
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobState(size: size, active: false)
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeBlobState(size: blobSize, active: false, blueGradient: self.blueGradient, greenGradient: self.greenGradient)
|
||||
case .cantSpeak:
|
||||
iconColor = UIColor(rgb: 0xff3b30)
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeDisabledState()
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .connecting:
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeConnectingState()
|
||||
backgroundState = VoiceChatActionButtonBackgroundNodeConnectingState(blueGradient: self.blueGradient)
|
||||
}
|
||||
self.backgroundNode.update(state: backgroundState, animated: true)
|
||||
|
||||
@ -722,7 +816,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
||||
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height + 16.0 - totalHeight / 2.0) - 20.0), size: titleSize)
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height + 16.0 - totalHeight / 2.0) - 56.0), size: titleSize)
|
||||
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
@ -55,6 +55,12 @@ private final class VoiceChatControllerTitleView: UIView {
|
||||
self.infoNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: UIColor.white.withAlphaComponent(0.5))
|
||||
}
|
||||
|
||||
func animateIn(duration: Double) {
|
||||
self.titleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 49.0), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.infoNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 49.0), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.titleNode.layer.animateScale(from: 0.882, to: 1.0, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
@ -377,6 +383,7 @@ public final class VoiceChatController: ViewController {
|
||||
})))
|
||||
}
|
||||
default:
|
||||
if peer.id != strongSelf.context.account.peerId {
|
||||
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
||||
if let muteState = entry.muteState, !muteState.canUnmute {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_UnmutePeer, icon: { theme in
|
||||
@ -403,7 +410,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
if peer.id != strongSelf.context.account.peerId {
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
@ -420,6 +427,8 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
|
||||
}))
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
@ -812,7 +821,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
|
||||
let centralButtonSize = CGSize(width: 244.0, height: 244.0)
|
||||
let centralButtonSize = CGSize(width: 300.0, height: 300.0)
|
||||
let sideButtonInset: CGFloat = 27.0
|
||||
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - centralButtonSize.width) / 2.0), y: layout.size.height - bottomAreaHeight - layout.intrinsicInsets.bottom + floor((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
||||
@ -898,7 +907,7 @@ public final class VoiceChatController: ViewController {
|
||||
soundImage = .speaker
|
||||
case .speaker:
|
||||
soundImage = .speaker
|
||||
soundAppearance = .blurred(isFilled: true)
|
||||
// soundAppearance = .blurred(isFilled: true)
|
||||
case .headphones:
|
||||
soundImage = .bluetooth
|
||||
case let .bluetooth(type):
|
||||
@ -926,30 +935,116 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
func animateIn(sourcePanel: ASDisplayNode?) {
|
||||
self.alpha = 1.0
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
|
||||
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
guard let (layout, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
if let sourcePanel = sourcePanel as? GroupCallNavigationAccessoryPanel {
|
||||
let sourceFrame = sourcePanel.view.convert(sourcePanel.bounds, to: self.view)
|
||||
self.contentContainer.clipsToBounds = true
|
||||
|
||||
let duration: Double = 0.4
|
||||
if let titleView = self.controller?.navigationItem.titleView as? VoiceChatControllerTitleView {
|
||||
titleView.animateIn(duration: duration)
|
||||
|
||||
self.controller?.navigationBar?.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||
|
||||
if let panelTitleView = sourcePanel.titleNode.view.snapshotContentTree() {
|
||||
let frame = sourcePanel.titleNode.view.convert(sourcePanel.titleNode.bounds, to: self.view)
|
||||
panelTitleView.frame = frame
|
||||
self.view.addSubview(panelTitleView)
|
||||
|
||||
panelTitleView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -49.0), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
panelTitleView.layer.animateScale(from: 1.0, to: 1.13, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
panelTitleView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak panelTitleView] _ in
|
||||
panelTitleView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
if let panelTextView = sourcePanel.textNode.view.snapshotContentTree() {
|
||||
let frame = sourcePanel.textNode.view.convert(sourcePanel.textNode.bounds, to: self.view)
|
||||
panelTextView.frame = frame
|
||||
self.view.addSubview(panelTextView)
|
||||
|
||||
panelTextView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -49.0), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
panelTextView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak panelTextView] _ in
|
||||
panelTextView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let (backgroundView, foregroundView) = sourcePanel.rightButtonSnapshotViews() {
|
||||
self.view.addSubview(backgroundView)
|
||||
self.view.addSubview(foregroundView)
|
||||
|
||||
self.optionsButton.isHidden = true
|
||||
let optionsFrame = self.optionsButton.view.convert(self.optionsButton.bounds, to: self.view)
|
||||
|
||||
let dotsView = UIImageView(image: optionsButtonImage())
|
||||
dotsView.center = foregroundView.center
|
||||
self.view.addSubview(dotsView)
|
||||
|
||||
backgroundView.layer.animateBounds(from: backgroundView.bounds, to: CGRect(origin: CGPoint(), size: CGSize(width: backgroundView.bounds.height, height: backgroundView.bounds.height)), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
backgroundView.layer.animatePosition(from: backgroundView.center, to: CGPoint(x: optionsFrame.midX, y: optionsFrame.midY), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
backgroundView.layer.animateScale(from: 1.0, to: optionsFrame.height / backgroundView.frame.height, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
foregroundView.layer.animatePosition(from: foregroundView.center, to: CGPoint(x: optionsFrame.midX, y: optionsFrame.midY), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
|
||||
backgroundView.layer.animate(from: backgroundView.backgroundColor!.cgColor, to: UIColor(rgb: 0x1c1c1e).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration - 0.1, removeOnCompletion: false)
|
||||
foregroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration - 0.1, removeOnCompletion: false, completion: { [weak self, weak foregroundView, weak backgroundView, weak dotsView] _ in
|
||||
backgroundView?.removeFromSuperview()
|
||||
foregroundView?.removeFromSuperview()
|
||||
dotsView?.removeFromSuperview()
|
||||
|
||||
self?.optionsButton.isHidden = false
|
||||
})
|
||||
|
||||
foregroundView.layer.animateScale(from: 1.0, to: 0.3, duration: duration - 0.1, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
dotsView.layer.animatePosition(from: dotsView.center, to: CGPoint(x: optionsFrame.midX, y: optionsFrame.midY), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
dotsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration - 0.1, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
self.contentContainer.layer.animateFrame(from: sourceFrame, to: self.contentContainer.frame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.contentContainer.layer.animate(from: 0.0 as NSNumber, to: layout.deviceMetrics.screenCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, duration: duration, removeOnCompletion: true, completion: { [weak self] value in
|
||||
if value {
|
||||
self?.contentContainer.clipsToBounds = false
|
||||
}
|
||||
})
|
||||
|
||||
self.contentContainer.layer.animate(from: self.presentationData.theme.rootController.navigationBar.backgroundColor.cgColor, to: UIColor.black.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration - 0.1)
|
||||
|
||||
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||
self.actionButton.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.audioOutputNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.leaveNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.actionButton.titleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
self.actionButton.subtitleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
self.audioOutputNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
self.leaveNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
} else {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.actionButton.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
self.audioOutputNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.leaveNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
self.actionButton.titleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
self.actionButton.subtitleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
self.audioOutputNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
self.leaveNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
|
||||
self.contentContainer.layer.animateBoundsOriginYAdditive(from: 80.0, to: 0.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)?) {
|
||||
self.alpha = 0.0
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { _ in
|
||||
self.layer.allowsGroupOpacity = true
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] _ in
|
||||
completion?()
|
||||
self?.layer.allowsGroupOpacity = false
|
||||
})
|
||||
self.contentContainer.layer.animateScale(from: 1.0, to: 1.04, duration: 0.3)
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: ListTransition) {
|
||||
@ -1108,6 +1203,7 @@ public final class VoiceChatController: ViewController {
|
||||
initialBounds.origin = CGPoint()
|
||||
self?.bounds = initialBounds
|
||||
self?.controller?.statusBar.statusBarStyle = .White
|
||||
self?.contentContainer.cornerRadius = 0.0
|
||||
})
|
||||
}
|
||||
default:
|
||||
@ -1120,6 +1216,8 @@ public final class VoiceChatController: ViewController {
|
||||
public let call: PresentationGroupCall
|
||||
private let presentationData: PresentationData
|
||||
|
||||
public weak var sourcePanel: ASDisplayNode?
|
||||
|
||||
fileprivate let contentsReady = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
fileprivate let dataReady = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let _ready = Promise<Bool>(false)
|
||||
@ -1187,7 +1285,8 @@ public final class VoiceChatController: ViewController {
|
||||
if !self.didAppearOnce {
|
||||
self.didAppearOnce = true
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
self.controllerNode.animateIn(sourcePanel: self.sourcePanel)
|
||||
self.sourcePanel = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,18 +151,24 @@ final class VoiceChatMicrophoneNode: ASDisplayNode {
|
||||
|
||||
context.setFillColor(parameters.color.cgColor)
|
||||
|
||||
var lineWidth: CGFloat = 1.0 + UIScreenPixel
|
||||
if bounds.size.width > 36.0 {
|
||||
context.scaleBy(x: 2.5, y: 2.5)
|
||||
} else if bounds.size.width < 30.0 {
|
||||
context.scaleBy(x: 0.7, y: 0.7)
|
||||
lineWidth = 2.0
|
||||
}
|
||||
context.translateBy(x: 18.0, y: 18.0)
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M-0.004000000189989805,-9.86400032043457 C2.2960000038146973,-9.86400032043457 4.165999889373779,-8.053999900817871 4.25600004196167,-5.77400016784668 C4.25600004196167,-5.77400016784668 4.265999794006348,-5.604000091552734 4.265999794006348,-5.604000091552734 C4.265999794006348,-5.604000091552734 4.265999794006348,-0.8040000200271606 4.265999794006348,-0.8040000200271606 C4.265999794006348,1.555999994277954 2.3559999465942383,3.4660000801086426 -0.004000000189989805,3.4660000801086426 C-2.2939999103546143,3.4660000801086426 -4.164000034332275,1.6460000276565552 -4.263999938964844,-0.6240000128746033 C-4.263999938964844,-0.6240000128746033 -4.263999938964844,-0.8040000200271606 -4.263999938964844,-0.8040000200271606 C-4.263999938964844,-0.8040000200271606 -4.263999938964844,-5.604000091552734 -4.263999938964844,-5.604000091552734 C-4.263999938964844,-7.953999996185303 -2.3540000915527344,-9.86400032043457 -0.004000000189989805,-9.86400032043457 Z ")
|
||||
|
||||
if bounds.size.width > 30.0 {
|
||||
context.setBlendMode(.clear)
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M0.004000000189989805,-8.53600025177002 C-1.565999984741211,-8.53600025177002 -2.8459999561309814,-7.306000232696533 -2.936000108718872,-5.75600004196167 C-2.936000108718872,-5.75600004196167 -2.936000108718872,-5.5960001945495605 -2.936000108718872,-5.5960001945495605 C-2.936000108718872,-5.5960001945495605 -2.936000108718872,-0.7960000038146973 -2.936000108718872,-0.7960000038146973 C-2.936000108718872,0.8240000009536743 -1.6260000467300415,2.134000062942505 0.004000000189989805,2.134000062942505 C1.5740000009536743,2.134000062942505 2.8540000915527344,0.9039999842643738 2.934000015258789,-0.6460000276565552 C2.934000015258789,-0.6460000276565552 2.934000015258789,-0.7960000038146973 2.934000015258789,-0.7960000038146973 C2.934000015258789,-0.7960000038146973 2.934000015258789,-5.5960001945495605 2.934000015258789,-5.5960001945495605 C2.934000015258789,-7.22599983215332 1.6239999532699585,-8.53600025177002 0.004000000189989805,-8.53600025177002 Z ")
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
}
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M6.796000003814697,-1.4639999866485596 C7.165999889373779,-1.4639999866485596 7.466000080108643,-1.1640000343322754 7.466000080108643,-0.8040000200271606 C7.466000080108643,3.0959999561309814 4.47599983215332,6.296000003814697 0.6660000085830688,6.636000156402588 C0.6660000085830688,6.636000156402588 0.6660000085830688,9.196000099182129 0.6660000085830688,9.196000099182129 C0.6660000085830688,9.565999984741211 0.3659999966621399,9.866000175476074 -0.004000000189989805,9.866000175476074 C-0.33399999141693115,9.866000175476074 -0.6140000224113464,9.605999946594238 -0.6539999842643738,9.28600025177002 C-0.6539999842643738,9.28600025177002 -0.6639999747276306,9.196000099182129 -0.6639999747276306,9.196000099182129 C-0.6639999747276306,9.196000099182129 -0.6639999747276306,6.636000156402588 -0.6639999747276306,6.636000156402588 C-4.473999977111816,6.296000003814697 -7.464000225067139,3.0959999561309814 -7.464000225067139,-0.8040000200271606 C-7.464000225067139,-1.1640000343322754 -7.164000034332275,-1.4639999866485596 -6.803999900817871,-1.4639999866485596 C-6.434000015258789,-1.4639999866485596 -6.133999824523926,-1.1640000343322754 -6.133999824523926,-0.8040000200271606 C-6.133999824523926,2.5859999656677246 -3.384000062942505,5.335999965667725 -0.004000000189989805,5.335999965667725 C3.385999917984009,5.335999965667725 6.136000156402588,2.5859999656677246 6.136000156402588,-0.8040000200271606 C6.136000156402588,-1.1640000343322754 6.435999870300293,-1.4639999866485596 6.796000003814697,-1.4639999866485596 Z ")
|
||||
|
||||
@ -188,7 +194,7 @@ final class VoiceChatMicrophoneNode: ASDisplayNode {
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
context.setStrokeColor(parameters.color.cgColor)
|
||||
context.setLineWidth(1.0 + UIScreenPixel)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
|
||||
|
@ -3,6 +3,17 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
func optionsButtonImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fillEllipse(in: CGRect(x: 6.0, y: 12.0, width: 4.0, height: 4.0))
|
||||
context.fillEllipse(in: CGRect(x: 12.0, y: 12.0, width: 4.0, height: 4.0))
|
||||
context.fillEllipse(in: CGRect(x: 18.0, y: 12.0, width: 4.0, height: 4.0))
|
||||
})
|
||||
}
|
||||
|
||||
final class VoiceChatOptionsButton: HighlightableButtonNode {
|
||||
let extractedContainerNode: ContextExtractedContentContainingNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
|
@ -271,6 +271,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect)
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0))
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.actionContainerNode.layer, offset: CGPoint(x: isExtracted ? -24.0 : 0.0, y: 0.0))
|
||||
|
@ -1375,7 +1375,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let callResult = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peer.id {
|
||||
context.sharedContext.navigateToCurrentCall()
|
||||
context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
|
||||
} else {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
@ -5986,10 +5986,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
let callResult = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: false)
|
||||
let callResult = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: false, sourcePanel: nil)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peer.id {
|
||||
strongSelf.context.sharedContext.navigateToCurrentCall()
|
||||
strongSelf.context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
|
||||
} else {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
@ -5999,7 +5999,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self, let current = current {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: true)
|
||||
let _ = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id, initialCall: activeCall, endCurrentIfAny: true, sourcePanel: nil)
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
} else {
|
||||
|
@ -3169,7 +3169,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
guard let cachedChannelData = self.data?.cachedData as? CachedChannelData else {
|
||||
return
|
||||
}
|
||||
let _ = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id, initialCall: cachedChannelData.activeCall, endCurrentIfAny: false)
|
||||
let _ = self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id, initialCall: cachedChannelData.activeCall, endCurrentIfAny: false, sourcePanel: nil)
|
||||
|
||||
return
|
||||
}
|
||||
@ -3185,7 +3185,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
let callResult = self.context.sharedContext.callManager?.requestCall(context: self.context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: false)
|
||||
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
|
||||
if currentPeerId == peer.id {
|
||||
self.context.sharedContext.navigateToCurrentCall()
|
||||
self.context.sharedContext.navigateToCurrentCall(sourcePanel: nil)
|
||||
} else {
|
||||
let presentationData = self.presentationData
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in
|
||||
|
@ -28,11 +28,6 @@ import AlertUI
|
||||
import PresentationDataUtils
|
||||
import LocationUI
|
||||
|
||||
private enum CallStatusText: Equatable {
|
||||
case none
|
||||
case inProgress(Double?)
|
||||
}
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
let tokens = Bag<Void>()
|
||||
@ -98,8 +93,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|
||||
private var callDisposable: Disposable?
|
||||
private var callStateDisposable: Disposable?
|
||||
private var currentCallStatusText: CallStatusText = .none
|
||||
private var currentCallStatusTextTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var currentCallStatusBarNode: CallStatusBarNodeImpl?
|
||||
|
||||
private var groupCallDisposable: Disposable?
|
||||
|
||||
@ -645,6 +640,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
if let call = call {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
||||
groupCallController.sourcePanel = call.sourcePanel
|
||||
call.sourcePanel = nil
|
||||
strongSelf.groupCallController = groupCallController
|
||||
strongSelf.mainWindow?.present(groupCallController, on: .calls)
|
||||
strongSelf.hasOngoingCall.set(true)
|
||||
@ -655,54 +652,56 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
})
|
||||
|
||||
self.callStateDisposable = combineLatest(queue: .mainQueue(),
|
||||
self.callState.get(),
|
||||
let callAndStateSignal: Signal<(PresentationCall, PresentationCallState)?, NoError> = .single(nil)
|
||||
|> then(
|
||||
callManager.currentCallSignal
|
||||
|> mapToSignal { call in
|
||||
if let call = call {
|
||||
return call.state
|
||||
|> map { state in
|
||||
return (call, state)
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
)
|
||||
let groupCallAndStateSignal: Signal<PresentationGroupCall?, NoError> = .single(nil)
|
||||
|> then(
|
||||
callManager.currentGroupCallSignal
|
||||
|> map { call -> Bool in
|
||||
return call != nil
|
||||
}
|
||||
).start(next: { [weak self] state, hasGroupCall in
|
||||
)
|
||||
|
||||
self.callStateDisposable = combineLatest(queue: .mainQueue(),
|
||||
callAndStateSignal,
|
||||
groupCallAndStateSignal
|
||||
).start(next: { [weak self] callAndState, groupCall in
|
||||
if let strongSelf = self {
|
||||
let resolvedText: CallStatusText
|
||||
if let state = state {
|
||||
switch state.state {
|
||||
case .connecting, .requesting, .terminating, .ringing, .waiting:
|
||||
resolvedText = .inProgress(nil)
|
||||
case .terminated:
|
||||
resolvedText = .none
|
||||
case .active(let timestamp, _, _), .reconnecting(let timestamp, _, _):
|
||||
resolvedText = .inProgress(timestamp)
|
||||
}
|
||||
} else if hasGroupCall {
|
||||
resolvedText = .inProgress(nil)
|
||||
let statusBarContent: CallStatusBarNodeImpl.Content?
|
||||
|
||||
if let (call, state) = callAndState {
|
||||
statusBarContent = .call(call)
|
||||
} else if let groupCall = groupCall {
|
||||
statusBarContent = .groupCall(groupCall)
|
||||
} else {
|
||||
resolvedText = .none
|
||||
statusBarContent = nil
|
||||
}
|
||||
|
||||
if strongSelf.currentCallStatusText != resolvedText {
|
||||
strongSelf.currentCallStatusText = resolvedText
|
||||
var resolvedCallStatusBarNode: CallStatusBarNodeImpl?
|
||||
|
||||
var referenceTimestamp: Double?
|
||||
if case let .inProgress(timestamp) = resolvedText, let concreteTimestamp = timestamp {
|
||||
referenceTimestamp = concreteTimestamp
|
||||
}
|
||||
|
||||
if let _ = referenceTimestamp {
|
||||
if strongSelf.currentCallStatusTextTimer == nil {
|
||||
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateStatusBarText()
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
strongSelf.currentCallStatusTextTimer = timer
|
||||
timer.start()
|
||||
}
|
||||
if let statusBarContent = statusBarContent {
|
||||
if let current = strongSelf.currentCallStatusBarNode {
|
||||
resolvedCallStatusBarNode = current
|
||||
} else {
|
||||
strongSelf.currentCallStatusTextTimer?.invalidate()
|
||||
strongSelf.currentCallStatusTextTimer = nil
|
||||
resolvedCallStatusBarNode = CallStatusBarNodeImpl()
|
||||
strongSelf.currentCallStatusBarNode = resolvedCallStatusBarNode
|
||||
}
|
||||
resolvedCallStatusBarNode?.update(content: statusBarContent)
|
||||
} else {
|
||||
strongSelf.currentCallStatusBarNode = nil
|
||||
}
|
||||
|
||||
strongSelf.updateStatusBarText()
|
||||
if let navigationController = strongSelf.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.setForceInCallStatusBar(resolvedCallStatusBarNode)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -780,7 +779,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
self.callDisposable?.dispose()
|
||||
self.groupCallDisposable?.dispose()
|
||||
self.callStateDisposable?.dispose()
|
||||
self.currentCallStatusTextTimer?.invalidate()
|
||||
}
|
||||
|
||||
private func updateAccountBackupData(account: Account) -> Signal<Never, NoError> {
|
||||
@ -988,34 +986,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return openChatMessageImpl(params)
|
||||
}
|
||||
|
||||
private func updateStatusBarText() {
|
||||
if case let .inProgress(timestamp) = self.currentCallStatusText {
|
||||
let text: String
|
||||
let presentationData = self.currentPresentationData.with { $0 }
|
||||
if let timestamp = timestamp {
|
||||
let duration = Int32(CFAbsoluteTimeGetCurrent() - timestamp)
|
||||
let durationString: String
|
||||
if duration > 60 * 60 {
|
||||
durationString = String(format: "%02d:%02d:%02d", arguments: [duration / 3600, (duration / 60) % 60, duration % 60])
|
||||
} else {
|
||||
durationString = String(format: "%02d:%02d", arguments: [(duration / 60) % 60, duration % 60])
|
||||
}
|
||||
|
||||
text = presentationData.strings.Call_StatusBar(durationString).0
|
||||
} else {
|
||||
text = presentationData.strings.Call_StatusBar("").0
|
||||
}
|
||||
if let navigationController = self.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.setForceInCallStatusBar(text)
|
||||
}
|
||||
} else {
|
||||
if let navigationController = self.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.setForceInCallStatusBar(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func navigateToCurrentCall() {
|
||||
public func navigateToCurrentCall(sourcePanel: ASDisplayNode? = nil) {
|
||||
guard let mainWindow = self.mainWindow else {
|
||||
return
|
||||
}
|
||||
@ -1026,6 +997,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
} else if let groupCallController = self.groupCallController {
|
||||
if groupCallController.isNodeLoaded && groupCallController.view.superview == nil {
|
||||
groupCallController.sourcePanel = sourcePanel
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
mainWindow.present(groupCallController, on: .calls)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user