diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index b7601ab3db..cb73129ceb 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -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: diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index d32af0563a..4665bac49b 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -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 diff --git a/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift b/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift index 436bb7a054..971e8b0d6b 100644 --- a/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift @@ -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, 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 } }