Voice Chat UI improvements

This commit is contained in:
Ilya Laktyushin 2020-12-09 17:02:22 +04:00
parent 7489524aa5
commit 8af1de7109
7 changed files with 148 additions and 65 deletions

View File

@ -1051,14 +1051,15 @@ public extension ContainedViewLayoutTransition {
#if os(iOS)
public extension ContainedViewLayoutTransition {
func animateView(_ f: @escaping () -> Void) {
func animateView(_ f: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
f()
completion?(true)
case let .animated(duration, curve):
UIView.animate(withDuration: duration, delay: 0.0, options: curve.viewAnimationOptions, animations: {
f()
}, completion: nil)
}, completion: completion)
}
}
}

View File

@ -500,6 +500,8 @@ open class NavigationController: UINavigationController, ContainableController,
var notifyGlobalOverlayControllersUpdated = false
var additionalSideInsets = UIEdgeInsets()
var modalStyleOverlayTransitionFactor: CGFloat = 0.0
var previousGlobalOverlayContainer: NavigationOverlayContainer?
for i in (0 ..< self.globalOverlayContainers.count).reversed() {
@ -527,6 +529,9 @@ open class NavigationController: UINavigationController, ContainableController,
notifyGlobalOverlayControllersUpdated = true
}
let controllerAdditionalSideInsets = overlayContainer.controller.additionalSideInsets
additionalSideInsets = UIEdgeInsets(top: 0.0, left: max(additionalSideInsets.left, controllerAdditionalSideInsets.left), bottom: 0.0, right: max(additionalSideInsets.right, controllerAdditionalSideInsets.right))
if overlayContainer.supernode != nil {
previousGlobalOverlayContainer = overlayContainer
let controllerStatusBarStyle = overlayContainer.controller.statusBar.statusBarStyle
@ -546,7 +551,6 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
var additionalSideInsets = UIEdgeInsets()
var previousOverlayContainer: NavigationOverlayContainer?
for i in (0 ..< self.overlayContainers.count).reversed() {
let overlayContainer = self.overlayContainers[i]

View File

@ -47,15 +47,13 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
var pressing: Bool = false {
didSet {
var pressing = self.pressing
var snap = false
if let (_, _, _, _, _, _, _, snapValue) = self.currentParams, snapValue {
pressing = false
snap = true
if let (_, _, _, _, _, _, _, snapValue) = self.currentParams {
snap = snapValue
}
if pressing {
if self.pressing {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: self.iconNode, scale: 0.9)
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 0.9)
} else {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 1.0)
@ -80,18 +78,15 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.iconNode)
self.highligthedChanged = { [weak self] highlighted in
self.highligthedChanged = { [weak self] pressing in
if let strongSelf = self {
var highlighted = highlighted
var snap = false
if let (_, _, _, _, _, _, _, snapValue) = strongSelf.currentParams, snapValue {
highlighted = false
snap = true
if let (_, _, _, _, _, _, _, snapValue) = strongSelf.currentParams {
snap = snapValue
}
if highlighted {
if pressing {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: strongSelf.iconNode, scale: 0.9)
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 0.9)
} else if !strongSelf.pressing {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 1.0)
@ -173,7 +168,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
if snap {
transition.updateTransformScale(node: self.backgroundNode, scale: active ? 0.75 : 0.5)
transition.updateTransformScale(node: self.backgroundNode, scale: self.pressing ? 0.75 : 0.5)
transition.updateTransformScale(node: self.iconNode, scale: 0.5)
transition.updateAlpha(node: self.titleLabel, alpha: 0.0)
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
@ -909,8 +904,8 @@ private final class VoiceBlobView: UIView {
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 0.85,
maxSpeed: 7,
minSpeed: 0.9,
maxSpeed: 4.0,
minScale: mediumBlobRange.min,
maxScale: mediumBlobRange.max
)
@ -918,8 +913,8 @@ private final class VoiceBlobView: UIView {
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 0.85,
maxSpeed: 7,
minSpeed: 1.0,
maxSpeed: 4.4,
minScale: bigBlobRange.min,
maxScale: bigBlobRange.max
)

View File

@ -460,6 +460,7 @@ public final class VoiceChatController: ViewController {
self.dimNode.backgroundColor = dimColor
self.contentContainer = ASDisplayNode()
self.contentContainer.isHidden = true
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
@ -1596,7 +1597,7 @@ public final class VoiceChatController: ViewController {
}
func animateIn() {
guard let (layout, _) = self.validLayout else {
guard let (layout, navigationHeight) = self.validLayout else {
return
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
@ -1605,13 +1606,23 @@ public final class VoiceChatController: ViewController {
let initialBounds = self.contentContainer.bounds
self.contentContainer.bounds = initialBounds.offsetBy(dx: 0.0, dy: -(layout.size.height - topPanelFrame.minY))
transition.animateView {
self.contentContainer.isHidden = false
transition.animateView({
self.contentContainer.view.bounds = initialBounds
}
}, completion: { _ in
self.bottomPanelNode.addSubnode(self.actionButton)
self.containerLayoutUpdated(layout, navigationHeight:navigationHeight, transition: .immediate)
self.controller?.currentOverlayController?.dismiss()
self.controller?.currentOverlayController = nil
})
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
func animateOut(completion: (() -> Void)?) {
guard let (layout, _) = self.validLayout else {
return
}
var offsetCompleted = false
let internalCompletion: () -> Void = { [weak self] in
if offsetCompleted {
@ -1627,7 +1638,9 @@ public final class VoiceChatController: ViewController {
}
}
self.contentContainer.layer.animateBoundsOriginYAdditive(from: self.contentContainer.bounds.origin.y, to: -self.contentContainer.bounds.size.height, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
let topPanelFrame = self.topPanelNode.view.convert(self.topPanelNode.bounds, to: self.view)
self.contentContainer.layer.animateBoundsOriginYAdditive(from: self.contentContainer.bounds.origin.y, to: -(layout.size.height - topPanelFrame.minY), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
offsetCompleted = true
internalCompletion()
})
@ -1857,6 +1870,7 @@ public final class VoiceChatController: ViewController {
private var didAppearOnce: Bool = false
private var isDismissed: Bool = false
private var isDisconnected: Bool = false
private var controllerNode: Node {
return self.displayNode as! Node
@ -1864,7 +1878,7 @@ public final class VoiceChatController: ViewController {
private let idleTimerExtensionDisposable = MetaDisposable()
private var currentOverlayController: VoiceChatOverlayController?
private weak var currentOverlayController: VoiceChatOverlayController?
private var validLayout: ContainerViewLayout?
@ -1937,39 +1951,41 @@ public final class VoiceChatController: ViewController {
self.idleTimerExtensionDisposable.set(nil)
DispatchQueue.main.async {
self.didAppearOnce = false
self.isDismissed = true
self.detachActionButton()
self.onViewDidDisappear?()
}
}
public func dismiss(closing: Bool) {
if closing {
self.dismiss()
if !closing {
self.detachActionButton()
} else {
let overlayController = VoiceChatOverlayController(actionButton: self.controllerNode.actionButton)
if let navigationController = self.navigationController as? NavigationController {
navigationController.presentOverlay(controller: overlayController, inGlobal: true, blockInteraction: false)
self.isDisconnected = true
}
self.dismiss()
}
private func detachActionButton() {
guard self.currentOverlayController == nil && !self.isDisconnected else {
return
}
let overlayController = VoiceChatOverlayController(actionButton: self.controllerNode.actionButton)
if let navigationController = self.navigationController as? NavigationController {
navigationController.presentOverlay(controller: overlayController, inGlobal: true, blockInteraction: false)
}
self.currentOverlayController = overlayController
self.reclaimActionButton = { [weak self, weak overlayController] in
if let strongSelf = self {
let actionButton = strongSelf.controllerNode.actionButton
overlayController?.animateOut(reclaim: true, completion: {})
strongSelf.reclaimActionButton = nil
}
self.sharedContext.presentGlobalController(overlayController, nil)
self.currentOverlayController = overlayController
self.reclaimActionButton = { [weak self, weak overlayController] in
if let strongSelf = self {
let actionButton = strongSelf.controllerNode.actionButton
overlayController?.animateOut(reclaim: true, completion: { [weak self] in
if let strongSelf = self {
strongSelf.controllerNode.bottomPanelNode.addSubnode(actionButton)
if let validLayout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(validLayout, transition: .immediate)
}
}
})
strongSelf.currentOverlayController = nil
strongSelf.reclaimActionButton = nil
}
}
self.dismiss()
}
}

View File

@ -15,7 +15,7 @@ import AppBundle
import ContextUI
import PresentationDataUtils
final class VoiceChatOverlayController: ViewController {
public final class VoiceChatOverlayController: ViewController {
private final class Node: ViewControllerTracingNode, UIGestureRecognizerDelegate {
private weak var controller: VoiceChatOverlayController?
@ -24,6 +24,41 @@ final class VoiceChatOverlayController: ViewController {
init(controller: VoiceChatOverlayController) {
self.controller = controller
}
private var isButtonHidden = false
func update(hidden: Bool, slide: Bool, animated: Bool) {
guard let actionButton = self.controller?.actionButton, actionButton.supernode === self else {
return
}
if self.isButtonHidden == hidden {
return
}
self.isButtonHidden = hidden
if animated {
if hidden {
if slide {
} else {
actionButton.layer.removeAllAnimations()
actionButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak actionButton] _ in
actionButton?.isHidden = true
})
}
} else {
if slide {
} else {
actionButton.layer.removeAllAnimations()
actionButton.isHidden = false
actionButton.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
}
}
} else {
}
}
func animateIn(from: CGRect) {
guard let actionButton = self.controller?.actionButton else {
@ -53,7 +88,7 @@ final class VoiceChatOverlayController: ViewController {
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
}
actionButton.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak self] _ in
actionButton.layer.animateKeyframes(values: keyframes, duration: 0.2, keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { _ in
})
}
@ -88,9 +123,8 @@ final class VoiceChatOverlayController: ViewController {
actionButton.update(snap: false)
actionButton.position = targetPosition
actionButton.layer.animateKeyframes(values: keyframes, duration: 0.4, keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { [weak self] _ in
actionButton.layer.animateKeyframes(values: keyframes, duration: 0.4, keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { _ in
completion()
self?.controller?.dismiss()
})
} else {
actionButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak self, weak actionButton] _ in
@ -101,11 +135,14 @@ final class VoiceChatOverlayController: ViewController {
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let actionButton = self.controller?.actionButton, actionButton.supernode === self, actionButton.frame.contains(point) {
return actionButton.hitTest(self.view.convert(point, to: actionButton.view), with: event)
} else {
return nil
if let actionButton = self.controller?.actionButton, actionButton.supernode === self {
let actionButtonSize = CGSize(width: 84.0, height: 84.0)
let actionButtonFrame = CGRect(origin: CGPoint(x: actionButton.position.x - actionButtonSize.width / 2.0, y: actionButton.position.y - actionButtonSize.height / 2.0), size: actionButtonSize)
if actionButtonFrame.contains(point) {
return actionButton.hitTest(self.view.convert(point, to: actionButton.view), with: event)
}
}
return nil
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -141,6 +178,10 @@ final class VoiceChatOverlayController: ViewController {
self.additionalSideInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 75.0)
}
deinit {
print("")
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -150,7 +191,7 @@ final class VoiceChatOverlayController: ViewController {
self.displayNodeDidLoad()
}
override func dismiss(completion: (() -> Void)? = nil) {
public override func dismiss(completion: (() -> Void)? = nil) {
super.dismiss(completion: completion)
self.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
@ -160,6 +201,10 @@ final class VoiceChatOverlayController: ViewController {
self.controllerNode.animateOut(reclaim: reclaim, completion: completion)
}
public func update(hidden: Bool, slide: Bool, animated: Bool) {
self.controllerNode.update(hidden: hidden, slide: slide, animated: animated)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)

View File

@ -666,8 +666,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}, sendMessagesWithSignals: { [weak self] signals, _, _ in
if let strongSelf = self {
strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in })
strongSelf.editMessageMediaWithLegacySignals(signals!)
if canEditMessage(context: strongSelf.context, limitsConfiguration: strongSelf.context.currentLimitsConfiguration.with { $0 }, message: message) {
strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in })
strongSelf.editMessageMediaWithLegacySignals(signals!)
} else {
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: false)
}
}
}, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
@ -7052,6 +7056,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if saveInterfaceState {
self.saveInterfaceState(includeScrollState: false)
}
if let navigationController = self.navigationController as? NavigationController {
var voiceChatOverlayController: VoiceChatOverlayController?
for controller in navigationController.globalOverlayControllers {
if let controller = controller as? VoiceChatOverlayController {
voiceChatOverlayController = controller
break
}
}
if let controller = voiceChatOverlayController {
if self.presentationInterfaceState.inputMode == .text && self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 {
controller.update(hidden: true, slide: false, animated: true)
} else {
controller.update(hidden: false, slide: false, animated: true)
}
}
}
}
private func updateItemNodesSelectionStates(animated: Bool) {

View File

@ -634,7 +634,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|> deliverOnMainQueue).start(next: { [weak self] call in
if let strongSelf = self {
if call !== strongSelf.groupCallController?.call {
strongSelf.groupCallController?.dismiss()
strongSelf.groupCallController?.dismiss(closing: true)
strongSelf.groupCallController = nil
strongSelf.hasOngoingCall.set(false)