Adjusting layout and design, fixing fullscreen PiP and other tweaks

This commit is contained in:
Ilya Yelagov
2023-01-06 00:14:38 +04:00
parent c8ab1b8971
commit f1108af238
4 changed files with 222 additions and 89 deletions

View File

@@ -21,7 +21,7 @@ public final class AnimatedCountView: UIView {
super.init(frame: frame)
self.foregroundGradientLayer.type = .radial
self.foregroundGradientLayer.colors = [pink.cgColor, purple.cgColor, purple.cgColor]
// self.foregroundGradientLayer.colors = [pink.cgColor, purple.cgColor, purple.cgColor]
self.foregroundGradientLayer.locations = [0.0, 0.85, 1.0]
self.foregroundGradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
self.foregroundGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
@@ -46,18 +46,34 @@ public final class AnimatedCountView: UIView {
self.foregroundView.frame = CGRect(origin: CGPoint.zero, size: bounds.size)// .insetBy(dx: -40, dy: -40)
self.foregroundGradientLayer.frame = CGRect(origin: .zero, size: bounds.size).insetBy(dx: -60, dy: -60)
self.maskingView.frame = CGRect(origin: .zero, size: bounds.size)
countLabel.frame = CGRect(origin: .zero, size: bounds.size)
subtitleLabel.frame = .init(x: bounds.midX - subtitleLabel.intrinsicContentSize.width / 2 - 10, y: subtitleLabel.text == "No viewers" ? bounds.midY - 8 : bounds.height - 12, width: subtitleLabel.intrinsicContentSize.width + 20, height: 20)
let subtitleHeight: CGFloat = subtitleLabel.intrinsicContentSize.height// 18
// let counterInset: CGFloat = 8
// let counterBottomOffset: CGFloat = subtitleHeight + counterInset
countLabel.frame = CGRect(origin: .zero, size: CGSize(width: bounds.width, height: bounds.height))
subtitleLabel.frame = .init(x: bounds.midX - subtitleLabel.intrinsicContentSize.width / 2 - 10, y: subtitleLabel.text == "No viewers" ? bounds.midY - subtitleHeight / 2 : bounds.height - subtitleHeight, width: subtitleLabel.intrinsicContentSize.width + 20, height: subtitleHeight)
// backgroundColor = .white.withAlphaComponent(0.3)
// countLabel.backgroundColor = .red.withAlphaComponent(0.2)
// subtitleLabel.backgroundColor = .blue.withAlphaComponent(0.2)
}
func update(countString: String, subtitle: String, fontSize: CGFloat = 48.0) {
func update(countString: String, subtitle: String, fontSize: CGFloat = 48.0, gradientColors: [CGColor] = [pink.cgColor, purple.cgColor, purple.cgColor]) {
self.setupGradientAnimations()
let backgroundGradientColors: [CGColor]
if gradientColors.count == 1 {
backgroundGradientColors = [gradientColors[0], gradientColors[0]]
} else {
backgroundGradientColors = gradientColors
}
self.foregroundGradientLayer.colors = backgroundGradientColors
let text: String = countString
self.countLabel.fontSize = fontSize
self.countLabel.attributedText = NSAttributedString(string: text, font: Font.with(size: fontSize, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, attributes: [.font: UIFont.systemFont(ofSize: max(floor(fontSize / 3), 12), weight: .semibold)])
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, attributes: [.font: UIFont.systemFont(ofSize: max(floor((fontSize + 4.0) / 3.0), 12.0), weight: .semibold)])
self.subtitleLabel.isHidden = subtitle.isEmpty
}
@@ -152,7 +168,7 @@ class AnimatedCountLabel: UILabel {
private let containerView = UIView()
var itemWidth: CGFloat { 36 * fontSize / 60 }
var commaWidthForSpacing: CGFloat { 8 * fontSize / 60 }
var commaWidthForSpacing: CGFloat { 12 * fontSize / 60 }
var commaFrameWidth: CGFloat { 36 * fontSize / 60 }
var interItemSpacing: CGFloat { 0 * fontSize / 60 }
var didBegin = false
@@ -180,9 +196,9 @@ class AnimatedCountLabel: UILabel {
}
if characters.count > index && characters[index].string == "," {
if index > 0, ["1", "7"].contains(characters[index - 1].string) {
offset -= commaWidthForSpacing * 0.7
offset -= commaWidthForSpacing * 0.5
} else {
offset -= commaWidthForSpacing / 3
offset -= commaWidthForSpacing / 6// 3
}
}
return offset
@@ -199,6 +215,7 @@ class AnimatedCountLabel: UILabel {
let offset = offsetForChar(at: index)
char.frame.origin.x = offset
char.frame.origin.y = 0
char.frame.size.height = containerView.bounds.height
}
}

View File

@@ -58,6 +58,7 @@ public final class MediaStreamComponent: CombinedComponent {
var isFullscreen: Bool = false
var videoSize: CGSize?
var prevFullscreenOrientation: UIDeviceOrientation?
var didAutoDismissForPiP: Bool = false
private(set) var canManageCall: Bool = false
// TODO: also handle pictureInPicturePossible
@@ -169,11 +170,11 @@ public final class MediaStreamComponent: CombinedComponent {
var updated = false
// TODO: remove debug timer
// Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
var shouldReplaceNoViewersWithOne: Bool { true }
strongSelf.infoThrottler.publish(shouldReplaceNoViewersWithOne ? max(members.totalCount, 1) : members.totalCount /*Int.random(in: 0..<10000000)*/) { [weak strongSelf] latestCount in
// let _ = members.totalCount
let membersCount = Int.random(in: 0..<10000000) // members.totalCount
strongSelf.infoThrottler.publish(shouldReplaceNoViewersWithOne ? max(membersCount, 1) : membersCount) { [weak strongSelf] latestCount in
let _ = members.totalCount
guard let strongSelf = strongSelf else { return }
var updated = false
let originInfo = OriginInfo(title: callPeer.debugDisplayTitle, memberCount: latestCount)
@@ -185,7 +186,7 @@ public final class MediaStreamComponent: CombinedComponent {
strongSelf.updated(transition: .immediate)
}
}
// }.fire()
}.fire()
if state.canManageCall != strongSelf.canManageCall {
strongSelf.canManageCall = state.canManageCall
updated = true
@@ -228,6 +229,8 @@ public final class MediaStreamComponent: CombinedComponent {
strongSelf.deactivatePictureInPictureIfVisible.invoke(Void())
})
} else {
// MARK: TODO: fullscreen ui toggle
}
}
})
@@ -318,6 +321,11 @@ public final class MediaStreamComponent: CombinedComponent {
return
}
if controller.view.window == nil {
if state.didAutoDismissForPiP {
state.updated(transition: .easeInOut(duration: 3))
deactivatePictureInPicture.invoke(Void())
// call.accountContext.sharedContext.mainWindow?.inCallNavigate?()
}
return
}
state.updated(transition: .easeInOut(duration: 3))
@@ -349,10 +357,10 @@ public final class MediaStreamComponent: CombinedComponent {
let videoHeight: CGFloat = forceFullScreenInLandscape
? (context.availableSize.width - videoInset * 2) / 16 * 9
: context.state.videoSize?.height ?? (min(context.availableSize.width, context.availableSize.height) - videoInset * 2) / 16 * 9
let bottomPadding = 40 + environment.safeInsets.bottom
let bottomPadding = 32.0 + environment.safeInsets.bottom
let requiredSheetHeight: CGFloat = isFullscreen
? context.availableSize.height
: (44 + videoHeight + 40 + 69 + 16 + 32 + 70 + bottomPadding)
: (44 + videoHeight + 40 + 69 + 16 + 32 + 70 + bottomPadding + 8)
let safeAreaTopInView: CGFloat
if #available(iOS 16.0, *) {
@@ -374,7 +382,8 @@ public final class MediaStreamComponent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width, height: dismissTapAreaHeight),
transition: context.transition
)
// (controller() as? MediaStreamComponentController)?.prefersOnScreenNavigationHidden = isFullscreen
// (controller() as? MediaStreamComponentController)?.window?.invalidatePrefersOnScreenNavigationHidden()
let video = video.update(
component: MediaStreamVideoComponent(
call: context.component.call,
@@ -432,12 +441,17 @@ public final class MediaStreamComponent: CombinedComponent {
)))
]
)),
action: {
action: { [weak state] in
guard let state, state.videoIsPlayable else { return }
activatePictureInPicture.invoke(Action {
guard let controller = controller() as? MediaStreamComponentController else {
return
}
controller.dismiss(closing: false, manual: true)
if state.displayUI {
state.toggleDisplayUI()
}
})
}
).minSize(CGSize(width: 44.0, height: 44.0)))))
@@ -624,8 +638,8 @@ public final class MediaStreamComponent: CombinedComponent {
let alertController = textAlertController(
context: call.accountContext,
forceTheme: defaultDarkPresentationTheme,
title: nil,
text: presentationData.strings.VoiceChat_StopRecordingTitle,
title: presentationData.strings.LiveStream_EndConfirmationTitle,
text: presentationData.strings.LiveStream_EndConfirmationText,
actions: [
TextAlertAction(
type: .genericAction,
@@ -633,8 +647,8 @@ public final class MediaStreamComponent: CombinedComponent {
action: {}
),
TextAlertAction(
type: .defaultAction,
title: presentationData.strings.VoiceChat_StopRecordingStop,
type: .destructiveAction,
title: presentationData.strings.VoiceChat_EndConfirmationEnd,
action: { [weak call] in
guard let call = call else {
return
@@ -754,6 +768,9 @@ public final class MediaStreamComponent: CombinedComponent {
return
}
controller.dismiss(closing: false, manual: true)
if state.displayUI {
state.toggleDisplayUI()
}
})
} else {
guard let controller = controller() as? MediaStreamComponentController else {
@@ -811,7 +828,11 @@ public final class MediaStreamComponent: CombinedComponent {
sideInset: environment.safeInsets.left,
leftItem: AnyComponent(Button(
content: AnyComponent(RoundGradientButtonComponent(// BundleIconComponent(
gradientColors: [UIColor(red: 0.18, green: 0.17, blue: 0.30, alpha: 1).cgColor, UIColor(red: 0.17, green: 0.16, blue: 0.30, alpha: 1).cgColor],
gradientColors: [
UIColor(red: 0.165, green: 0.173, blue: 0.357, alpha: 1).cgColor
// UIColor(red: 0.18, green: 0.17, blue: 0.30, alpha: 1).cgColor,
// UIColor(red: 0.17, green: 0.16, blue: 0.30, alpha: 1).cgColor
],
image: generateTintedImage(image: UIImage(bundleImageName: "Call/CallShareButton"), color: .white),
// TODO: localize:
title: "share")),
@@ -824,7 +845,11 @@ public final class MediaStreamComponent: CombinedComponent {
).minSize(CGSize(width: 65, height: 80))),
rightItem: AnyComponent(Button(
content: AnyComponent(RoundGradientButtonComponent(
gradientColors: [UIColor(red: 0.44, green: 0.18, blue: 0.22, alpha: 1).cgColor, UIColor(red: 0.44, green: 0.18, blue: 0.22, alpha: 1).cgColor],
gradientColors: [
UIColor(red: 0.314, green: 0.161, blue: 0.197, alpha: 1).cgColor
// UIColor(red: 0.44, green: 0.18, blue: 0.22, alpha: 1).cgColor,
// UIColor(red: 0.44, green: 0.18, blue: 0.22, alpha: 1).cgColor
],
image: generateImage(CGSize(width: 44.0 * imageRenderScale, height: 44 * imageRenderScale), opaque: false, rotatedContext: { size, context in
context.translateBy(x: size.width / 2, y: size.height / 2)
context.scaleBy(x: 0.4, y: 0.4)
@@ -853,7 +878,11 @@ public final class MediaStreamComponent: CombinedComponent {
).minSize(CGSize(width: 44.0, height: 44.0))),
centerItem: AnyComponent(Button(
content: AnyComponent(RoundGradientButtonComponent(
gradientColors: [UIColor(red: 0.23, green: 0.17, blue: 0.29, alpha: 1).cgColor, UIColor(red: 0.21, green: 0.16, blue: 0.29, alpha: 1).cgColor],
gradientColors: [
UIColor(red: 0.165, green: 0.173, blue: 0.357, alpha: 1).cgColor
// UIColor(red: 0.23, green: 0.17, blue: 0.29, alpha: 1).cgColor,
// UIColor(red: 0.21, green: 0.16, blue: 0.29, alpha: 1).cgColor
],
image: generateImage(CGSize(width: 44 * imageRenderScale, height: 44 * imageRenderScale), opaque: false, rotatedContext: { size, context in
let imageColor = UIColor.white
@@ -930,7 +959,7 @@ public final class MediaStreamComponent: CombinedComponent {
bottomButtonsRow: bottomComponent,
topOffset: topOffset,
sheetHeight: sheetHeight,
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
backgroundColor: (isFullscreen && !state.hasVideo) ? .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()!
isFullyExtended: isFullyDragged,
@@ -944,22 +973,12 @@ public final class MediaStreamComponent: CombinedComponent {
transition: context.transition
)
let sheetOffset: CGFloat = context.availableSize.height - requiredSheetHeight + dragOffset
let sheetPosition = sheetOffset + requiredSheetHeight / 2
// let sheetOffset: CGFloat = context.availableSize.height - requiredSheetHeight + dragOffset
// let sheetPosition = sheetOffset + requiredSheetHeight / 2
// Sheet underneath the video when in modal sheet
context.add(sheet
.position(.init(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2))
)
let videoPos: CGFloat
if isFullscreen {
videoPos = context.availableSize.height / 2 + dragOffset
} else {
videoPos = sheetPosition - requiredSheetHeight / 2 + videoHeight / 2 + 50 + 12
}
context.add(video
.position(CGPoint(x: context.availableSize.width / 2.0, y: videoPos))
)
//
//
@@ -1022,9 +1041,23 @@ public final class MediaStreamComponent: CombinedComponent {
transition: context.transition
)
let videoPos: CGFloat
if isFullscreen {
videoPos = context.availableSize.height / 2 + dragOffset
} else {
videoPos = /*sheetPosition - requiredSheetHeight / 2*/topOffset + 28.0 + 28.0 + videoHeight / 2 // + 50 + 12
}
context.add(video
.position(CGPoint(x: context.availableSize.width / 2.0, y: videoPos))
)
context.add(topItem
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + (isFullscreen ? topItem.size.height / 2.0 : 32)))
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + (isFullscreen ? topItem.size.height / 2.0 : 28.0)))
.opacity((!isFullscreen || state.displayUI) ? 1 : 0)
.gesture(.pan { panState in
onPanGesture(panState)
})
// .animation(key: "position")
)
@@ -1043,7 +1076,7 @@ public final class MediaStreamComponent: CombinedComponent {
//
//
} else {
let fullScreenToolbarComponent = AnyComponent(ToolbarComponent(
/*let fullScreenToolbarComponent = AnyComponent(ToolbarComponent(
bottomInset: environment.safeInsets.bottom,
sideInset: environment.safeInsets.left,
leftItem: AnyComponent(Button(
@@ -1104,9 +1137,18 @@ public final class MediaStreamComponent: CombinedComponent {
context.add(video
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2 + dragOffset)
))
))*/
}
// TODO: add variable isPictureInPictureActive
// let isPictureInPictureActive = state.isPictureInPictureSupported && state.videoIsPlayable && state.hasVideo
// if !state.isVisibleInHierarchy && isPictureInPictureActive && state.isFullscreen {
// if !state.didAutoDismissForPiP {
// state.didAutoDismissForPiP = true
// (controller() as? MediaStreamComponentController)?.dismiss(closing: false, manual: true)
// }
// } else {
// state.didAutoDismissForPiP = false
// }
return context.availableSize
}
}
@@ -1155,7 +1197,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
self.view.clipsToBounds = true
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
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.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
})
self.view.layer.allowsGroupOpacity = true
@@ -1376,19 +1418,35 @@ final class StreamTitleComponent: Component {
private let stalledAnimatedGradient = CAGradientLayer()
private var wasLive = false
var desiredWidth: CGFloat { label.intrinsicContentSize.width + 6.0 + 6.0 }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
addSubview(label)
label.text = "LIVE"
label.font = .systemFont(ofSize: 12, weight: .semibold)
label.textAlignment = .center
label.textColor = .white
let liveString = NSAttributedString(
string: "LIVE",
attributes: [
.font: Font.with(size: 11.0, design: .round, weight: .bold),
.paragraphStyle: {
let style = NSMutableParagraphStyle()
style.alignment = .center
return style
}(),
.foregroundColor: UIColor.white,
.kern: -0.6
]
)
label.attributedText = liveString
// label.text = "LIVE"
// label.font = Font.with(size: 11.0, design: .round, weight: .bold)// .systemFont(ofSize: 12, weight: .semibold)
// label.textAlignment = .center
// label.textColor = .white
layer.addSublayer(stalledAnimatedGradient)
self.clipsToBounds = true
if #available(iOS 13.0, *) {
self.layer.cornerCurve = .continuous
}
// if #available(iOS 13.0, *) {
// self.layer.cornerCurve = .continuous
// }
toggle(isLive: false)
}
@@ -1549,7 +1607,9 @@ final class StreamTitleComponent: Component {
}
func update(component: StreamTitleComponent, availableSize: CGSize, transition: Transition) -> CGSize {
let liveIndicatorWidth: CGFloat = 40
let liveIndicatorWidth: CGFloat = self.liveIndicatorView.desiredWidth
let liveIndicatorHeight: CGFloat = 20.0
let currentText = self.titleLabel.text
if currentText != component.text {
if currentText?.isEmpty == false {
@@ -1605,7 +1665,7 @@ final class StreamTitleComponent: Component {
self.updateTitleFadeLayer(textFrame: textFrame)
}
liveIndicatorView.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 6.0, y: /*floorToScreenPixels((size.height - textSize.height) / 2.0 - 2) + 1.0*/textFrame.midY - 22 / 2), size: .init(width: 40, height: 22))
liveIndicatorView.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 6.0, y: /*floorToScreenPixels((size.height - textSize.height) / 2.0 - 2) + 1.0*/textFrame.midY - liveIndicatorHeight / 2), size: .init(width: liveIndicatorWidth, height: liveIndicatorHeight))
self.liveIndicatorView.toggle(isLive: component.isActive)
if let indicatorView = self.indicatorView, let image = indicatorView.image {
@@ -1786,18 +1846,19 @@ private final class OriginInfoComponent: CombinedComponent {
component: ParticipantsComponent(
count: context.component.participantsCount,
showsSubtitle: true,
fontSize: 18.0
fontSize: 18.0,
gradientColors: [UIColor.white.cgColor]
),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: context.transition
)
var size = CGSize(width: viewerCounter.size.width, height: viewerCounter.size.height)
let heightReduction: CGFloat = 16.0
var size = CGSize(width: viewerCounter.size.width, height: viewerCounter.size.height - heightReduction)
size.width = min(size.width, context.availableSize.width)
size.height = min(size.height, context.availableSize.height)
context.add(viewerCounter
.position(CGPoint(x: size.width / 2.0, y: (context.availableSize.height - viewerCounter.size.height) / 2.0))
.position(CGPoint(x: size.width / 2.0, y: /*(context.availableSize.height - viewerCounter.size.height)*/context.availableSize.height / 2.0 + 16.0 - heightReduction / 2))
)
return size
@@ -2007,7 +2068,7 @@ private final class ButtonsRowComponent: CombinedComponent {
return { context in
var availableWidth = context.availableSize.width
let sideInset: CGFloat = 40 + context.component.sideInset
let sideInset: CGFloat = 48.0 + context.component.sideInset
let contentHeight: CGFloat = 80 // 44
let size = CGSize(width: context.availableSize.width, height: contentHeight + context.component.bottomInset)
@@ -2126,7 +2187,7 @@ final class RoundGradientButtonComponent: Component {
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.invalidateIntrinsicContentSize()
let heightForIcon = bounds.height - max(titleLabel.intrinsicContentSize.height, 12) - 6
let heightForIcon = bounds.height - max(round(titleLabel.intrinsicContentSize.height), 12) - 8.0
iconView.frame = .init(x: bounds.midX - heightForIcon / 2, y: 0, width: heightForIcon, height: heightForIcon)
gradientLayer.masksToBounds = true
gradientLayer.cornerRadius = min(iconView.frame.width, iconView.frame.height) / 2
@@ -2141,6 +2202,12 @@ final class RoundGradientButtonComponent: Component {
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
view.iconView.image = image ?? icon.flatMap { UIImage(bundleImageName: $0) }
let gradientColors: [CGColor]
if self.gradientColors.count == 1 {
gradientColors = [self.gradientColors[0], self.gradientColors[0]]
} else {
gradientColors = self.gradientColors
}
view.gradientLayer.colors = gradientColors
view.titleLabel.text = title
view.setNeedsLayout()

View File

@@ -131,7 +131,7 @@ final class MediaStreamVideoComponent: Component {
private var videoStalled = false {
didSet {
if videoStalled != oldValue {
self.updateVideoStalled(isStalled: self.videoStalled)
self.updateVideoStalled(isStalled: self.videoStalled, transition: nil)
// state?.updated()
}
}
@@ -181,14 +181,17 @@ final class MediaStreamVideoComponent: Component {
return false
}
var didPassExpandFromPiP = false
func expandFromPictureInPicture() {
didPassExpandFromPiP = true
if let pictureInPictureController = self.pictureInPictureController, pictureInPictureController.isPictureInPictureActive {
self.requestedExpansion = true
self.pictureInPictureController?.stopPictureInPicture()
}
}
private var isAnimating = false
private func updateVideoStalled(isStalled: Bool) {
private func updateVideoStalled(isStalled: Bool, transition: Transition?) {
if isStalled {
guard let component = self.component else { return }
@@ -229,14 +232,30 @@ final class MediaStreamVideoComponent: Component {
shimmerBorderLayer.cornerRadius = cornerRadius
shimmerBorderLayer.masksToBounds = true
shimmerBorderLayer.compositingFilter = "softLightBlendMode"
shimmerBorderLayer.frame = loadingBlurView.bounds
let borderMask = CAShapeLayer()
borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
shimmerBorderLayer.mask = borderMask
if let transition, shimmerBorderLayer.mask != nil {
let initialPath = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
borderMask.path = initialPath
transition.setFrame(layer: shimmerBorderLayer, frame: loadingBlurView.bounds)
let borderMaskPath = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
transition.setShapeLayerPath(layer: borderMask, path: borderMaskPath)
} else {
shimmerBorderLayer.frame = loadingBlurView.bounds
let borderMaskPath = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
borderMask.path = borderMaskPath
}
borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
borderMask.strokeColor = UIColor.white.withAlphaComponent(0.7).cgColor
borderMask.lineWidth = 3
borderMask.compositingFilter = "softLightBlendMode"
shimmerBorderLayer.mask = borderMask
borderShimmer = StandaloneShimmerEffect()
borderShimmer.layer = shimmerBorderLayer
@@ -281,9 +300,9 @@ final class MediaStreamVideoComponent: Component {
}
if !component.hasVideo || component.videoLoading || self.videoStalled {
updateVideoStalled(isStalled: true)
updateVideoStalled(isStalled: true, transition: transition)
} else {
updateVideoStalled(isStalled: false)
updateVideoStalled(isStalled: false, transition: transition)
}
if component.hasVideo, self.videoView == nil {
@@ -543,7 +562,14 @@ final class MediaStreamVideoComponent: Component {
}
let loadingBlurViewFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize)
videoFrameUpdateTransition.setFrame(view: loadingBlurView, frame: loadingBlurViewFrame)
// UIView.animate(withDuration: 0.5) {
// self.loadingBlurView.frame = loadingBlurViewFrame
// }
if loadingBlurView.frame == .zero {
loadingBlurView.frame = loadingBlurViewFrame
} else {
transition.setFrame(view: loadingBlurView, frame: loadingBlurViewFrame)
}
// loadingBlurView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize)
videoFrameUpdateTransition.setCornerRadius(layer: loadingBlurView.layer, cornerRadius: videoCornerRadius)
@@ -559,17 +585,28 @@ final class MediaStreamVideoComponent: Component {
// $0.frame = placeholderView.bounds
}
let initialShimmerBounds = shimmerBorderLayer.bounds
videoFrameUpdateTransition.setFrame(layer: shimmerBorderLayer, frame: loadingBlurView.bounds)
// shimmerBorderLayer.frame = loadingBlurView.bounds
let borderMask = CAShapeLayer()
borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil)
let initialPath = CGPath(roundedRect: .init(x: 0, y: 0, width: initialShimmerBounds.width, height: initialShimmerBounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil)
borderMask.path = initialPath
// borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil)
videoFrameUpdateTransition.setShapeLayerPath(layer: borderMask, path: CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil))
borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
borderMask.strokeColor = UIColor.white.withAlphaComponent(0.7).cgColor
borderMask.lineWidth = 3
shimmerBorderLayer.mask = borderMask
shimmerBorderLayer.cornerRadius = videoCornerRadius
// if component.isAdmin {
// shimmerBorderLayer.isHidden = true
// } else {
// shimmerBorderLayer.isHidden = false
// }
//
if !self.hadVideo {
if self.noSignalTimer == nil {
@@ -623,7 +660,7 @@ final class MediaStreamVideoComponent: Component {
guard let strongSelf = self, let pictureInPictureController = strongSelf.pictureInPictureController else {
return
}
print("[pip] started")
pictureInPictureController.startPictureInPicture()
completion(Void())
@@ -701,25 +738,27 @@ final class MediaStreamVideoComponent: Component {
completionHandler(false)
return
}
didRequestBringBack = true
component.bringBackControllerForPictureInPictureDeactivation {
completionHandler(true)
}
}
var didRequestBringBack = false
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
self.didRequestBringBack = false
self.state?.updated(transition: .immediate)
}
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
if self.requestedExpansion {
self.requestedExpansion = false
} else {
} else if !didRequestBringBack {
self.component?.pictureInPictureClosed()
}
didRequestBringBack = false
// TODO: extract precise animation timing or observe window changes
// Handle minimized case separatelly (can we detect minimized?)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.videoView?.alpha = 1
}
UIView.animate(withDuration: 0.3) { [self] in

View File

@@ -168,7 +168,7 @@ final class StreamSheetComponent: CombinedComponent {
let background = background.update(
component: SheetBackgroundComponent(
color: context.component.backgroundColor,
radius: context.component.isFullyExtended ? context.component.deviceCornerRadius : 16,
radius: context.component.isFullyExtended ? context.component.deviceCornerRadius : 10.0,
offset: backgroundExtraOffset
),
availableSize: CGSize(width: size.width, height: context.component.sheetHeight),
@@ -184,7 +184,7 @@ final class StreamSheetComponent: CombinedComponent {
}
let viewerCounter = viewerCounter.update(
component: ParticipantsComponent(count: context.component.participantsCount),
component: ParticipantsComponent(count: context.component.participantsCount, fontSize: 44.0),
availableSize: CGSize(width: context.availableSize.width, height: 70),
transition: context.transition
)
@@ -208,17 +208,18 @@ final class StreamSheetComponent: CombinedComponent {
if let topItem = topItem {
context.add(topItem
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + (isFullscreen ? topItem.size.height / 2.0 : 32)))
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + (isFullscreen ? topItem.size.height / 2.0 : 28)))
)
(context.view as? StreamSheetComponent.View)?.overlayComponentsFrames.append(.init(x: 0, y: topOffset, width: topItem.size.width, height: topItem.size.height))
}
let videoHeight = context.component.videoHeight
let sheetHeight = context.component.sheetHeight
let animatedParticipantsVisible = context.component.participantsCount != -1
let animatedParticipantsVisible = !isFullscreen// context.component.participantsCount != -1
if true {
context.add(viewerCounter
.position(CGPoint(x: context.availableSize.width / 2, y: topOffset + 50 + videoHeight + (sheetHeight - 69 - videoHeight - 50 - context.component.bottomPadding) / 2 - 12))
.position(CGPoint(x: context.availableSize.width / 2, y: topOffset + 50 + videoHeight + (sheetHeight - 69 - videoHeight - 50 - context.component.bottomPadding) / 2 - 10))
.opacity(animatedParticipantsVisible ? 1 : 0)
// .animation(key: "position")
)
}
@@ -259,18 +260,24 @@ final class SheetBackgroundComponent: Component {
let extraBottom: CGFloat = 500
if backgroundView.backgroundColor != color && backgroundView.backgroundColor != nil {
UIView.animate(withDuration: 0.4) { [self] in
backgroundView.backgroundColor = color
// TODO: determine if animation is needed (with logic, not color)
backgroundView.frame = .init(origin: .init(x: 0, y: offset), size: .init(width: availableSize.width, height: availableSize.height + extraBottom))
if transition.animation.isImmediate {
UIView.animate(withDuration: 0.4) { [self] in
backgroundView.backgroundColor = color
// TODO: determine if animation is needed (with 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 {
transition.setBackgroundColor(view: backgroundView, color: color)
transition.setFrame(view: backgroundView, frame: CGRect(origin: .init(x: 0, y: offset), size: .init(width: availableSize.width, height: availableSize.height + extraBottom)))
transition.setCornerRadius(layer: backgroundView.layer, cornerRadius: cornerRadius)
}
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))
@@ -325,7 +332,8 @@ final class ParticipantsComponent: Component {
view.counter.update(
countString: self.count > 0 ? presentationStringsFormattedNumber(Int32(count), ",") : "",
subtitle: self.showsSubtitle ? (self.count > 0 ? "watching" : "no viewers") : "",
fontSize: self.fontSize
fontSize: self.fontSize,
gradientColors: self.gradientColors
)// environment.strings.LiveStream_NoViewers)
return availableSize
}
@@ -333,11 +341,13 @@ final class ParticipantsComponent: Component {
private let count: Int
private let showsSubtitle: Bool
private let fontSize: CGFloat
private let gradientColors: [CGColor]
init(count: Int, showsSubtitle: Bool = true, fontSize: CGFloat = 48) {
init(count: Int, showsSubtitle: Bool = true, fontSize: CGFloat = 48.0, gradientColors: [CGColor] = [pink.cgColor, purple.cgColor, purple.cgColor]) {
self.count = count
self.showsSubtitle = showsSubtitle
self.fontSize = fontSize
self.gradientColors = gradientColors
}
final class View: UIView {