mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-25 15:01:38 +00:00
299 lines
14 KiB
Swift
299 lines
14 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
|
|
private struct MappedStatusBar {
|
|
let style: StatusBarStyle
|
|
let frame: CGRect
|
|
let statusBar: StatusBar?
|
|
}
|
|
|
|
private struct MappedStatusBarSurface {
|
|
let statusBars: [MappedStatusBar]
|
|
let surface: StatusBarSurface
|
|
}
|
|
|
|
private func mapStatusBar(_ statusBar: StatusBar, forceInCall: Bool) -> MappedStatusBar {
|
|
let frame = CGRect(origin: statusBar.view.convert(CGPoint(), to: nil), size: statusBar.frame.size)
|
|
let resolvedStyle: StatusBarStyle
|
|
switch statusBar.statusBarStyle {
|
|
case .Black, .White:
|
|
if forceInCall {
|
|
resolvedStyle = .White
|
|
} else {
|
|
resolvedStyle = statusBar.statusBarStyle
|
|
}
|
|
default:
|
|
resolvedStyle = statusBar.statusBarStyle
|
|
}
|
|
return MappedStatusBar(style: resolvedStyle, frame: frame, statusBar: statusBar)
|
|
}
|
|
|
|
private func mappedSurface(_ surface: StatusBarSurface, forceInCall: Bool) -> MappedStatusBarSurface {
|
|
var statusBars: [MappedStatusBar] = []
|
|
for statusBar in surface.statusBars {
|
|
if statusBar.statusBarStyle != .Ignore {
|
|
statusBars.append(mapStatusBar(statusBar, forceInCall: forceInCall))
|
|
}
|
|
}
|
|
return MappedStatusBarSurface(statusBars: statusBars, surface: surface)
|
|
}
|
|
|
|
private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface, forceInCall: Bool) -> MappedStatusBarSurface {
|
|
if surface.statusBars.count > 1 {
|
|
for i in 1 ..< surface.statusBars.count {
|
|
if (!forceInCall && surface.statusBars[i].style != surface.statusBars[i - 1].style) || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat.ulpOfOne {
|
|
return surface
|
|
}
|
|
if let lhsStatusBar = surface.statusBars[i - 1].statusBar, let rhsStatusBar = surface.statusBars[i].statusBar , !lhsStatusBar.alpha.isEqual(to: rhsStatusBar.alpha) {
|
|
return surface
|
|
}
|
|
}
|
|
let size = statusBarSize
|
|
return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: forceInCall ? .White : surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface)
|
|
} else {
|
|
return surface
|
|
}
|
|
}
|
|
|
|
private func displayHiddenAnimation() -> CAAnimation {
|
|
let animation = CABasicAnimation(keyPath: "transform.translation.y")
|
|
animation.fromValue = NSNumber(value: Float(-40.0))
|
|
animation.toValue = NSNumber(value: Float(-40.0))
|
|
animation.fillMode = kCAFillModeBoth
|
|
animation.duration = 1.0
|
|
animation.speed = 0.0
|
|
animation.isAdditive = true
|
|
animation.isRemovedOnCompletion = false
|
|
|
|
return animation
|
|
}
|
|
|
|
class StatusBarManager {
|
|
private var host: StatusBarHost
|
|
private let volumeControlStatusBar: VolumeControlStatusBar
|
|
private let volumeControlStatusBarNode: VolumeControlStatusBarNode
|
|
|
|
private var surfaces: [StatusBarSurface] = []
|
|
private var validParams: (withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool)?
|
|
|
|
var inCallNavigate: (() -> Void)?
|
|
|
|
private var volumeTimer: SwiftSignalKit.Timer?
|
|
|
|
init(host: StatusBarHost, volumeControlStatusBar: VolumeControlStatusBar, volumeControlStatusBarNode: VolumeControlStatusBarNode) {
|
|
self.host = host
|
|
self.volumeControlStatusBar = volumeControlStatusBar
|
|
self.volumeControlStatusBarNode = volumeControlStatusBarNode
|
|
self.volumeControlStatusBarNode.isHidden = true
|
|
|
|
self.volumeControlStatusBar.valueChanged = { [weak self] previous, updated in
|
|
if let strongSelf = self {
|
|
strongSelf.startVolumeTimer()
|
|
strongSelf.volumeControlStatusBarNode.updateValue(from: CGFloat(previous), to: CGFloat(updated))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startVolumeTimer() {
|
|
self.volumeTimer?.invalidate()
|
|
let timer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in
|
|
self?.endVolumeTimer()
|
|
}, queue: Queue.mainQueue())
|
|
self.volumeTimer = timer
|
|
timer.start()
|
|
if self.volumeControlStatusBarNode.isHidden {
|
|
self.volumeControlStatusBarNode.isHidden = false
|
|
self.volumeControlStatusBarNode.alpha = 1.0
|
|
self.volumeControlStatusBarNode.allowsGroupOpacity = true
|
|
self.volumeControlStatusBarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.18, completion: { [weak self] _ in
|
|
self?.volumeControlStatusBarNode.allowsGroupOpacity = false
|
|
})
|
|
}
|
|
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
|
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
|
}
|
|
}
|
|
|
|
private func endVolumeTimer() {
|
|
self.volumeControlStatusBarNode.alpha = 0.0
|
|
self.volumeControlStatusBarNode.allowsGroupOpacity = true
|
|
self.volumeControlStatusBarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { [weak self] completed in
|
|
if let strongSelf = self, completed {
|
|
strongSelf.volumeControlStatusBarNode.isHidden = true
|
|
strongSelf.volumeControlStatusBarNode.allowsGroupOpacity = false
|
|
}
|
|
})
|
|
self.volumeTimer = nil
|
|
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
|
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
|
}
|
|
}
|
|
|
|
func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) {
|
|
let previousSurfaces = self.surfaces
|
|
self.surfaces = surfaces
|
|
self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated, alphaTransition: .immediate)
|
|
}
|
|
|
|
private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool, alphaTransition: ContainedViewLayoutTransition) {
|
|
let statusBarFrame = self.host.statusBarFrame
|
|
guard let statusBarView = self.host.statusBarView else {
|
|
return
|
|
}
|
|
|
|
self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows)
|
|
|
|
if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) {
|
|
self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil)
|
|
}
|
|
|
|
var mappedSurfaces: [MappedStatusBarSurface] = []
|
|
var mapIndex = 0
|
|
var doNotOptimize = false
|
|
for surface in self.surfaces {
|
|
inner: for statusBar in surface.statusBars {
|
|
if statusBar.statusBarStyle == .Hide {
|
|
doNotOptimize = true
|
|
break inner
|
|
}
|
|
}
|
|
|
|
let mapped = mappedSurface(surface, forceInCall: forceInCallStatusBarText != nil)
|
|
|
|
if doNotOptimize {
|
|
mappedSurfaces.append(mapped)
|
|
} else {
|
|
mappedSurfaces.append(optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mapped, forceInCall: forceInCallStatusBarText != nil))
|
|
}
|
|
mapIndex += 1
|
|
}
|
|
|
|
var reduceSurfaces = true
|
|
var reduceSurfacesStatusBarStyleAndAlpha: (StatusBarStyle, CGFloat)?
|
|
var reduceIndex = 0
|
|
outer: for surface in mappedSurfaces {
|
|
for mappedStatusBar in surface.statusBars {
|
|
if reduceIndex == 0 && mappedStatusBar.style == .Hide {
|
|
reduceSurfaces = false
|
|
break outer
|
|
}
|
|
if mappedStatusBar.frame.origin.equalTo(CGPoint()) {
|
|
let statusBarAlpha = mappedStatusBar.statusBar?.alpha ?? 1.0
|
|
if let reduceSurfacesStatusBarStyleAndAlpha = reduceSurfacesStatusBarStyleAndAlpha {
|
|
if mappedStatusBar.style != reduceSurfacesStatusBarStyleAndAlpha.0 {
|
|
reduceSurfaces = false
|
|
break outer
|
|
}
|
|
if !statusBarAlpha.isEqual(to: reduceSurfacesStatusBarStyleAndAlpha.1) {
|
|
reduceSurfaces = false
|
|
break outer
|
|
}
|
|
} else {
|
|
reduceSurfacesStatusBarStyleAndAlpha = (mappedStatusBar.style, statusBarAlpha)
|
|
}
|
|
}
|
|
}
|
|
reduceIndex += 1
|
|
}
|
|
|
|
if reduceSurfaces {
|
|
outer: for surface in mappedSurfaces {
|
|
for mappedStatusBar in surface.statusBars {
|
|
if mappedStatusBar.frame.origin.equalTo(CGPoint()) {
|
|
if let statusBar = mappedStatusBar.statusBar , !statusBar.layer.hasPositionOrOpacityAnimations() {
|
|
mappedSurfaces = [MappedStatusBarSurface(statusBars: [mappedStatusBar], surface: surface.surface)]
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var visibleStatusBars: [StatusBar] = []
|
|
|
|
var globalStatusBar: (StatusBarStyle, CGFloat, CGFloat)?
|
|
|
|
var coveredIdentity = false
|
|
var statusBarIndex = 0
|
|
for i in 0 ..< mappedSurfaces.count {
|
|
for mappedStatusBar in mappedSurfaces[i].statusBars {
|
|
if let statusBar = mappedStatusBar.statusBar {
|
|
if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() && !statusBar.offsetNode.layer.hasPositionAnimations() {
|
|
if !coveredIdentity {
|
|
if statusBar.statusBarStyle != .Hide {
|
|
if statusBar.offsetNode.frame.origin.equalTo(CGPoint()) {
|
|
coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha)
|
|
}
|
|
if statusBarIndex == 0 && globalStatusBar == nil {
|
|
globalStatusBar = (mappedStatusBar.style, statusBar.alpha, statusBar.offsetNode.frame.origin.y)
|
|
} else {
|
|
visibleStatusBars.append(statusBar)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
visibleStatusBars.append(statusBar)
|
|
}
|
|
} else {
|
|
if !coveredIdentity {
|
|
coveredIdentity = true
|
|
if statusBarIndex == 0 && globalStatusBar == nil {
|
|
globalStatusBar = (mappedStatusBar.style, 1.0, 0.0)
|
|
}
|
|
}
|
|
}
|
|
statusBarIndex += 1
|
|
}
|
|
}
|
|
|
|
for surface in previousSurfaces {
|
|
for statusBar in surface.statusBars {
|
|
if !visibleStatusBars.contains(where: {$0 === statusBar}) {
|
|
statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated)
|
|
}
|
|
}
|
|
}
|
|
|
|
for surface in self.surfaces {
|
|
for statusBar in surface.statusBars {
|
|
statusBar.inCallNavigate = self.inCallNavigate
|
|
if !visibleStatusBars.contains(where: {$0 === statusBar}) {
|
|
statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated)
|
|
}
|
|
}
|
|
}
|
|
|
|
for statusBar in visibleStatusBars {
|
|
statusBar.updateState(statusBar: statusBarView, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated)
|
|
}
|
|
|
|
if self.volumeTimer != nil {
|
|
globalStatusBar?.1 = 0.0
|
|
}
|
|
self.volumeControlStatusBarNode.isDark = globalStatusBar?.0.systemStyle == UIStatusBarStyle.lightContent
|
|
|
|
if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows {
|
|
let statusBarStyle: UIStatusBarStyle
|
|
if forceInCallStatusBarText != nil {
|
|
statusBarStyle = .lightContent
|
|
} else {
|
|
statusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent
|
|
}
|
|
if self.host.statusBarStyle != statusBarStyle {
|
|
self.host.statusBarStyle = statusBarStyle
|
|
}
|
|
if let statusBarWindow = self.host.statusBarWindow {
|
|
alphaTransition.updateAlpha(layer: statusBarView.layer, alpha: globalStatusBar.1)
|
|
var statusBarBounds = statusBarWindow.bounds
|
|
if !statusBarBounds.origin.y.isEqual(to: globalStatusBar.2) {
|
|
statusBarBounds.origin.y = globalStatusBar.2
|
|
statusBarWindow.bounds = statusBarBounds
|
|
}
|
|
}
|
|
} else {
|
|
statusBarView.alpha = 0.0
|
|
}
|
|
}
|
|
}
|