mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-15 18:59:54 +00:00
Stylizing elements
This commit is contained in:
parent
a23580635f
commit
bfd81f6a0b
@ -19,90 +19,6 @@ import CreateExternalMediaStreamScreen
|
||||
import HierarchyTrackingLayer
|
||||
import UndoPanelComponent
|
||||
|
||||
final class NavigationBackButtonComponent: Component {
|
||||
let text: String
|
||||
let color: UIColor
|
||||
|
||||
init(text: String, color: UIColor) {
|
||||
self.text = text
|
||||
self.color = color
|
||||
}
|
||||
|
||||
static func ==(lhs: NavigationBackButtonComponent, rhs: NavigationBackButtonComponent) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let arrowView: UIImageView
|
||||
private let textView: ComponentHostView<Empty>
|
||||
|
||||
private var component: NavigationBackButtonComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.arrowView = UIImageView()
|
||||
self.textView = ComponentHostView<Empty>()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.arrowView)
|
||||
self.addSubview(self.textView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: NavigationBackButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
let spacing: CGFloat = 6.0
|
||||
let innerArrowInset: CGFloat = -8.0
|
||||
|
||||
if self.component?.color != component.color {
|
||||
self.arrowView.image = NavigationBarTheme.generateBackArrowImage(color: component.color)
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
let textSize = self.textView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(
|
||||
text: component.text,
|
||||
font: Font.regular(17.0),
|
||||
color: component.color
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
var leftInset: CGFloat = 0.0
|
||||
var size = textSize
|
||||
if let arrowImage = self.arrowView.image {
|
||||
size.width += innerArrowInset + arrowImage.size.width + spacing
|
||||
size.height = max(size.height, arrowImage.size.height)
|
||||
|
||||
self.arrowView.frame = CGRect(origin: CGPoint(x: innerArrowInset, y: floor((size.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
leftInset += innerArrowInset + arrowImage.size.width + spacing
|
||||
}
|
||||
self.textView.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class StreamTitleComponent: Component {
|
||||
let text: String
|
||||
let isRecording: Bool
|
||||
@ -137,10 +53,13 @@ final class StreamTitleComponent: Component {
|
||||
|
||||
addSubview(label)
|
||||
label.text = "LIVE"
|
||||
label.font = .systemFont(ofSize: 10, weight: .medium)
|
||||
label.font = .systemFont(ofSize: 12, weight: .semibold)
|
||||
label.textAlignment = .center
|
||||
layer.addSublayer(stalledAnimatedGradient)
|
||||
self.clipsToBounds = true
|
||||
if #available(iOS 13.0, *) {
|
||||
self.layer.cornerCurve = .continuous
|
||||
}
|
||||
toggle(isLive: false)
|
||||
}
|
||||
|
||||
@ -161,15 +80,32 @@ final class StreamTitleComponent: Component {
|
||||
if isLive {
|
||||
if !wasLive {
|
||||
// TODO: animate
|
||||
wasLive = true
|
||||
let frame = self.frame
|
||||
UIView.animate(withDuration: 0.15, animations: {
|
||||
self.toggle(isLive: true)
|
||||
self.transform = .init(scaleX: 1.5, y: 1.5)
|
||||
}, completion: { _ in
|
||||
UIView.animate(withDuration: 0.15) {
|
||||
self.transform = .identity
|
||||
self.frame = frame
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
self.backgroundColor = .systemPink
|
||||
self.backgroundColor = UIColor(red: 0.82, green: 0.26, blue: 0.37, alpha: 1)
|
||||
stalledAnimatedGradient.opacity = 0
|
||||
stalledAnimatedGradient.removeAllAnimations()
|
||||
} else {
|
||||
if wasLive {
|
||||
// TODO: animate
|
||||
wasLive = false
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
self.toggle(isLive: false)
|
||||
}
|
||||
return
|
||||
}
|
||||
self.backgroundColor = .gray
|
||||
self.backgroundColor = UIColor(white: 0.36, alpha: 1)
|
||||
stalledAnimatedGradient.opacity = 1
|
||||
// stalledAnimatedGradient.add(<#T##anim: CAAnimation##CAAnimation#>, forKey: <#T##String?#>)
|
||||
}
|
||||
@ -247,13 +183,13 @@ final class StreamTitleComponent: Component {
|
||||
indicatorView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
liveIndicatorView.toggle(isLive: component.isActive)
|
||||
let sideInset: CGFloat = 20.0
|
||||
let size = CGSize(width: textSize.width + sideInset * 2.0, height: textSize.height)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - textSize.height) / 2.0)), size: textSize)
|
||||
self.textView.frame = textFrame
|
||||
|
||||
liveIndicatorView.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 6.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0) + 1.0), size: .init(width: 40, height: 18))
|
||||
liveIndicatorView.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 6.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0 - 2) + 1.0), size: .init(width: 40, height: 22))
|
||||
self.liveIndicatorView.toggle(isLive: component.isActive)
|
||||
|
||||
if let indicatorView = self.indicatorView, let image = indicatorView.image {
|
||||
indicatorView.frame = CGRect(origin: CGPoint(x: liveIndicatorView.frame.maxX + 6.0, y: floorToScreenPixels((size.height - image.size.height) / 2.0) + 1.0), size: image.size)
|
||||
@ -377,9 +313,10 @@ private final class NavigationBarComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
// let maxCenterInset = max(centerLeftInset, centerRightInset)
|
||||
let someUndesiredOffset: CGFloat = 16
|
||||
if let centerItem = centerItem {
|
||||
context.add(centerItem
|
||||
.position(CGPoint(x: context.availableSize.width / 2 /*maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0*/, y: context.component.topInset + contentHeight / 2.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2 - someUndesiredOffset /*maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0*/, y: context.component.topInset + contentHeight / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
@ -785,6 +722,7 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
private(set) var hasVideo: Bool = false
|
||||
private var stateDisposable: Disposable?
|
||||
private var infoDisposable: Disposable?
|
||||
private var connectionDisposable: Disposable?
|
||||
|
||||
private(set) var originInfo: OriginInfo?
|
||||
|
||||
@ -808,13 +746,14 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
private var isVisibleInHierarchyDisposable: Disposable?
|
||||
|
||||
private var scheduledDismissUITimer: SwiftSignalKit.Timer?
|
||||
var videoStalled: Bool = false
|
||||
|
||||
let deactivatePictureInPictureIfVisible = StoredActionSlot(Void.self)
|
||||
|
||||
var videoHiddenForPip = false
|
||||
/// To update videoHiddenForPip
|
||||
var onExpandedFromPictureInPicture: ((State) -> Void)?
|
||||
private let infoThrottler = Throttler<Int?>.init(duration: 5, queue: .main)
|
||||
private let infoThrottler = Throttler<Int>.init(duration: 5, queue: .main)
|
||||
|
||||
init(call: PresentationGroupCallImpl) {
|
||||
self.call = call
|
||||
@ -845,6 +784,15 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
strongSelf.updated(transition: .immediate)
|
||||
})
|
||||
|
||||
self.connectionDisposable = call.state.start(next: { [weak self] state in
|
||||
switch state.networkState {
|
||||
case .connected:
|
||||
self?.videoStalled = false
|
||||
default:
|
||||
self?.videoStalled = true
|
||||
}
|
||||
})
|
||||
|
||||
let callPeer = call.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId))
|
||||
|
||||
self.infoDisposable = (combineLatest(queue: .mainQueue(), call.state, call.members, callPeer)
|
||||
@ -855,11 +803,12 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
|
||||
var updated = false
|
||||
// TODO: remove debug
|
||||
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
|
||||
strongSelf.infoThrottler.publish(members.totalCount/*Int.random(in: 0..<10000000)*/) { [weak strongSelf] latestCount in
|
||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
|
||||
strongSelf.infoThrottler.publish(/*members.totalCount*/Int.random(in: 0..<10000000)) { [weak strongSelf] latestCount in
|
||||
guard let strongSelf = strongSelf else { return }
|
||||
var updated = false
|
||||
let originInfo = OriginInfo(title: callPeer.debugDisplayTitle, memberCount: members.totalCount)
|
||||
print(members)
|
||||
let originInfo = OriginInfo(title: callPeer.debugDisplayTitle, memberCount: latestCount)
|
||||
if strongSelf.originInfo != originInfo {
|
||||
strongSelf.originInfo = originInfo
|
||||
updated = true
|
||||
@ -927,6 +876,7 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
self.stateDisposable?.dispose()
|
||||
self.infoDisposable?.dispose()
|
||||
self.isVisibleInHierarchyDisposable?.dispose()
|
||||
self.connectionDisposable?.dispose()
|
||||
}
|
||||
|
||||
func toggleDisplayUI() {
|
||||
@ -1015,11 +965,14 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
}
|
||||
var isFullscreen = state.isFullscreen
|
||||
let isLandscape = context.availableSize.width > context.availableSize.height
|
||||
if let _ = context.state.videoSize {
|
||||
if let videoSize = context.state.videoSize {
|
||||
// Always fullscreen in landscape
|
||||
if /*videoSize.width > videoSize.height &&*/ isLandscape && !isFullscreen {
|
||||
state.isFullscreen = true
|
||||
isFullscreen = true
|
||||
} else if videoSize.width > videoSize.height && !isLandscape && isFullscreen {
|
||||
state.isFullscreen = false
|
||||
isFullscreen = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -1033,6 +986,7 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
// TODO: find out how to get image
|
||||
peerImage: nil,
|
||||
isFullscreen: isFullscreen,
|
||||
videoLoading: context.state.videoStalled,
|
||||
activatePictureInPicture: activatePictureInPicture,
|
||||
deactivatePictureInPicture: deactivatePictureInPicture,
|
||||
bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in
|
||||
@ -1061,9 +1015,16 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
// let contextView = context.view
|
||||
if context.state.isPictureInPictureSupported, context.state.hasVideo {
|
||||
navigationRightItems.append(AnyComponentWithIdentity(id: "pip", component: AnyComponent(Button(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Media Gallery/PictureInPictureButton",
|
||||
tintColor: .white
|
||||
content: AnyComponent(ZStack([
|
||||
AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle(
|
||||
fillColor: .white.withAlphaComponent(0.08),
|
||||
size: CGSize(width: 32.0, height: 32.0)
|
||||
))),
|
||||
AnyComponentWithIdentity(id: "a", component: AnyComponent(BundleIconComponent(
|
||||
name: "Media Gallery/PictureInPictureButton",
|
||||
tintColor: .white
|
||||
)))
|
||||
]
|
||||
)),
|
||||
action: {
|
||||
activatePictureInPicture.invoke(Action {
|
||||
@ -1084,9 +1045,8 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
AnyComponent(Button(
|
||||
content: AnyComponent(ZStack([
|
||||
AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle(
|
||||
strokeColor: .white,
|
||||
strokeWidth: 1.5,
|
||||
size: CGSize(width: 22.0, height: 22.0)
|
||||
fillColor: .white.withAlphaComponent(0.08),
|
||||
size: CGSize(width: 32.0, height: 32.0)
|
||||
))),
|
||||
AnyComponentWithIdentity(id: "a", component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
@ -1098,7 +1058,7 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
"Point 3.Group 1.Fill 1": whiteColor,
|
||||
"Point 1.Group 1.Fill 1": whiteColor
|
||||
],
|
||||
size: CGSize(width: 22.0, height: 22.0)
|
||||
size: CGSize(width: 32.0, height: 32.0)
|
||||
).tagged(moreAnimationTag))),
|
||||
])),
|
||||
action: { [weak call, weak state] in
|
||||
@ -1322,7 +1282,7 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
})
|
||||
)*/,
|
||||
rightItems: navigationRightItems,
|
||||
centerItem: AnyComponent(StreamTitleComponent(text: state.peerTitle, isRecording: state.recordingStartTimestamp != nil, isActive: call.hasVideo))
|
||||
centerItem: AnyComponent(StreamTitleComponent(text: state.peerTitle, isRecording: state.recordingStartTimestamp != nil, isActive: context.state.hasVideo))
|
||||
)
|
||||
|
||||
// let navigationBar = navigationBar.update(
|
||||
@ -1356,7 +1316,7 @@ 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 + 32 + 70 + bottomPadding)
|
||||
let sheetHeight: CGFloat = isFullscreen ? context.availableSize.height : (44 + videoHeight + 40 + 69 + 16 + 32 + 70 + bottomPadding)
|
||||
let isFullyDragged = context.availableSize.height - sheetHeight + state.dismissOffset < 30
|
||||
|
||||
context.add(background
|
||||
@ -1519,8 +1479,8 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
sheetHeight: max(sheetHeight - context.state.dismissOffset, sheetHeight),
|
||||
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
|
||||
bottomPadding: bottomPadding,
|
||||
participantsCount: // [0, 5, 15, 16, 95, 100, 16042, 942539].randomElement()!
|
||||
context.state.originInfo?.memberCount ?? 0
|
||||
participantsCount: context.state.originInfo?.memberCount ?? 0 // Int.random(in: 0...999998)// [0, 5, 15, 16, 95, 100, 16042, 942539].randomElement()!
|
||||
//
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
@ -1610,6 +1570,7 @@ public final class _MediaStreamComponent: CombinedComponent {
|
||||
)
|
||||
context.add(fullScreenOverlayComponent
|
||||
.position(.init(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2))
|
||||
.opacity(state.displayUI ? 1 : 0)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ final class _MediaStreamVideoComponent: Component {
|
||||
let peerImage: Any?
|
||||
let isFullscreen: Bool
|
||||
let onVideoSizeRetrieved: (CGSize) -> Void
|
||||
let videoLoading: Bool
|
||||
init(
|
||||
call: PresentationGroupCallImpl,
|
||||
hasVideo: Bool,
|
||||
@ -32,6 +33,7 @@ final class _MediaStreamVideoComponent: Component {
|
||||
peerTitle: String,
|
||||
peerImage: Any?,
|
||||
isFullscreen: Bool,
|
||||
videoLoading: Bool,
|
||||
activatePictureInPicture: ActionSlot<Action<Void>>,
|
||||
deactivatePictureInPicture: ActionSlot<Void>,
|
||||
bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void,
|
||||
@ -43,6 +45,7 @@ final class _MediaStreamVideoComponent: Component {
|
||||
self.isVisible = isVisible
|
||||
self.isAdmin = isAdmin
|
||||
self.peerTitle = peerTitle
|
||||
self.videoLoading = videoLoading
|
||||
self.activatePictureInPicture = activatePictureInPicture
|
||||
self.deactivatePictureInPicture = deactivatePictureInPicture
|
||||
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
|
||||
@ -100,7 +103,8 @@ final class _MediaStreamVideoComponent: Component {
|
||||
|
||||
private var videoPlaceholderView: UIView?
|
||||
private var noSignalView: ComponentHostView<Empty>?
|
||||
|
||||
private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||
private let shimmerOverlayView = CALayer()
|
||||
private var pictureInPictureController: AVPictureInPictureController?
|
||||
|
||||
private var component: _MediaStreamVideoComponent?
|
||||
@ -181,7 +185,6 @@ final class _MediaStreamVideoComponent: Component {
|
||||
if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) {
|
||||
self.videoView = videoView
|
||||
self.addSubview(videoView)
|
||||
videoView.alpha = 1
|
||||
if let sampleBufferVideoView = videoView as? SampleBufferVideoRenderingView {
|
||||
if #available(iOS 13.0, *) {
|
||||
sampleBufferVideoView.sampleBufferLayer.preventsDisplaySleepDuringVideoPlayback = true
|
||||
@ -262,6 +265,8 @@ final class _MediaStreamVideoComponent: Component {
|
||||
strongSelf.noSignalView?.removeFromSuperview()
|
||||
strongSelf.noSignalView = nil
|
||||
|
||||
let snapshot = strongSelf.videoView?.snapshotView(afterScreenUpdates: true)
|
||||
strongSelf.addSubview(snapshot ?? UIVisualEffectView(effect: UIBlurEffect(style: .dark)))
|
||||
state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
@ -316,7 +321,12 @@ final class _MediaStreamVideoComponent: Component {
|
||||
videoView.layer.cornerRadius = component.isFullscreen ? 0 : 10
|
||||
// var aspect = videoView.getAspect()
|
||||
// if aspect <= 0.01 {
|
||||
|
||||
// TODO: remove debug
|
||||
// if component.videoLoading {
|
||||
// videoView.alpha = 0.5
|
||||
// } else {
|
||||
// videoView.alpha = 1
|
||||
// }
|
||||
|
||||
transition.withAnimation(.none).setFrame(view: videoView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize), completion: nil)
|
||||
|
||||
@ -354,7 +364,7 @@ final class _MediaStreamVideoComponent: Component {
|
||||
activityIndicatorTransition = transition.withAnimation(.none)
|
||||
activityIndicatorView = ComponentHostView<Empty>()
|
||||
self.activityIndicatorView = activityIndicatorView
|
||||
self.addSubview(activityIndicatorView)
|
||||
// self.addSubview(activityIndicatorView)
|
||||
}
|
||||
|
||||
let activityIndicatorSize = activityIndicatorView.update(
|
||||
@ -431,6 +441,11 @@ final class _MediaStreamVideoComponent: Component {
|
||||
return availableSize
|
||||
}
|
||||
|
||||
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
videoView?.alpha = 0
|
||||
videoBlurView?.alpha = 0
|
||||
}
|
||||
|
||||
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
||||
guard let component = self.component else {
|
||||
completionHandler(false)
|
||||
@ -455,6 +470,8 @@ final class _MediaStreamVideoComponent: Component {
|
||||
}
|
||||
|
||||
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
self.videoView?.alpha = 1
|
||||
self.videoBlurView?.alpha = 1
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,16 +171,16 @@ final class StreamSheetComponent: CombinedComponent {
|
||||
|
||||
if let topItem = topItem {
|
||||
context.add(topItem
|
||||
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + contentHeight / 2.0))
|
||||
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + 32))
|
||||
)
|
||||
(context.view as? StreamSheetComponent.View)?.overlayComponentsFrames.append(.init(x: 0, y: topOffset, width: topItem.size.width, height: topItem.size.height))
|
||||
}
|
||||
|
||||
let videoHeight = (availableWidth - 32) / 16 * 9
|
||||
let sheetHeight = context.component.sheetHeight
|
||||
let animatedParticipantsVisible = context.component.participantsCount != -1
|
||||
if true {
|
||||
context.add(viewerCounter
|
||||
.position(CGPoint(x: context.availableSize.width / 2, y: topOffset + 50 + videoHeight + 40 + 30))
|
||||
.position(CGPoint(x: context.availableSize.width / 2, y: topOffset + 50 + videoHeight + (sheetHeight - 69 - videoHeight - 50 - context.component.bottomPadding) / 2 - 12))
|
||||
.opacity(animatedParticipantsVisible ? 1 : 0)
|
||||
)
|
||||
}
|
||||
@ -215,55 +215,6 @@ private let pink = UIColor(rgb: 0xe4436c)
|
||||
private let latePurple = UIColor(rgb: 0x974aa9)
|
||||
private let latePink = UIColor(rgb: 0xf0436c)
|
||||
|
||||
final class ViewerCountComponent: Component {
|
||||
private let count: Int
|
||||
|
||||
// private let counterView: VoiceChatTimerNode
|
||||
|
||||
static func ==(lhs: ViewerCountComponent, rhs: ViewerCountComponent) -> Bool {
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
init(count: Int) {
|
||||
self.count = count
|
||||
}
|
||||
|
||||
public func update(view: UIView, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
|
||||
/*self.foregroundView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.foregroundGradientLayer.frame = self.foregroundView.bounds
|
||||
self.maskView.frame = self.foregroundView.bounds
|
||||
|
||||
let text: String = presentationStringsFormattedNumber(participants, groupingSeparator)
|
||||
let subtitle = "listening"
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: "", font: Font.with(size: 23.0, design: .round, weight: .semibold, traits: []), textColor: .white)
|
||||
let titleSize = self.titleNode.updateLayout(size)
|
||||
self.titleNode.frame = CGRect(x: floor((size.width - titleSize.width) / 2.0), y: 48.0, width: titleSize.width, height: titleSize.height)
|
||||
|
||||
self.timerNode.attributedText = NSAttributedString(string: text, font: Font.with(size: 68.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
|
||||
|
||||
var timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height))
|
||||
if timerSize.width > size.width - 32.0 {
|
||||
self.timerNode.attributedText = NSAttributedString(string: text, font: Font.with(size: 60.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
|
||||
timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height))
|
||||
}
|
||||
|
||||
self.timerNode.frame = CGRect(x: floor((size.width - timerSize.width) / 2.0), y: 78.0, width: timerSize.width, height: timerSize.height)
|
||||
|
||||
self.subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.with(size: 21.0, design: .round, weight: .semibold, traits: []), textColor: .white)
|
||||
let subtitleSize = self.subtitleNode.updateLayout(size)
|
||||
self.subtitleNode.frame = CGRect(x: floor((size.width - subtitleSize.width) / 2.0), y: 164.0, width: subtitleSize.width, height: subtitleSize.height)
|
||||
|
||||
self.foregroundView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
*/
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
final class SheetBackgroundComponent: Component {
|
||||
private let color: UIColor
|
||||
|
||||
@ -401,7 +352,7 @@ public final class AnimatedCountView: UIView {
|
||||
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 - 10 : bounds.height, width: subtitleLabel.intrinsicContentSize.width + 20, height: 20)
|
||||
subtitleLabel.frame = .init(x: bounds.midX - subtitleLabel.intrinsicContentSize.width / 2 - 10, y: subtitleLabel.text == "No viewers" ? bounds.midY - 10 : bounds.height - 6, width: subtitleLabel.intrinsicContentSize.width + 20, height: 20)
|
||||
}
|
||||
|
||||
func update(countString: String, subtitle: String) {
|
||||
@ -614,7 +565,6 @@ class AnimatedCountLabel: UILabel {
|
||||
}
|
||||
|
||||
func udpateAttributed(with newString: NSAttributedString) {
|
||||
let initialDuration: TimeInterval = 0.25
|
||||
let interItemSpacing: CGFloat = 0
|
||||
|
||||
let separatedStrings = Array(newString.string).map { String($0) }
|
||||
@ -629,10 +579,22 @@ class AnimatedCountLabel: UILabel {
|
||||
|
||||
let currentChars = chars.map { $0.attributedText ?? .init() }
|
||||
|
||||
let maxAnimationDuration: TimeInterval = 0.5
|
||||
var numberOfChanges = abs(newChars.count - currentChars.count)
|
||||
for index in 0..<min(newChars.count, currentChars.count) {
|
||||
let newCharIndex = newChars.count - 1 - index
|
||||
let currCharIndex = currentChars.count - 1 - index
|
||||
if newChars[newCharIndex] != currentChars[currCharIndex] {
|
||||
numberOfChanges += 1
|
||||
}
|
||||
}
|
||||
|
||||
let initialDuration: TimeInterval = min(0.25, maxAnimationDuration / Double(numberOfChanges)) /// 0.25
|
||||
|
||||
// let currentWidth = itemWidth * CGFloat(currentChars.count)
|
||||
// let newWidth = itemWidth * CGFloat(newChars.count)
|
||||
|
||||
let interItemDelay: TimeInterval = 0.15
|
||||
let interItemDelay: TimeInterval = 0.08
|
||||
var changeIndex = 0
|
||||
|
||||
var newLayers = [AnimatedCharLayer]()
|
||||
@ -706,7 +668,7 @@ class AnimatedCountLabel: UILabel {
|
||||
return $0 + itemWidth + interItemSpacing
|
||||
}
|
||||
if didBegin && prevCount != chars.count {
|
||||
UIView.animate(withDuration: 0.3, delay: initialDuration * Double(changeIndex)) { [self] in
|
||||
UIView.animate(withDuration: Double(changeIndex) * initialDuration/*, delay: initialDuration * Double(changeIndex)*/) { [self] in
|
||||
containerView.frame = .init(x: self.bounds.midX - countWidth / 2, y: 0, width: countWidth, height: self.bounds.height)
|
||||
// containerView.backgroundColor = .red.withAlphaComponent(0.3)
|
||||
}
|
||||
@ -714,13 +676,14 @@ class AnimatedCountLabel: UILabel {
|
||||
containerView.frame = .init(x: self.bounds.midX - countWidth / 2, y: 0, width: countWidth, height: self.bounds.height)
|
||||
didBegin = true
|
||||
}
|
||||
self.backgroundColor = .green.withAlphaComponent(0.2)
|
||||
// self.backgroundColor = .green.withAlphaComponent(0.2)
|
||||
self.clipsToBounds = false
|
||||
}
|
||||
var didBegin = false
|
||||
func animateOut(for layer: CALayer, duration: CFTimeInterval, beginTime: CFTimeInterval) {
|
||||
let animation = CAKeyframeAnimation()
|
||||
animation.keyPath = "opacity"
|
||||
animation.values = [layer.value(forKey: "opacity") ?? 1, 0.0]
|
||||
animation.values = [layer.presentation()?.value(forKey: "opacity") ?? 1, 0.0]
|
||||
animation.keyTimes = [0, 1]
|
||||
animation.duration = duration
|
||||
animation.beginTime = CACurrentMediaTime() + beginTime
|
||||
@ -735,7 +698,7 @@ class AnimatedCountLabel: UILabel {
|
||||
layer.removeFromSuperlayer()
|
||||
}
|
||||
let scaleOutAnimation = CABasicAnimation(keyPath: "transform.scale")
|
||||
scaleOutAnimation.fromValue = layer.value(forKey: "transform.scale") ?? 1
|
||||
scaleOutAnimation.fromValue = layer.presentation()?.value(forKey: "transform.scale") ?? 1
|
||||
scaleOutAnimation.toValue = 0.1
|
||||
scaleOutAnimation.duration = duration
|
||||
scaleOutAnimation.beginTime = CACurrentMediaTime() + beginTime
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user