Voice Chat UI improvements

This commit is contained in:
Ilya Laktyushin 2020-12-04 10:09:19 +04:00
parent dc4eed539e
commit 0897f59b5b
17 changed files with 479 additions and 260 deletions

View File

@ -153,7 +153,6 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
} }
final class BlobView: UIView { final class BlobView: UIView {
let pointsCount: Int let pointsCount: Int
let smoothness: CGFloat let smoothness: CGFloat
@ -169,7 +168,6 @@ final class BlobView: UIView {
var scaleLevelsToBalance = [CGFloat]() var scaleLevelsToBalance = [CGFloat]()
// If true ignores randomness and pointsCount
let isCircle: Bool let isCircle: Bool
var level: CGFloat = 0 { var level: CGFloat = 0 {

View File

@ -2618,6 +2618,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource { private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = true let keepInPlace: Bool = true
let ignoreContentTouches: Bool = true let ignoreContentTouches: Bool = true
let blurBackground: Bool = true
private let controller: ChatListController private let controller: ChatListController
private let sourceNode: ContextExtractedContentContainingNode private let sourceNode: ContextExtractedContentContainingNode
@ -2639,6 +2640,7 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte
private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource { private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool let keepInPlace: Bool
let ignoreContentTouches: Bool = true let ignoreContentTouches: Bool = true
let blurBackground: Bool = true
private let controller: ChatListController private let controller: ChatListController
private let sourceNode: ContextExtractedContentContainingNode private let sourceNode: ContextExtractedContentContainingNode

View File

@ -956,6 +956,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private final class MessageContextExtractedContentSource: ContextExtractedContentSource { private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false let keepInPlace: Bool = false
let ignoreContentTouches: Bool = true let ignoreContentTouches: Bool = true
let blurBackground: Bool = true
private let sourceNode: ContextExtractedContentContainingNode private let sourceNode: ContextExtractedContentContainingNode

View File

@ -211,9 +211,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.scrollNode.view.delegate = self self.scrollNode.view.delegate = self
self.view.addSubview(self.effectView) if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
self.addSubnode(self.dimNode) } else {
self.addSubnode(self.withoutBlurDimNode) self.view.addSubview(self.effectView)
self.addSubnode(self.dimNode)
self.addSubnode(self.withoutBlurDimNode)
}
self.addSubnode(self.clippingNode) self.addSubnode(self.clippingNode)
@ -1074,7 +1077,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
switch layout.metrics.widthClass { switch layout.metrics.widthClass {
case .compact: case .compact:
if self.effectView.superview == nil { if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
} else if self.effectView.superview == nil {
self.view.insertSubview(self.effectView, at: 0) self.view.insertSubview(self.effectView, at: 0)
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
if let propertyAnimator = self.propertyAnimator { if let propertyAnimator = self.propertyAnimator {
@ -1088,7 +1092,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.dimNode.isHidden = false self.dimNode.isHidden = false
self.withoutBlurDimNode.isHidden = true self.withoutBlurDimNode.isHidden = true
case .regular: case .regular:
if self.effectView.superview != nil { if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
} else if self.effectView.superview != nil {
self.effectView.removeFromSuperview() self.effectView.removeFromSuperview()
self.withoutBlurDimNode.alpha = 1.0 self.withoutBlurDimNode.alpha = 1.0
} }
@ -1471,6 +1476,7 @@ public final class ContextControllerPutBackViewInfo {
public protocol ContextExtractedContentSource: class { public protocol ContextExtractedContentSource: class {
var keepInPlace: Bool { get } var keepInPlace: Bool { get }
var ignoreContentTouches: Bool { get } var ignoreContentTouches: Bool { get }
var blurBackground: Bool { get }
func takeView() -> ContextControllerTakeViewInfo? func takeView() -> ContextControllerTakeViewInfo?
func putBack() -> ContextControllerPutBackViewInfo? func putBack() -> ContextControllerPutBackViewInfo?
@ -1528,7 +1534,11 @@ public final class ContextController: ViewController, StandalonePresentableContr
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Hide if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
self.statusBar.statusBarStyle = .Ignore
} else {
self.statusBar.statusBarStyle = .Hide
}
self.lockOrientation = true self.lockOrientation = true
} }

View File

@ -1009,8 +1009,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound { if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset, topItemFound {
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top) if !self.stackFromBottom {
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight) completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset - effectiveInsets.bottom - effectiveInsets.top)
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
} else {
effectiveInsets.top = self.visibleSize.height - completeHeight
completeHeight = max(completeHeight, self.visibleSize.height)
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
}
} }
var transition: ContainedViewLayoutTransition = .immediate var transition: ContainedViewLayoutTransition = .immediate
@ -1094,6 +1100,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
} }
print("off \(offset)")
if abs(offset) > CGFloat.ulpOfOne { if abs(offset) > CGFloat.ulpOfOne {
self.didScrollWithOffset?(-offset, .immediate, nil) self.didScrollWithOffset?(-offset, .immediate, nil)
@ -1413,8 +1420,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset { if let keepMinimalScrollHeightWithTopInset = self.keepMinimalScrollHeightWithTopInset {
completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset) if !self.stackFromBottom {
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight) completeHeight = max(completeHeight, self.visibleSize.height + keepMinimalScrollHeightWithTopInset)
bottomItemEdge = max(bottomItemEdge, topItemEdge + completeHeight)
}
} }
if self.stackFromBottom { if self.stackFromBottom {
@ -1435,11 +1444,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
self.ignoreScrollingEvents = true self.ignoreScrollingEvents = true
if topItemFound && bottomItemFound { if topItemFound && bottomItemFound {
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight) self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight)
if self.stackFromBottom { self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
} else {
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
}
self.scroller.contentOffset = self.lastContentOffset self.scroller.contentOffset = self.lastContentOffset
} else if topItemFound { } else if topItemFound {
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)

View File

@ -8,7 +8,7 @@ private final class VoiceChatIndicatorNode: ASDisplayNode {
private let centerLine: ASDisplayNode private let centerLine: ASDisplayNode
private let rightLine: ASDisplayNode private let rightLine: ASDisplayNode
private var isCurrentlyInHierarchy = false private var isCurrentlyInHierarchy = true
private var shouldBeAnimating = false private var shouldBeAnimating = false
var color: UIColor = UIColor(rgb: 0xffffff) { var color: UIColor = UIColor(rgb: 0xffffff) {
@ -45,6 +45,8 @@ private final class VoiceChatIndicatorNode: ASDisplayNode {
self.addSubnode(self.leftLine) self.addSubnode(self.leftLine)
self.addSubnode(self.centerLine) self.addSubnode(self.centerLine)
self.addSubnode(self.rightLine) self.addSubnode(self.rightLine)
self.updateAnimation()
} }
override func didEnterHierarchy() { override func didEnterHierarchy() {

View File

@ -265,36 +265,15 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
case .none, .all: case .none, .all:
break break
case let .peer(peerId): case let .peer(peerId):
let currentGroupCall: Signal<GroupCallPanelData?, NoError> = callManager.currentGroupCallSignal let currentGroupCall: Signal<PresentationGroupCall?, NoError> = callManager.currentGroupCallSignal
|> distinctUntilChanged(isEqual: { lhs, rhs in |> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs?.internalId == rhs?.internalId return lhs?.internalId == rhs?.internalId
}) })
|> mapToSignal { call -> Signal<GroupCallPanelData?, NoError> in |> map { call -> PresentationGroupCall? in
guard let call = call, call.peerId == peerId else { guard let call = call, call.peerId == peerId else {
return .single(nil) return nil
} }
return call.summaryState return call
|> filter { $0 != nil }
|> map { summary -> GroupCallPanelData? in
guard let summary = summary else {
return nil
}
return GroupCallPanelData(
peerId: call.peerId,
info: summary.info,
topParticipants: summary.topParticipants,
participantCount: summary.participantCount,
numberOfActiveSpeakers: summary.numberOfActiveSpeakers,
groupCall: call
)
}
|> take(until: { summary in
if summary != nil {
return SignalTakeAction(passthrough: true, complete: true)
} else {
return SignalTakeAction(passthrough: true, complete: false)
}
})
} }
let availableGroupCall: Signal<GroupCallPanelData?, NoError> let availableGroupCall: Signal<GroupCallPanelData?, NoError>
@ -373,12 +352,17 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(), self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(),
currentGroupCall, currentGroupCall,
availableGroupCall availableGroupCall
).start(next: { [weak self] currentState, availableState in ).start(next: { [weak self] currentGroupCall, availableState in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let panelData = currentState != nil ? nil : availableState let panelData: GroupCallPanelData?
if let _ = currentGroupCall {
panelData = nil
} else {
panelData = availableState
}
let wasEmpty = strongSelf.groupCallPanelData == nil let wasEmpty = strongSelf.groupCallPanelData == nil
strongSelf.groupCallPanelData = panelData strongSelf.groupCallPanelData = panelData

View File

@ -36,6 +36,7 @@ swift_library(
"//submodules/AudioBlob:AudioBlob", "//submodules/AudioBlob:AudioBlob",
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
"//submodules/AlertUI:AlertUI", "//submodules/AlertUI:AlertUI",
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -219,15 +219,12 @@ final class CallControllerButtonsNode: ASDisplayNode {
if videoState.isAvailable { if videoState.isAvailable {
let isCameraActive: Bool let isCameraActive: Bool
let isCameraEnabled: Bool
let isCameraInitializing: Bool let isCameraInitializing: Bool
if videoState.hasVideo { if videoState.hasVideo {
isCameraActive = videoState.isCameraActive isCameraActive = videoState.isCameraActive
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera isCameraInitializing = videoState.isInitializingCamera
} else { } else {
isCameraActive = false isCameraActive = false
isCameraEnabled = videoState.canChangeStatus
isCameraInitializing = videoState.isInitializingCamera isCameraInitializing = videoState.isInitializingCamera
} }
topButtons.append(.enableCamera(isCameraActive, false, isCameraInitializing)) topButtons.append(.enableCamera(isCameraActive, false, isCameraInitializing))

View File

@ -42,7 +42,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
} }
private let hierarchyTrackingNode: HierarchyTrackingNode private let hierarchyTrackingNode: HierarchyTrackingNode
private var isCurrentlyInHierarchy = false private var isCurrentlyInHierarchy = true
override init() { override init() {
self.foregroundView = UIView() self.foregroundView = UIView()
@ -89,9 +89,11 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
CATransaction.begin() CATransaction.begin()
CATransaction.setDisableActions(true) CATransaction.setDisableActions(true)
self.foregroundView.frame = self.bounds if self.maskCurveView.frame != self.bounds {
self.foregroundGradientLayer.frame = self.bounds self.foregroundView.frame = self.bounds
self.maskCurveView.frame = self.bounds self.foregroundGradientLayer.frame = self.bounds
self.maskCurveView.frame = self.bounds
}
CATransaction.commit() CATransaction.commit()
} }
@ -324,7 +326,7 @@ private final class VoiceCurveView: UIView {
minRandomness: 1, minRandomness: 1,
maxRandomness: 1.3, maxRandomness: 1.3,
minSpeed: 0.9, minSpeed: 0.9,
maxSpeed: 3.5, maxSpeed: 3.2,
minOffset: smallCurveRange.min, minOffset: smallCurveRange.min,
maxOffset: smallCurveRange.max maxOffset: smallCurveRange.max
) )
@ -333,7 +335,7 @@ private final class VoiceCurveView: UIView {
minRandomness: 1.2, minRandomness: 1.2,
maxRandomness: 1.5, maxRandomness: 1.5,
minSpeed: 1.0, minSpeed: 1.0,
maxSpeed: 4.5, maxSpeed: 4.4,
minOffset: mediumCurveRange.min, minOffset: mediumCurveRange.min,
maxOffset: mediumCurveRange.max maxOffset: mediumCurveRange.max
) )
@ -342,7 +344,7 @@ private final class VoiceCurveView: UIView {
minRandomness: 1.2, minRandomness: 1.2,
maxRandomness: 1.7, maxRandomness: 1.7,
minSpeed: 1.0, minSpeed: 1.0,
maxSpeed: 6.0, maxSpeed: 5.8,
minOffset: bigCurveRange.min, minOffset: bigCurveRange.min,
maxOffset: bigCurveRange.max maxOffset: bigCurveRange.max
) )
@ -476,7 +478,7 @@ final class CurveView: UIView {
override var frame: CGRect { override var frame: CGRect {
didSet { didSet {
if self.frame != oldValue { if self.frame.size != oldValue.size {
self.fromPoints = nil self.fromPoints = nil
self.toPoints = nil self.toPoints = nil
self.animateToNewShape() self.animateToNewShape()
@ -534,7 +536,7 @@ final class CurveView: UIView {
func updateSpeedLevel(to newSpeedLevel: CGFloat) { func updateSpeedLevel(to newSpeedLevel: CGFloat) {
speedLevel = max(speedLevel, newSpeedLevel) speedLevel = max(speedLevel, newSpeedLevel)
if abs(lastSpeedLevel - newSpeedLevel) > 0.3 { if abs(lastSpeedLevel - newSpeedLevel) > 0.45 {
animateToNewShape() animateToNewShape()
} }
} }
@ -642,7 +644,8 @@ final class CurveView: UIView {
CATransaction.begin() CATransaction.begin()
CATransaction.setDisableActions(true) CATransaction.setDisableActions(true)
shapeLayer.frame = self.bounds shapeLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
shapeLayer.bounds = self.bounds
CATransaction.commit() CATransaction.commit()
} }
} }

View File

@ -280,20 +280,13 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
} }
let membersText: String let membersText: String
let membersTextIsActive: Bool
if summaryState.participantCount == 0 { if summaryState.participantCount == 0 {
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
} else { } else {
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount)) membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
} }
membersTextIsActive = false
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
if strongSelf.textIsActive != membersTextIsActive {
strongSelf.textIsActive = membersTextIsActive
strongSelf.animateTextChange()
}
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? strongSelf.theme.chat.inputPanel.panelControlAccentColor : strongSelf.theme.chat.inputPanel.secondaryTextColor)
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false) strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
@ -366,22 +359,15 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
self.audioLevelDisposable.set(nil) self.audioLevelDisposable.set(nil)
let membersText: String let membersText: String
let membersTextIsActive: Bool
if data.participantCount == 0 { if data.participantCount == 0 {
membersText = self.strings.VoiceChat_Panel_TapToJoin membersText = self.strings.VoiceChat_Panel_TapToJoin
} else { } else {
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount)) membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
} }
membersTextIsActive = false
if self.textIsActive != membersTextIsActive { self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
self.textIsActive = membersTextIsActive
self.animateTextChange()
}
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: membersTextIsActive ? self.theme.chat.inputPanel.panelControlAccentColor : 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)
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
} }
if let (size, leftInset, rightInset) = self.validLayout { if let (size, leftInset, rightInset) = self.validLayout {

View File

@ -9,12 +9,13 @@ private let titleFont = Font.regular(17.0)
private let subtitleFont = Font.regular(13.0) private let subtitleFont = Font.regular(13.0)
private let white = UIColor(rgb: 0xffffff) private let white = UIColor(rgb: 0xffffff)
private let greyColor = UIColor(rgb: 0x1c1c1e) private let greyColor = UIColor(rgb: 0x2c2c2e)
private let secondaryGreyColor = UIColor(rgb: 0x1c1c1e)
private let blue = UIColor(rgb: 0x0078ff) private let blue = UIColor(rgb: 0x0078ff)
private let lightBlue = UIColor(rgb: 0x59c7f8) private let lightBlue = UIColor(rgb: 0x59c7f8)
private let green = UIColor(rgb: 0x33c659) private let green = UIColor(rgb: 0x33c659)
private let areaSize = CGSize(width: 370.0, height: 370.0) private let areaSize = CGSize(width: 440.0, height: 440.0)
private let blobSize = CGSize(width: 244.0, height: 244.0) private let blobSize = CGSize(width: 244.0, height: 244.0)
final class VoiceChatActionButton: HighlightTrackingButtonNode { final class VoiceChatActionButton: HighlightTrackingButtonNode {
@ -35,7 +36,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
private let titleLabel: ImmediateTextNode private let titleLabel: ImmediateTextNode
private let subtitleLabel: ImmediateTextNode private let subtitleLabel: ImmediateTextNode
private var currentParams: (size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, small: Bool, title: String, subtitle: String)? private var currentParams: (size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, dark: Bool, small: Bool, title: String, subtitle: String)?
private var activePromise = ValuePromise<Bool>(false) private var activePromise = ValuePromise<Bool>(false)
private var outerColorPromise = ValuePromise<UIColor?>(nil) private var outerColorPromise = ValuePromise<UIColor?>(nil)
@ -48,10 +49,10 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
didSet { didSet {
if self.pressing { if self.pressing {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: self.containerNode, scale: 0.9) transition.updateTransformScale(node: self.iconNode, scale: 0.9)
} else { } else {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: self.containerNode, scale: 1.0) transition.updateTransformScale(node: self.iconNode, scale: 1.0)
} }
} }
} }
@ -77,10 +78,10 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
if let strongSelf = self { if let strongSelf = self {
if highlighted { if highlighted {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: strongSelf.containerNode, scale: 0.9) transition.updateTransformScale(node: strongSelf.iconNode, scale: 0.9)
} else if !strongSelf.pressing { } else if !strongSelf.pressing {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: strongSelf.containerNode, scale: 1.0) transition.updateTransformScale(node: strongSelf.iconNode, scale: 1.0)
} }
} }
} }
@ -103,7 +104,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
} }
func applyParams(animated: Bool) { func applyParams(animated: Bool) {
guard let (size, _, _, small, title, subtitle) = self.currentParams else { guard let (size, _, _, _, small, title, subtitle) = self.currentParams else {
return return
} }
@ -136,7 +137,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude)) let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
let totalHeight = titleSize.height + subtitleSize.height + 1.0 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 - totalHeight / 2.0) - 75.0), size: titleSize) self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 110.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.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) self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
@ -153,9 +154,9 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize) self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
} }
func update(size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, title: String, subtitle: String, small: Bool, animated: Bool = false) { func update(size: CGSize, buttonSize: CGSize, state: VoiceChatActionButton.State, title: String, subtitle: String, dark: Bool, small: Bool, animated: Bool = false) {
let previousState = self.currentParams?.state let previousState = self.currentParams?.state
self.currentParams = (size, buttonSize, state, small, title, subtitle) self.currentParams = (size, buttonSize, state, dark, small, title, subtitle)
var iconMuted = true var iconMuted = true
var iconColor: UIColor = .white var iconColor: UIColor = .white
@ -175,6 +176,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
case .connecting: case .connecting:
backgroundState = .connecting backgroundState = .connecting
} }
self.backgroundNode.updateColor(dark: dark)
self.backgroundNode.update(state: backgroundState, animated: true) self.backgroundNode.update(state: backgroundState, animated: true)
self.iconNode.update(state: VoiceChatMicrophoneNode.State(muted: iconMuted, color: iconColor), animated: true) self.iconNode.update(state: VoiceChatMicrophoneNode.State(muted: iconMuted, color: iconColor), animated: true)
@ -193,7 +195,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
var hitRect = self.bounds var hitRect = self.bounds
if let (_, buttonSize, _, _, _, _) = self.currentParams { if let (_, buttonSize, _, _, _, _, _) = self.currentParams {
hitRect = self.bounds.insetBy(dx: (self.bounds.width - buttonSize.width) / 2.0, dy: (self.bounds.height - buttonSize.height) / 2.0) hitRect = self.bounds.insetBy(dx: (self.bounds.width - buttonSize.width) / 2.0, dy: (self.bounds.height - buttonSize.height) / 2.0)
} }
let result = super.hitTest(point, with: event) let result = super.hitTest(point, with: event)
@ -334,7 +336,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
override init() { override init() {
self.state = .connecting self.state = .connecting
self.maskBlobView = VoiceBlobView(frame: CGRect(origin: CGPoint(x: (areaSize.width - blobSize.width) / 2.0, y: (areaSize.height - blobSize.height) / 2.0), size: blobSize), maxLevel: 2.5, mediumBlobRange: (0.69, 0.87), bigBlobRange: (0.71, 1.0)) self.maskBlobView = VoiceBlobView(frame: CGRect(origin: CGPoint(x: (areaSize.width - blobSize.width) / 2.0, y: (areaSize.height - blobSize.height) / 2.0), size: blobSize), maxLevel: 2.0, mediumBlobRange: (0.69, 0.87), bigBlobRange: (0.71, 1.0))
self.maskBlobView.setColor(white) self.maskBlobView.setColor(white)
var updateInHierarchy: ((Bool) -> Void)? var updateInHierarchy: ((Bool) -> Void)?
@ -364,7 +366,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
self.maskView.backgroundColor = .clear self.maskView.backgroundColor = .clear
self.maskGradientLayer.type = .radial self.maskGradientLayer.type = .radial
self.maskGradientLayer.colors = [UIColor(rgb: 0xffffff, alpha: 0.7).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor] self.maskGradientLayer.colors = [UIColor(rgb: 0xffffff, alpha: 0.4).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
self.maskGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5) self.maskGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
self.maskGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) self.maskGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.maskGradientLayer.transform = CATransform3DMakeScale(0.3, 0.3, 1.0) self.maskGradientLayer.transform = CATransform3DMakeScale(0.3, 0.3, 1.0)
@ -446,7 +448,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
} else { } else {
let animation = CABasicAnimation(keyPath: "transform.rotation.z") let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = 1.5 animation.duration = 1.0
animation.fromValue = NSNumber(value: Float(0.0)) animation.fromValue = NSNumber(value: Float(0.0))
animation.toValue = NSNumber(value: Float.pi * 2.0) animation.toValue = NSNumber(value: Float.pi * 2.0)
animation.repeatCount = Float.infinity animation.repeatCount = Float.infinity
@ -493,16 +495,16 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
if let active = active { if let active = active {
if active { if active {
targetColors = [blue.cgColor, green.cgColor] targetColors = [blue.cgColor, green.cgColor]
targetScale = 0.95 targetScale = 0.89
outerColor = UIColor(rgb: 0x005720) outerColor = UIColor(rgb: 0x005720)
} else { } else {
targetColors = [lightBlue.cgColor, blue.cgColor] targetColors = [lightBlue.cgColor, blue.cgColor]
targetScale = 0.8 targetScale = 0.85
outerColor = UIColor(rgb: 0x00274d) outerColor = UIColor(rgb: 0x00274d)
} }
} else { } else {
targetColors = [lightBlue.cgColor, blue.cgColor] targetColors = [lightBlue.cgColor, blue.cgColor]
targetScale = 0.35 targetScale = 0.3
outerColor = nil outerColor = nil
} }
self.updatedOuterColor?(outerColor) self.updatedOuterColor?(outerColor)
@ -606,9 +608,10 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
self?.transition = nil self?.transition = nil
} }
} else if transition == .disabled { } else if transition == .disabled {
self.transition = nil
} else if case let .blob(previousActive) = transition { } else if case let .blob(previousActive) = transition {
updateGlowAndGradientAnimations(active: newActive, previousActive: previousActive) updateGlowAndGradientAnimations(active: newActive, previousActive: previousActive)
self.transition = nil
} }
} else { } else {
self.maskBlobView.startAnimating() self.maskBlobView.startAnimating()
@ -618,6 +621,20 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
} }
} }
func updateColor(dark: Bool) {
let previousColor: CGColor = self.backgroundCircleLayer.fillColor ?? greyColor.cgColor
let targetColor: CGColor
if dark {
targetColor = secondaryGreyColor.cgColor
} else {
targetColor = greyColor.cgColor
}
self.backgroundCircleLayer.fillColor = targetColor
self.foregroundCircleLayer.fillColor = targetColor
self.backgroundCircleLayer.animate(from: previousColor, to: targetColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
self.foregroundCircleLayer.animate(from: previousColor, to: targetColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
}
func update(state: State, animated: Bool) { func update(state: State, animated: Bool) {
var animated = animated var animated = animated
var hadState = true var hadState = true
@ -868,7 +885,7 @@ final class BlobView: UIView {
func updateSpeedLevel(to newSpeedLevel: CGFloat) { func updateSpeedLevel(to newSpeedLevel: CGFloat) {
speedLevel = max(speedLevel, newSpeedLevel) speedLevel = max(speedLevel, newSpeedLevel)
if abs(lastSpeedLevel - newSpeedLevel) > 0.3 { if abs(lastSpeedLevel - newSpeedLevel) > 0.45 {
animateToNewShape() animateToNewShape()
} }
} }

View File

@ -20,6 +20,38 @@ import DeleteChatPeerActionSheetItem
import UndoUI import UndoUI
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import DirectionalPanGesture
private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
private let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
private let dimColor = UIColor(white: 0.0, alpha: 0.5)
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
if !top && !bottom {
return nil
}
return generateImage(CGSize(width: 50.0, height: 50.0), rotatedContext: { (size, context) in
let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor((dark ? fullscreenBackgroundColor : panelBackgroundColor).cgColor)
context.fill(bounds)
context.setBlendMode(.clear)
var corners: UIRectCorner = []
if top {
corners.insert(.topLeft)
corners.insert(.topRight)
}
if bottom {
corners.insert(.bottomLeft)
corners.insert(.bottomRight)
}
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0))
context.addPath(path.cgPath)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25)
}
private final class VoiceChatControllerTitleView: UIView { private final class VoiceChatControllerTitleView: UIView {
private var theme: PresentationTheme private var theme: PresentationTheme
@ -57,12 +89,6 @@ private final class VoiceChatControllerTitleView: UIView {
self.infoNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: UIColor.white.withAlphaComponent(0.5)) 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() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
@ -340,21 +366,26 @@ public final class VoiceChatController: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var darkTheme: PresentationTheme private var darkTheme: PresentationTheme
private let optionsButton: VoiceChatOptionsButton
private let closeButton: HighlightableButtonNode
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
private let contentContainer: ASDisplayNode private let contentContainer: ASDisplayNode
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let listNode: ListView private let listNode: ListView
private let topPanelNode: ASDisplayNode
private let optionsButton: VoiceChatHeaderButton
private let closeButton: VoiceChatHeaderButton
private let topCornersNode: ASImageNode
private let bottomPanelNode: ASDisplayNode
private let bottomCornersNode: ASImageNode
private let audioOutputNode: CallControllerButtonItemNode private let audioOutputNode: CallControllerButtonItemNode
private let leaveNode: CallControllerButtonItemNode private let leaveNode: CallControllerButtonItemNode
private let actionButton: VoiceChatActionButton private let actionButton: VoiceChatActionButton
private let leftBorderNode: ASDisplayNode
private let rightBorderNode: ASDisplayNode
private let titleView: VoiceChatControllerTitleView private let titleView: VoiceChatControllerTitleView
private var enqueuedTransitions: [ListTransition] = [] private var enqueuedTransitions: [ListTransition] = []
private var maxListHeight: CGFloat? private var floatingHeaderOffset: CGFloat?
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (ContainerViewLayout, CGFloat)?
private var didSetContentsReady: Bool = false private var didSetContentsReady: Bool = false
@ -403,31 +434,60 @@ public final class VoiceChatController: ViewController {
self.presentationData = sharedContext.currentPresentationData.with { $0 } self.presentationData = sharedContext.currentPresentationData.with { $0 }
self.darkTheme = defaultDarkColorPresentationTheme self.darkTheme = defaultDarkColorPresentationTheme
self.optionsButton = VoiceChatOptionsButton()
self.closeButton = HighlightableButtonNode()
self.dimNode = ASDisplayNode() self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.dimNode.backgroundColor = dimColor
self.contentContainer = ASDisplayNode() self.contentContainer = ASDisplayNode()
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = UIColor(rgb: 0x000000) self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
self.backgroundNode.cornerRadius = 12.0
self.listNode = ListView() self.listNode = ListView()
self.listNode.backgroundColor = self.darkTheme.list.itemBlocksBackgroundColor
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3) self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
self.listNode.clipsToBounds = true self.listNode.clipsToBounds = true
self.listNode.cornerRadius = 12.0 self.listNode.stackFromBottom = true
self.listNode.keepMinimalScrollHeightWithTopInset = 0
self.topPanelNode = ASDisplayNode()
self.topPanelNode.backgroundColor = panelBackgroundColor
self.topPanelNode.clipsToBounds = false
self.topPanelNode.layer.cornerRadius = 12.0
self.optionsButton = VoiceChatHeaderButton()
self.optionsButton.setImage(optionsButtonImage(dark: false))
self.closeButton = VoiceChatHeaderButton()
self.closeButton.setImage(closeButtonImage(dark: false))
self.titleView = VoiceChatControllerTitleView(theme: self.presentationData.theme)
self.titleView.set(title: self.presentationData.strings.VoiceChat_Title, subtitle: self.presentationData.strings.SocksProxySetup_ProxyStatusConnecting)
self.titleView.isUserInteractionEnabled = false
self.topCornersNode = ASImageNode()
self.topCornersNode.displaysAsynchronously = false
self.topCornersNode.displayWithoutProcessing = true
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: false)
self.bottomPanelNode = ASDisplayNode()
self.bottomPanelNode.backgroundColor = panelBackgroundColor
self.bottomPanelNode.clipsToBounds = false
self.bottomCornersNode = ASImageNode()
self.bottomCornersNode.displaysAsynchronously = false
self.bottomCornersNode.displayWithoutProcessing = true
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
self.audioOutputNode = CallControllerButtonItemNode() self.audioOutputNode = CallControllerButtonItemNode()
self.leaveNode = CallControllerButtonItemNode() self.leaveNode = CallControllerButtonItemNode()
self.actionButton = VoiceChatActionButton() self.actionButton = VoiceChatActionButton()
self.titleView = VoiceChatControllerTitleView(theme: self.presentationData.theme) self.leftBorderNode = ASDisplayNode()
self.titleView.set(title: self.presentationData.strings.VoiceChat_Title, subtitle: self.presentationData.strings.SocksProxySetup_ProxyStatusConnecting) self.leftBorderNode.backgroundColor = panelBackgroundColor
self.leftBorderNode.isUserInteractionEnabled = false
self.rightBorderNode = ASDisplayNode()
self.rightBorderNode.backgroundColor = panelBackgroundColor
self.rightBorderNode.isUserInteractionEnabled = false
super.init() super.init()
@ -521,7 +581,7 @@ public final class VoiceChatController: ViewController {
return return
} }
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false, blurBackground: true)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController) strongSelf.controller?.presentInGlobalOverlay(contextController)
}, setPeerIdWithRevealedOptions: { peerId, _ in }, setPeerIdWithRevealedOptions: { peerId, _ in
updateState { state in updateState { state in
@ -531,18 +591,25 @@ public final class VoiceChatController: ViewController {
} }
}) })
self.backgroundNode.addSubnode(self.listNode) self.topPanelNode.view.addSubview(self.titleView)
self.backgroundNode.addSubnode(self.audioOutputNode) self.topPanelNode.addSubnode(self.optionsButton)
self.backgroundNode.addSubnode(self.leaveNode) self.topPanelNode.addSubnode(self.closeButton)
self.backgroundNode.addSubnode(self.actionButton) self.topPanelNode.addSubnode(self.topCornersNode)
self.backgroundNode.view.addSubview(self.titleView)
self.backgroundNode.addSubnode(self.optionsButton) self.bottomPanelNode.addSubnode(self.bottomCornersNode)
self.backgroundNode.addSubnode(self.closeButton) self.bottomPanelNode.addSubnode(self.audioOutputNode)
self.bottomPanelNode.addSubnode(self.leaveNode)
self.bottomPanelNode.addSubnode(self.actionButton)
self.addSubnode(self.dimNode) self.addSubnode(self.dimNode)
self.addSubnode(self.contentContainer) self.addSubnode(self.contentContainer)
self.contentContainer.addSubnode(self.backgroundNode) 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 self.memberStatesDisposable = (self.call.members
|> deliverOnMainQueue).start(next: { [weak self] callMembers in |> deliverOnMainQueue).start(next: { [weak self] callMembers in
guard let strongSelf = self, let callMembers = callMembers else { guard let strongSelf = self, let callMembers = callMembers else {
@ -558,14 +625,6 @@ public final class VoiceChatController: ViewController {
strongSelf.titleView.set(title: strongSelf.presentationData.strings.VoiceChat_Title, subtitle: subtitle) strongSelf.titleView.set(title: strongSelf.presentationData.strings.VoiceChat_Title, subtitle: subtitle)
}) })
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
guard let strongSelf = self else {
return
}
if case let .known(value) = offset, value < 40.0 {
}
}
self.peerViewDisposable = (combineLatest(self.context.account.viewTracker.peerView(self.call.peerId), self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)) self.peerViewDisposable = (combineLatest(self.context.account.viewTracker.peerView(self.call.peerId), self.context.account.postbox.loadedPeerWithId(self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] view, accountPeer in |> deliverOnMainQueue).start(next: { [weak self] view, accountPeer in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -762,14 +821,32 @@ public final class VoiceChatController: ViewController {
}))) })))
} }
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode, keepInPlace: true)), items: .single(items), reactionItems: [], gesture: gesture) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode, keepInPlace: true, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController) strongSelf.controller?.presentInGlobalOverlay(contextController)
} }
self.closeButton.setImage(closeButtonImage(), for: [.normal]) self.optionsButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside)
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside) self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
self.optionsButton.addTarget(self, action: #selector(self.optionsPressed), forControlEvents: .touchUpInside) self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
if let strongSelf = self {
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
}
}
self.listNode.endedInteractiveDragging = { [weak self] in
guard let strongSelf = self else {
return
}
switch strongSelf.listNode.visibleContentOffset() {
case let .known(value):
if value <= -10.0 {
// strongSelf.controller?.dismiss()
}
default:
break
}
}
} }
deinit { deinit {
@ -793,14 +870,22 @@ public final class VoiceChatController: ViewController {
longTapRecognizer.delegate = self longTapRecognizer.delegate = self
self.actionButton.view.addGestureRecognizer(longTapRecognizer) self.actionButton.view.addGestureRecognizer(longTapRecognizer)
let panRecognizer = CallPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) let panRecognizer = DirectionalPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
panRecognizer.shouldBegin = { [weak self] _ in panRecognizer.delegate = self
guard let _ = self else { panRecognizer.delaysTouchesBegan = false
return false panRecognizer.cancelsTouchesInView = true
} // panRecognizer.shouldBegin = { [weak self] point in
return true // guard let strongSelf = self else {
} // return false
self.backgroundNode.view.addGestureRecognizer(panRecognizer) // }
// if strongSelf.topPanelNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.topPanelNode.view)) {
// if strongSelf.topPanelNode.frame.maxY <= strongSelf.listNode.frame.minY {
// return true
// }
// }
// return false
// }
self.view.addGestureRecognizer(panRecognizer)
} }
@objc private func optionsPressed() { @objc private func optionsPressed() {
@ -829,15 +914,6 @@ public final class VoiceChatController: ViewController {
} }
private var actionButtonPressGestureStartTime: Double = 0.0 private var actionButtonPressGestureStartTime: Double = 0.0
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let callState = self.callState, case .connected = callState.networkState, let muteState = callState.muteState, !muteState.canUnmute {
return false
} else {
return true
}
}
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { @objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard let callState = self.callState else { guard let callState = self.callState else {
return return
@ -955,6 +1031,101 @@ public final class VoiceChatController: ViewController {
} }
} }
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
guard let (validLayout, _) = self.validLayout else {
return
}
self.floatingHeaderOffset = offset
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
let topPanelHeight: CGFloat = 63.0
let listTopInset = layoutTopInset + topPanelHeight
let rawPanelOffset = offset + listTopInset - topPanelHeight
let panelOffset = max(layoutTopInset, rawPanelOffset)
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: validLayout.size.width, height: topPanelHeight))
let previousFrame = self.topPanelNode.frame
if !topPanelFrame.equalTo(previousFrame) {
self.topPanelNode.frame = topPanelFrame
let positionDelta = CGPoint(x: topPanelFrame.minX - previousFrame.minX, y: topPanelFrame.minY - previousFrame.minY)
transition.animateOffsetAdditive(node: self.topPanelNode, offset: positionDelta.y)
}
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: validLayout.size.width, height: validLayout.size.height))
let sideInset: CGFloat = 16.0
let leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
let rightBorderFrame = CGRect(origin: CGPoint(x: validLayout.size.width - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
let previousBackgroundFrame = self.backgroundNode.frame
let previousLeftBorderFrame = self.leftBorderNode.frame
let previousRightBorderFrame = self.rightBorderNode.frame
self.updateColors(fullscreen: panelOffset == layoutTopInset)
if !backgroundFrame.equalTo(previousBackgroundFrame) {
self.backgroundNode.frame = backgroundFrame
self.leftBorderNode.frame = leftBorderFrame
self.rightBorderNode.frame = rightBorderFrame
let backgroundPositionDelta = CGPoint(x: backgroundFrame.minX - previousBackgroundFrame.minX, y: backgroundFrame.minY - previousBackgroundFrame.minY)
transition.animateOffsetAdditive(node: self.backgroundNode, offset: backgroundPositionDelta.y)
let leftBorderPositionDelta = CGPoint(x: leftBorderFrame.minX - previousLeftBorderFrame.minX, y: leftBorderFrame.minY - previousLeftBorderFrame.minY)
transition.animateOffsetAdditive(node: self.leftBorderNode, offset: leftBorderPositionDelta.y)
let rightBorderPositionDelta = CGPoint(x: rightBorderFrame.minX - previousRightBorderFrame.minX, y: rightBorderFrame.minY - previousRightBorderFrame.minY)
transition.animateOffsetAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta.y)
}
}
var isFullscreen = false
func updateColors(fullscreen: Bool) {
guard self.isFullscreen != fullscreen else {
return
}
self.isFullscreen = fullscreen
self.controller?.statusBar.statusBarStyle = fullscreen ? .White : .Ignore
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor)
transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
transition.updateBackgroundColor(node: self.topPanelNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.bottomPanelNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.leftBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
if let snapshotView = self.topCornersNode.view.snapshotContentTree() {
snapshotView.frame = self.topCornersNode.frame
self.topPanelNode.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: fullscreen)
if let snapshotView = self.bottomCornersNode.view.snapshotContentTree() {
snapshotView.frame = self.bottomCornersNode.frame
self.bottomPanelNode.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: fullscreen)
self.optionsButton.setImage(optionsButtonImage(dark: fullscreen), animated: transition.isAnimated)
self.closeButton.setImage(closeButtonImage(dark: fullscreen), animated: transition.isAnimated)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let isFirstTime = self.validLayout == nil let isFirstTime = self.validLayout == nil
self.validLayout = (layout, navigationHeight) self.validLayout = (layout, navigationHeight)
@ -965,31 +1136,41 @@ public final class VoiceChatController: ViewController {
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let contentHeight: CGFloat = layout.size.height - 240.0
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - contentHeight), size: CGSize(width: layout.size.width, height: contentHeight + 1000.0)))
let bottomAreaHeight: CGFloat = 290.0 let bottomAreaHeight: CGFloat = 268.0
let listOrigin = CGPoint(x: 16.0, y: 64.0) let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
var listHeight: CGFloat = 44.0 + 56.0 let sideInset: CGFloat = 16.0
if let maxListHeight = self.maxListHeight { var insets = UIEdgeInsets()
listHeight = min(max(1.0, contentHeight - bottomAreaHeight - listOrigin.y - layout.intrinsicInsets.bottom + 25.0), maxListHeight + 44.0) insets.left = layout.safeInsets.left + sideInset
} insets.right = layout.safeInsets.right + sideInset
let listFrame = CGRect(origin: listOrigin, size: CGSize(width: layout.size.width - 16.0 * 2.0, height: listHeight)) let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
transition.updateFrame(node: self.listNode, frame: listFrame) let listTopInset = layoutTopInset + 63.0
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
insets.top = max(0.0, listSize.height - 44.0 - floor(56.0 * 3.5))
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listSize))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listFrame.size, insets: UIEdgeInsets(top: -1.0, left: -6.0, bottom: -1.0, right: -6.0), scrollIndicatorInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0), duration: duration, curve: curve) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
// let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listFrame.size, insets: UIEdgeInsets(top: -1.0, left: -6.0, bottom: -1.0, right: -6.0), scrollIndicatorInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0), duration: duration, curve: curve)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
transition.updateFrame(node: self.topCornersNode, frame: CGRect(origin: CGPoint(x: sideInset, y: 63.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: 50.0)))
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: layout.size.width, height: bottomPanelHeight))
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
transition.updateFrame(node: self.bottomCornersNode, frame: CGRect(origin: CGPoint(x: sideInset, y: -50.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: 50.0)))
let sideButtonSize = CGSize(width: 60.0, height: 60.0) let sideButtonSize = CGSize(width: 60.0, height: 60.0)
let centralButtonSize = CGSize(width: 370.0, height: 370.0) let centralButtonSize = CGSize(width: 440.0, height: 440.0)
let actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - centralButtonSize.width) / 2.0), y: contentHeight - bottomAreaHeight - layout.intrinsicInsets.bottom + floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize) let actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
let actionButtonState: VoiceChatActionButton.State let actionButtonState: VoiceChatActionButton.State
let actionButtonTitle: String let actionButtonTitle: String
@ -1036,7 +1217,7 @@ public final class VoiceChatController: ViewController {
} }
self.actionButton.isUserInteractionEnabled = actionButtonEnabled self.actionButton.isUserInteractionEnabled = actionButtonEnabled
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 144.0, height: 144.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, small: layout.size.width < 330.0, animated: true) self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 144.0, height: 144.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: false, small: layout.size.width < 330.0, animated: true)
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame) transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
var audioMode: CallControllerButtonsSpeakerMode = .none var audioMode: CallControllerButtonsSpeakerMode = .none
@ -1094,8 +1275,8 @@ public final class VoiceChatController: ViewController {
let sideButtonOffset = min(36.0, floor((((layout.size.width - 144.0) / 2.0) - sideButtonSize.width) / 2.0)) let sideButtonOffset = min(36.0, floor((((layout.size.width - 144.0) / 2.0) - sideButtonSize.width) / 2.0))
let sideButtonOrigin = max(sideButtonMinimalInset, floor((layout.size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width) let sideButtonOrigin = max(sideButtonMinimalInset, floor((layout.size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonOrigin, y: contentHeight - bottomAreaHeight - layout.intrinsicInsets.bottom + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)) transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideButtonOrigin - sideButtonSize.width, y: contentHeight - bottomAreaHeight - layout.intrinsicInsets.bottom + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)) transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideButtonOrigin - sideButtonSize.width, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
if isFirstTime { if isFirstTime {
while !self.enqueuedTransitions.isEmpty { while !self.enqueuedTransitions.isEmpty {
@ -1105,45 +1286,31 @@ public final class VoiceChatController: ViewController {
} }
func animateIn() { func animateIn() {
guard let (layout, _) = self.validLayout else { self.layer.animateBoundsOriginYAdditive(from: -self.bounds.size.height, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
return self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} self.dimNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.bounds.size.height), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true)
self.isHidden = false
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset: CGFloat = layout.size.height - 240.0
self.contentContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: { _ in
})
} }
func animateOut(completion: (() -> Void)?) { func animateOut(completion: (() -> Void)?) {
guard let (layout, _) = self.validLayout else {
return
}
var dimCompleted = false var dimCompleted = false
var offsetCompleted = false var offsetCompleted = false
let internalCompletion: () -> Void = { [weak self] in let internalCompletion: () -> Void = { [weak self] in
if dimCompleted && offsetCompleted { if dimCompleted && offsetCompleted {
if let strongSelf = self { if let strongSelf = self {
strongSelf.layer.removeAllAnimations()
strongSelf.dimNode.layer.removeAllAnimations() strongSelf.dimNode.layer.removeAllAnimations()
strongSelf.contentContainer.layer.removeAllAnimations()
} }
completion?() completion?()
} }
} }
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in self.layer.animateBoundsOriginYAdditive(from: self.bounds.origin.y, to: -self.bounds.size.height, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
dimCompleted = true offsetCompleted = true
internalCompletion() internalCompletion()
}) })
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
let offset: CGFloat = layout.size.height - 240.0 self.dimNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.bounds.size.height), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
self.contentContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in dimCompleted = true
offsetCompleted = true
internalCompletion() internalCompletion()
}) })
} }
@ -1180,14 +1347,13 @@ public final class VoiceChatController: ViewController {
strongSelf.controller?.contentsReady.set(true) strongSelf.controller?.contentsReady.set(true)
} }
if !transition.deletions.isEmpty || !transition.insertions.isEmpty { if false, !transition.deletions.isEmpty || !transition.insertions.isEmpty {
var itemHeight: CGFloat = 56.0 var itemHeight: CGFloat = 56.0
strongSelf.listNode.forEachVisibleItemNode { node in strongSelf.listNode.forEachVisibleItemNode { node in
if node.frame.height > 0 { if node.frame.height > 0 {
itemHeight = node.frame.height itemHeight = node.frame.height
} }
} }
strongSelf.maxListHeight = CGFloat(transition.count - 1) * itemHeight
if let (layout, navigationHeight) = strongSelf.validLayout { if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring)) strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
} }
@ -1272,39 +1438,87 @@ public final class VoiceChatController: ViewController {
self.enqueueTransition(transition) self.enqueueTransition(transition)
} }
@objc private func panGesture(_ recognizer: CallPanGestureRecognizer) { override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// if let callState = self.callState, case .connected = callState.networkState, let muteState = callState.muteState, !muteState.canUnmute {
// return false
// }
if let recognizer = gestureRecognizer as? UIPanGestureRecognizer {
let location = recognizer.location(in: self.view)
if let view = super.hitTest(location, with: nil) {
if let gestureRecognizers = view.gestureRecognizers, view != self.view {
for gestureRecognizer in gestureRecognizers {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled {
print(view)
if panGestureRecognizer.state != .began {
panGestureRecognizer.isEnabled = false
panGestureRecognizer.isEnabled = true
}
}
}
}
}
}
return true
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state { switch recognizer.state {
case .began: case .began:
self.contentContainer.clipsToBounds = true break
case .changed: case .changed:
let offset = recognizer.translation(in: self.view).y let translation = recognizer.translation(in: self.contentContainer.view)
var bounds = self.contentContainer.bounds var bounds = self.contentContainer.bounds
bounds.origin.y = -offset bounds.origin.y = -translation.y
self.contentContainer.bounds = bounds bounds.origin.y = min(0.0, bounds.origin.y)
case .cancelled, .ended: if bounds.origin.y < 0.0 {
let velocity = recognizer.velocity(in: self.view).y //let delta = -bounds.origin.y
if velocity < 200.0 { //bounds.origin.y = -((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0)
var bounds = self.contentContainer.bounds
let previous = bounds
bounds.origin = CGPoint()
self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previous, to: bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
} else {
var bounds = self.contentContainer.bounds
let previous = bounds
bounds.origin = CGPoint(x: 0.0, y: velocity > 0.0 ? -bounds.height: bounds.height)
self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previous, to: bounds, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { [weak self] _ in
self?.controller?.dismissInteractively()
var initialBounds = bounds
initialBounds.origin = CGPoint()
self?.contentContainer.bounds = initialBounds
})
} }
self.contentContainer.bounds = bounds
case .ended:
let translation = recognizer.translation(in: self.contentContainer.view)
var bounds = self.contentContainer.bounds
bounds.origin.y = -translation.y
let velocity = recognizer.velocity(in: self.contentContainer.view)
if (bounds.minY < -60.0 || velocity.y > 300.0) {
self.controller?.dismiss()
} else {
let previousBounds = self.bounds
var bounds = self.bounds
bounds.origin.y = 0.0
self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
}
case .cancelled:
let previousBounds = self.contentContainer.bounds
var bounds = self.contentContainer.bounds
bounds.origin.y = 0.0
self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
default: default:
break break
} }
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
print("actually hitting")
if result === self.topPanelNode.view || result === self.bottomPanelNode.view {
return self.view
}
if !self.bounds.contains(point) {
return nil
}
if point.y < self.topPanelNode.frame.minY {
return self.dimNode.view
}
return result
}
} }
private let sharedContext: SharedAccountContext private let sharedContext: SharedAccountContext
@ -1420,14 +1634,16 @@ public final class VoiceChatController: ViewController {
private final class VoiceChatContextExtractedContentSource: ContextExtractedContentSource { private final class VoiceChatContextExtractedContentSource: ContextExtractedContentSource {
var keepInPlace: Bool var keepInPlace: Bool
let ignoreContentTouches: Bool = true let ignoreContentTouches: Bool = true
let blurBackground: Bool
private let controller: ViewController private let controller: ViewController
private let sourceNode: ContextExtractedContentContainingNode private let sourceNode: ContextExtractedContentContainingNode
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) { init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool) {
self.controller = controller self.controller = controller
self.sourceNode = sourceNode self.sourceNode = sourceNode
self.keepInPlace = keepInPlace self.keepInPlace = keepInPlace
self.blurBackground = blurBackground
} }
func takeView() -> ContextControllerTakeViewInfo? { func takeView() -> ContextControllerTakeViewInfo? {

View File

@ -3,10 +3,13 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
func optionsButtonImage() -> UIImage? { func optionsButtonImage(dark: Bool) -> UIImage? {
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor) 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: 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: 12.0, y: 12.0, width: 4.0, height: 4.0))
@ -14,11 +17,11 @@ func optionsButtonImage() -> UIImage? {
}) })
} }
func closeButtonImage() -> UIImage? { func closeButtonImage(dark: Bool) -> UIImage? {
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0x1c1c1e).cgColor) context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0) context.setLineWidth(2.0)
@ -35,7 +38,7 @@ func closeButtonImage() -> UIImage? {
}) })
} }
final class VoiceChatOptionsButton: HighlightableButtonNode { final class VoiceChatHeaderButton: HighlightableButtonNode {
let extractedContainerNode: ContextExtractedContentContainingNode let extractedContainerNode: ContextExtractedContentContainingNode
let containerNode: ContextControllerSourceNode let containerNode: ContextControllerSourceNode
private let iconNode: ASImageNode private let iconNode: ASImageNode
@ -57,6 +60,12 @@ final class VoiceChatOptionsButton: HighlightableButtonNode {
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.containerNode.shouldBegin = { [weak self] location in
guard let strongSelf = self, let _ = strongSelf.contextAction else {
return false
}
return true
}
self.containerNode.activated = { [weak self] gesture, _ in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -64,17 +73,7 @@ final class VoiceChatOptionsButton: HighlightableButtonNode {
strongSelf.contextAction?(strongSelf.containerNode, gesture) strongSelf.contextAction?(strongSelf.containerNode, gesture)
} }
self.iconNode.image = generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in self.iconNode.image = optionsButtonImage(dark: false)
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0x1c1c1e).cgColor)
context.fillEllipse(in: 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))
})
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0)) self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
self.extractedContainerNode.frame = self.containerNode.bounds self.extractedContainerNode.frame = self.containerNode.bounds
@ -82,6 +81,18 @@ final class VoiceChatOptionsButton: HighlightableButtonNode {
self.iconNode.frame = self.containerNode.bounds self.iconNode.frame = self.containerNode.bounds
} }
func setImage(_ image: UIImage?, animated: Bool = false) {
if animated, let snapshotView = self.iconNode.view.snapshotContentTree() {
snapshotView.frame = self.iconNode.frame
self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
self.iconNode.image = image
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
self.view.isOpaque = false self.view.isOpaque = false

View File

@ -139,7 +139,6 @@ public final class VoiceChatParticipantItem: ListViewItem {
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0)) private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
@ -172,9 +171,6 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)? private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
public init() { public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode() self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true self.topStripeNode.isLayerBacked = true
@ -470,7 +466,6 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
if let _ = updatedTheme { if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
} }
@ -517,20 +512,16 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let _ = titleApply() let _ = titleApply()
let _ = statusApply() let _ = statusApply()
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil { if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) strongSelf.insertSubnode(strongSelf.topStripeNode, at: 0)
} }
if strongSelf.bottomStripeNode.supernode == nil { if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1)
} }
strongSelf.topStripeNode.isHidden = first strongSelf.topStripeNode.isHidden = first
strongSelf.bottomStripeNode.isHidden = last strongSelf.bottomStripeNode.isHidden = last
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height + -separatorHeight), size: CGSize(width: layoutSize.width - leftInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height + -separatorHeight), size: CGSize(width: layoutSize.width - leftInset, height: separatorHeight)))
@ -665,7 +656,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.iconNode?.frame = CGRect(origin: CGPoint(), size: animationSize) strongSelf.iconNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
strongSelf.animationNode?.frame = CGRect(origin: CGPoint(), size: animationSize) strongSelf.animationNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
strongSelf.actionButtonNode.frame = CGRect(x: params.width - animationSize.width - 6.0, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height) strongSelf.actionButtonNode.frame = CGRect(x: params.width - animationSize.width - 6.0 - params.rightInset, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
if let presence = item.presence as? TelegramUserPresence { if let presence = item.presence as? TelegramUserPresence {
strongSelf.peerPresenceManager?.reset(presence: presence) strongSelf.peerPresenceManager?.reset(presence: presence)
@ -681,14 +672,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} }
var isHighlighted = false var isHighlighted = false
var reallyHighlighted: Bool {
var reallyHighlighted = self.isHighlighted
return reallyHighlighted
}
func updateIsHighlighted(transition: ContainedViewLayoutTransition) { func updateIsHighlighted(transition: ContainedViewLayoutTransition) {
if self.reallyHighlighted { if self.isHighlighted {
self.highlightedBackgroundNode.alpha = 1.0 self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil { if self.highlightedBackgroundNode.supernode == nil {
var anchorNode: ASDisplayNode? var anchorNode: ASDisplayNode?
@ -696,8 +681,6 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
anchorNode = self.bottomStripeNode anchorNode = self.bottomStripeNode
} else if self.topStripeNode.supernode != nil { } else if self.topStripeNode.supernode != nil {
anchorNode = self.topStripeNode anchorNode = self.topStripeNode
} else if self.backgroundNode.supernode != nil {
anchorNode = self.backgroundNode
} }
if let anchorNode = anchorNode { if let anchorNode = anchorNode {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
@ -760,7 +743,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
super.updateRevealOffset(offset: offset, transition: transition) super.updateRevealOffset(offset: offset, transition: transition)
if let item = self.layoutParams?.0, let params = self.layoutParams?.1 { if let item = self.layoutParams?.0, let params = self.layoutParams?.1 {
var leftInset: CGFloat = 65.0 + params.leftInset let leftInset: CGFloat = 65.0 + params.leftInset
var avatarFrame = self.avatarNode.frame var avatarFrame = self.avatarNode.frame
avatarFrame.origin.x = offset + leftInset - 50.0 avatarFrame.origin.x = offset + leftInset - 50.0

View File

@ -7,6 +7,7 @@ import Postbox
final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource { final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false let keepInPlace: Bool = false
let ignoreContentTouches: Bool = false let ignoreContentTouches: Bool = false
let blurBackground: Bool = true
private weak var chatNode: ChatControllerNode? private weak var chatNode: ChatControllerNode?
private let message: Message private let message: Message

View File

@ -6017,6 +6017,7 @@ public final class PeerInfoScreen: ViewController {
private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource { private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = true let keepInPlace: Bool = true
let ignoreContentTouches: Bool = true let ignoreContentTouches: Bool = true
let blurBackground: Bool = true
private let controller: ViewController private let controller: ViewController
private let sourceNode: ContextExtractedContentContainingNode private let sourceNode: ContextExtractedContentContainingNode
@ -6261,6 +6262,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
private final class MessageContextExtractedContentSource: ContextExtractedContentSource { private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false let keepInPlace: Bool = false
let ignoreContentTouches: Bool = true let ignoreContentTouches: Bool = true
let blurBackground: Bool = true
private let sourceNode: ContextExtractedContentContainingNode private let sourceNode: ContextExtractedContentContainingNode