Implementing sheet full expansion

This commit is contained in:
Ilya Yelagov
2022-12-06 23:44:06 +04:00
parent 81ee64d2ec
commit 96876f1611
3 changed files with 128 additions and 34 deletions

View File

@@ -617,6 +617,25 @@ public struct Transition {
}
}
public func animateCornerRadius(layer: CALayer, from fromValue: CGFloat, to toValue: CGFloat) {
switch self.animation {
case .none:
break
case let .curve(duration, curve):
layer.animate(
from: fromValue as NSNumber,
to: toValue as NSNumber,
keyPath: "cornerRadius",
duration: duration,
delay: 0.0,
curve: curve,
removeOnCompletion: true,
additive: false,
completion: nil
)
}
}
public func animateBoundsOrigin(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self.animation {
case .none:

View File

@@ -736,6 +736,7 @@ public final class _MediaStreamComponent: CombinedComponent {
private(set) var displayUI: Bool = true
var dismissOffset: CGFloat = 0.0
var initialOffset: CGFloat = 0.0
// TODO: remove (replaced by isFullscreen)
var storedIsLandscape: Bool?
var isFullscreen: Bool = false
@@ -976,6 +977,7 @@ public final class _MediaStreamComponent: CombinedComponent {
}
var isFullscreen = state.isFullscreen
let isLandscape = context.availableSize.width > context.availableSize.height
if let videoSize = context.state.videoSize {
// Always fullscreen in landscape
if /*videoSize.width > videoSize.height &&*/ isLandscape && !isFullscreen {
@@ -987,6 +989,16 @@ public final class _MediaStreamComponent: CombinedComponent {
}
}
let videoHeight: CGFloat = context.availableSize.width / 16 * 9
let bottomPadding = 40 + environment.safeInsets.bottom
let sheetHeight: CGFloat = isFullscreen ? context.availableSize.height : (44 + videoHeight + 40 + 69 + 16 + 32 + 70 + bottomPadding)
let isFullyDragged = context.availableSize.height - sheetHeight + state.dismissOffset - context.view.safeAreaInsets.top < 30
var dragOffset = context.state.dismissOffset
if isFullyDragged {
dragOffset = max(context.state.dismissOffset, sheetHeight - context.availableSize.height + context.view.safeAreaInsets.top)// sheetHeight - UIScreen.main.bounds.height
}
let video = video.update(
component: MediaStreamVideoComponent(
call: context.component.call,
@@ -1324,12 +1336,8 @@ public final class _MediaStreamComponent: CombinedComponent {
subtitle: memberCountString
))
}
let videoHeight: CGFloat = context.availableSize.width / 16 * 9
let bottomPadding = 40 + environment.safeInsets.bottom
let sheetHeight: CGFloat = isFullscreen ? context.availableSize.height : (44 + videoHeight + 40 + 69 + 16 + 32 + 70 + bottomPadding)
let isFullyDragged = context.availableSize.height - sheetHeight + state.dismissOffset < 30
let availableSize = context.availableSize
let safeAreaTop = context.view.safeAreaInsets.top
context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
.gesture(.tap { [weak state] in
@@ -1344,9 +1352,9 @@ public final class _MediaStreamComponent: CombinedComponent {
}
switch panState {
case .began:
break
state.initialOffset = state.dismissOffset
case let .updated(offset):
state.updateDismissOffset(value: offset.y, interactive: true)
state.updateDismissOffset(value: state.initialOffset + offset.y, interactive: true)
case let .ended(velocity):
// TODO: Dismiss sheet depending on velocity
if velocity.y > 200.0 {
@@ -1357,7 +1365,11 @@ public final class _MediaStreamComponent: CombinedComponent {
controller.updateOrientation(orientation: .portrait)
}
} else {
let _ = call.leave(terminateIfPossible: false)
if isFullyDragged || state.initialOffset != 0 {
state.updateDismissOffset(value: 0.0, interactive: false)
} else {
let _ = call.leave(terminateIfPossible: false)
}
}
/*activatePictureInPicture.invoke(Action { [weak state] in
guard let state = state, let controller = controller() as? MediaStreamComponentController else {
@@ -1367,7 +1379,16 @@ public final class _MediaStreamComponent: CombinedComponent {
controller.dismiss(closing: false, manual: true)
})*/
} else {
state.updateDismissOffset(value: 0.0, interactive: false)
if isFullyDragged {
state.updateDismissOffset(value: sheetHeight - availableSize.height + safeAreaTop, interactive: false)
} else {
if velocity.y < -200 {
// Expand
state.updateDismissOffset(value: sheetHeight - availableSize.height + safeAreaTop, interactive: false)
} else {
state.updateDismissOffset(value: 0.0, interactive: false)
}
}
}
}
})
@@ -1483,19 +1504,21 @@ public final class _MediaStreamComponent: CombinedComponent {
component: StreamSheetComponent(
topComponent: AnyComponent(navigationComponent),
bottomButtonsRow: bottomComponent,
topOffset: context.availableSize.height - sheetHeight + context.state.dismissOffset,
sheetHeight: max(sheetHeight - context.state.dismissOffset, sheetHeight),
topOffset: context.availableSize.height - sheetHeight + dragOffset,
sheetHeight: max(sheetHeight - dragOffset, sheetHeight),
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
bottomPadding: bottomPadding,
participantsCount: context.state.originInfo?.memberCount ?? 0 // Int.random(in: 0...999998)// [0, 5, 15, 16, 95, 100, 16042, 942539].randomElement()!
participantsCount: context.state.originInfo?.memberCount ?? 0, // Int.random(in: 0...999998)// [0, 5, 15, 16, 95, 100, 16042, 942539].randomElement()!
//
isFullyExtended: isFullyDragged,
deviceCornerRadius: deviceCornerRadius ?? 0
),
availableSize: context.availableSize,
transition: context.transition
)
// TODO: calculate (although not necessary currently)
let sheetOffset: CGFloat = context.availableSize.height - sheetHeight + context.state.dismissOffset
let sheetOffset: CGFloat = context.availableSize.height - sheetHeight + dragOffset
let sheetPosition = sheetOffset + sheetHeight / 2
// Sheet underneath the video when in sheet
// if !isFullscreen {
@@ -1507,7 +1530,7 @@ public final class _MediaStreamComponent: CombinedComponent {
let videoPos: CGFloat
if isFullscreen {
videoPos = context.availableSize.height / 2 + state.dismissOffset
videoPos = context.availableSize.height / 2 + dragOffset
} else {
videoPos = sheetPosition - sheetHeight / 2 + videoHeight / 2 + 50
}
@@ -1516,7 +1539,7 @@ public final class _MediaStreamComponent: CombinedComponent {
)
} else {
context.add(video
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2 + state.dismissOffset)
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2 + dragOffset)
))
}
@@ -1571,7 +1594,9 @@ public final class _MediaStreamComponent: CombinedComponent {
sheetHeight: max(sheetHeight - context.state.dismissOffset, sheetHeight),
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
bottomPadding: 12,
participantsCount: -1 // context.state.originInfo?.memberCount ?? 0
participantsCount: -1, // context.state.originInfo?.memberCount ?? 0
isFullyExtended: isFullyDragged,
deviceCornerRadius: deviceCornerRadius ?? 0
),
availableSize: context.availableSize,
transition: context.transition
@@ -1597,6 +1622,9 @@ public final class _MediaStreamComponent: CombinedComponent {
}
}
// TODO: pass to component properly
var deviceCornerRadius: CGFloat? = nil
public final class _MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController {
private let context: AccountContext
public let call: PresentationGroupCall
@@ -1642,12 +1670,15 @@ public final class _MediaStreamComponentController: ViewControllerComponentConta
view.expandFromPictureInPicture()
}
if let _ = self.validLayout {
if let validLayout = self.validLayout {
self.view.clipsToBounds = true
// TODO: pass to component properly
deviceCornerRadius = validLayout.deviceMetrics.screenCornerRadius - 1// 0.5
// self.view.layer.cornerRadius = validLayout.deviceMetrics.screenCornerRadius
if #available(iOS 13.0, *) {
self.view.layer.cornerCurve = .continuous
}
// if #available(iOS 13.0, *) {
// self.view.layer.cornerCurve = .continuous
// }
self.view.layer.animatePosition(from: CGPoint(x: self.view.frame.center.x, y: self.view.bounds.maxY + self.view.bounds.height / 2), to: self.view.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in // [weak self] _ in
// self?.view.layer.cornerRadius = 0.0

View File

@@ -19,6 +19,8 @@ final class StreamSheetComponent: CombinedComponent {
let backgroundColor: UIColor
let participantsCount: Int
let bottomPadding: CGFloat
let isFullyExtended: Bool
let deviceCornerRadius: CGFloat
init(
// color: UIColor,
@@ -28,7 +30,9 @@ final class StreamSheetComponent: CombinedComponent {
sheetHeight: CGFloat,
backgroundColor: UIColor,
bottomPadding: CGFloat,
participantsCount: Int
participantsCount: Int,
isFullyExtended: Bool,
deviceCornerRadius: CGFloat
) {
// self.leftItem = leftItem
self.topComponent = topComponent
@@ -39,6 +43,8 @@ final class StreamSheetComponent: CombinedComponent {
self.backgroundColor = backgroundColor
self.bottomPadding = bottomPadding
self.participantsCount = participantsCount
self.isFullyExtended = isFullyExtended
self.deviceCornerRadius = deviceCornerRadius
}
static func ==(lhs: StreamSheetComponent, rhs: StreamSheetComponent) -> Bool {
@@ -66,6 +72,9 @@ final class StreamSheetComponent: CombinedComponent {
if lhs.participantsCount != rhs.participantsCount {
return false
}
if lhs.isFullyExtended != rhs.isFullyExtended {
return false
}
return true
}
//
@@ -80,7 +89,7 @@ final class StreamSheetComponent: CombinedComponent {
}
func update(component: StreamSheetComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
self.backgroundColor = .purple.withAlphaComponent(0.6)
// self.backgroundColor = .purple.withAlphaComponent(0.6)
return availableSize
}
@@ -132,11 +141,21 @@ final class StreamSheetComponent: CombinedComponent {
return { context in
let availableWidth = context.availableSize.width
// let sideInset: CGFloat = 16.0 + context.component.sideInset
let contentHeight: CGFloat = 44.0
let size = context.availableSize// CGSize(width: context.availableSize.width, height:44)// context.component.topInset + contentHeight)
let background = background.update(component: SheetBackgroundComponent(color: context.component.backgroundColor), availableSize: CGSize(width: size.width, height: context.component.sheetHeight), transition: context.transition)
let topOffset = context.component.topOffset
let backgroundExtraOffset = context.component.isFullyExtended ? -context.view.safeAreaInsets.top : 0
let background = background.update(
component: SheetBackgroundComponent(
color: context.component.backgroundColor,
radius: context.component.isFullyExtended ? context.component.deviceCornerRadius : 16,
offset: backgroundExtraOffset
),
availableSize: CGSize(width: size.width, height: context.component.sheetHeight),
transition: context.transition
)
let topItem = context.component.topComponent.flatMap { topItemComponent in
return topItem.update(
@@ -160,10 +179,9 @@ final class StreamSheetComponent: CombinedComponent {
)
}
let topOffset = context.component.topOffset
context.add(background
.position(CGPoint(x: size.width / 2.0, y: context.component.topOffset + context.component.sheetHeight / 2))
.position(CGPoint(x: size.width / 2.0, y: topOffset + context.component.sheetHeight / 2))
// .position(CGPoint(x: size.width / 2.0, y: context.component.topOffset + context.component.sheetHeight / 2 + backgroundExtraOffset))
)
(context.view as? StreamSheetComponent.View)?.overlayComponentsFrames = []
@@ -208,27 +226,45 @@ private let latePink = UIColor(rgb: 0xf0436c)
final class SheetBackgroundComponent: Component {
private let color: UIColor
private let radius: CGFloat
private let offset: CGFloat
class View: UIView {
private let backgroundView = UIView()
func update(availableSize: CGSize, color: UIColor, transition: Transition) {
func update(availableSize: CGSize, color: UIColor, cornerRadius: CGFloat, offset: CGFloat, transition: Transition) {
if backgroundView.superview == nil {
self.addSubview(backgroundView)
}
// To fix release animation
let extraBottom: CGFloat = 500
backgroundView.frame = .init(origin: .zero, size: .init(width: availableSize.width, height: availableSize.height + extraBottom))
if backgroundView.backgroundColor != color {
if backgroundView.backgroundColor != color && backgroundView.backgroundColor != nil {
// let initialVelocity: CGFloat = 0
// let xtransition = ComponentFlow.Transition(animation: .curve(duration: 0.45, curve: .spring))// .animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
UIView.animate(withDuration: 0.4) { [self] in
backgroundView.backgroundColor = color
// TODO: determine if animation is needed (with facts and logic, not color)
backgroundView.frame = .init(origin: .init(x: 0, y: offset), size: .init(width: availableSize.width, height: availableSize.height + extraBottom))
}
let anim = CABasicAnimation(keyPath: "cornerRadius")
anim.fromValue = backgroundView.layer.cornerRadius
backgroundView.layer.cornerRadius = cornerRadius
anim.toValue = cornerRadius
anim.duration = 0.4
backgroundView.layer.add(anim, forKey: "cornerRadius")
} else {
backgroundView.backgroundColor = color
backgroundView.frame = .init(origin: .init(x: 0, y: offset), size: .init(width: availableSize.width, height: availableSize.height + extraBottom))
backgroundView.layer.cornerRadius = cornerRadius
}
backgroundView.isUserInteractionEnabled = false
backgroundView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
backgroundView.layer.cornerRadius = 16
// let currentRadius = backgroundView.layer.cornerRadius
// backgroundView.layer.cornerRadius = cornerRadius
// transition.animateCornerRadius(layer: backgroundView.layer, from: currentRadius, to: cornerRadius)
backgroundView.clipsToBounds = true
backgroundView.layer.masksToBounds = true
}
@@ -242,6 +278,12 @@ final class SheetBackgroundComponent: Component {
if !lhs.color.isEqual(rhs.color) {
return false
}
if lhs.radius != rhs.radius {
return false
}
if lhs.offset != rhs.offset {
return false
}
// if lhs.width != rhs.width {
// return false
// }
@@ -251,12 +293,14 @@ final class SheetBackgroundComponent: Component {
return true
}
public init(color: UIColor) {
public init(color: UIColor, radius: CGFloat, offset: CGFloat) {
self.color = color
self.radius = radius
self.offset = offset
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
view.update(availableSize: availableSize, color: color, transition: transition)
view.update(availableSize: availableSize, color: color, cornerRadius: radius, offset: offset, transition: transition)
return availableSize
}
}