mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Fixing streaming error message, dismiss animation, 1 member, stream title, context menu dismiss on recording, shimmer animation, adding animated counter to toolbar,
This commit is contained in:
parent
ea8be9e909
commit
1424e7135d
@ -5956,7 +5956,7 @@ Sorry for the inconvenience.";
|
|||||||
"LiveStream.RecordingInProgress" = "Live stream is being recorded";
|
"LiveStream.RecordingInProgress" = "Live stream is being recorded";
|
||||||
|
|
||||||
"VoiceChat.StopRecordingTitle" = "Stop Recording?";
|
"VoiceChat.StopRecordingTitle" = "Stop Recording?";
|
||||||
"VoiceChat.StopRecordingStop" = "Stop";
|
"VoiceChat.StopRecordingStop" = "Stop Recording";
|
||||||
|
|
||||||
"VoiceChat.RecordingSaved" = "Audio saved to **Saved Messages**.";
|
"VoiceChat.RecordingSaved" = "Audio saved to **Saved Messages**.";
|
||||||
|
|
||||||
|
|||||||
@ -46,6 +46,9 @@ public enum ContextMenuActionItemTextColor {
|
|||||||
public enum ContextMenuActionResult {
|
public enum ContextMenuActionResult {
|
||||||
case `default`
|
case `default`
|
||||||
case dismissWithoutContent
|
case dismissWithoutContent
|
||||||
|
/// Temporary
|
||||||
|
static var safeStreamRecordingDismissWithoutContent: ContextMenuActionResult { .dismissWithoutContent }
|
||||||
|
|
||||||
case custom(ContainedViewLayoutTransition)
|
case custom(ContainedViewLayoutTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -403,16 +403,19 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C
|
|||||||
|
|
||||||
private let getController: () -> ContextControllerProtocol?
|
private let getController: () -> ContextControllerProtocol?
|
||||||
private let item: ContextMenuCustomItem
|
private let item: ContextMenuCustomItem
|
||||||
|
private let requestDismiss: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
private var presentationData: PresentationData?
|
private var presentationData: PresentationData?
|
||||||
private var itemNode: ContextMenuCustomNode?
|
private var itemNode: ContextMenuCustomNode?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
getController: @escaping () -> ContextControllerProtocol?,
|
getController: @escaping () -> ContextControllerProtocol?,
|
||||||
item: ContextMenuCustomItem
|
item: ContextMenuCustomItem,
|
||||||
|
requestDismiss: @escaping (ContextMenuActionResult) -> Void
|
||||||
) {
|
) {
|
||||||
self.getController = getController
|
self.getController = getController
|
||||||
self.item = item
|
self.item = item
|
||||||
|
self.requestDismiss = requestDismiss
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
@ -433,7 +436,12 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C
|
|||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
getController: self.getController,
|
getController: self.getController,
|
||||||
actionSelected: { result in
|
actionSelected: { result in
|
||||||
let _ = result
|
switch result {
|
||||||
|
case .dismissWithoutContent/* where ContextMenuActionResult.safeStreamRecordingDismissWithoutContent == .dismissWithoutContent*/:
|
||||||
|
self.requestDismiss(result)
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.itemNode = itemNode
|
self.itemNode = itemNode
|
||||||
@ -505,7 +513,8 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack
|
|||||||
return Item(
|
return Item(
|
||||||
node: ContextControllerActionsListCustomItemNode(
|
node: ContextControllerActionsListCustomItemNode(
|
||||||
getController: getController,
|
getController: getController,
|
||||||
item: customItem
|
item: customItem,
|
||||||
|
requestDismiss: requestDismiss
|
||||||
),
|
),
|
||||||
separatorNode: ASDisplayNode()
|
separatorNode: ASDisplayNode()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -500,7 +500,7 @@ public final class StandaloneShimmerEffect {
|
|||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations) else { return }
|
guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations) else { return }
|
||||||
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.3), options: CGGradientDrawingOptions())
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.2), end: CGPoint(x: size.width, y: 0.8), options: CGGradientDrawingOptions())
|
||||||
})
|
})
|
||||||
|
|
||||||
self.updateHorizontalLayer()
|
self.updateHorizontalLayer()
|
||||||
@ -533,14 +533,28 @@ public final class StandaloneShimmerEffect {
|
|||||||
layer.contents = image.cgImage
|
layer.contents = image.cgImage
|
||||||
|
|
||||||
if layer.animation(forKey: "shimmer") == nil {
|
if layer.animation(forKey: "shimmer") == nil {
|
||||||
|
var delay: TimeInterval { 1.6 }
|
||||||
let animation = CABasicAnimation(keyPath: "contentsRect.origin.x")
|
let animation = CABasicAnimation(keyPath: "contentsRect.origin.x")
|
||||||
animation.fromValue = 1.0 as NSNumber
|
animation.fromValue = NSNumber(floatLiteral: delay)
|
||||||
animation.toValue = -1.0 as NSNumber
|
animation.toValue = NSNumber(floatLiteral: -delay)
|
||||||
animation.isAdditive = true
|
animation.isAdditive = true
|
||||||
animation.repeatCount = .infinity
|
animation.repeatCount = .infinity
|
||||||
animation.duration = 0.8
|
animation.duration = 0.8 * delay
|
||||||
animation.beginTime = layer.convertTime(1.0, from: nil)
|
animation.timingFunction = .init(name: .easeInEaseOut)
|
||||||
|
// animation.beginTime = layer.convertTime(1.0, from: nil)
|
||||||
layer.add(animation, forKey: "shimmer")
|
layer.add(animation, forKey: "shimmer")
|
||||||
|
/*let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
|
||||||
|
opacityAnimation.values = [0.0, 1.0, 0.0]
|
||||||
|
opacityAnimation.keyTimes = [0, 0.5, 0]
|
||||||
|
opacityAnimation.calculationMode = .linear
|
||||||
|
// opacityAnimation.fromValue = 2.0 as NSNumber
|
||||||
|
// opacityAnimation.toValue = -2.0 as NSNumber
|
||||||
|
// opacityAnimation.isAdditive = true
|
||||||
|
opacityAnimation.repeatCount = .infinity
|
||||||
|
opacityAnimation.duration = 1.6
|
||||||
|
opacityAnimation.timingFunctions = [.init(name: .easeInEaseOut)]
|
||||||
|
// opacityAnimation.beginTime = layer.convertTime(1.0, from: nil)
|
||||||
|
layer.add(opacityAnimation, forKey: "opacity")*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,14 +50,14 @@ public final class AnimatedCountView: UIView {
|
|||||||
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)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(countString: String, subtitle: String) {
|
func update(countString: String, subtitle: String, fontSize: CGFloat = 48.0) {
|
||||||
self.setupGradientAnimations()
|
self.setupGradientAnimations()
|
||||||
|
|
||||||
let text: String = countString
|
let text: String = countString
|
||||||
self.countLabel.fontSize = 48
|
self.countLabel.fontSize = fontSize
|
||||||
self.countLabel.attributedText = NSAttributedString(string: text, font: Font.with(size: 48, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
|
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: 16, weight: .semibold)])
|
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, attributes: [.font: UIFont.systemFont(ofSize: max(floor(fontSize / 3), 12), weight: .semibold)])
|
||||||
self.subtitleLabel.isHidden = subtitle.isEmpty
|
self.subtitleLabel.isHidden = subtitle.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -170,7 +170,9 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
var updated = false
|
var updated = false
|
||||||
// TODO: remove debug timer
|
// TODO: remove debug timer
|
||||||
// Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
|
// Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
|
||||||
strongSelf.infoThrottler.publish(members.totalCount/*Int.random(in: 0..<10000000)*/) { [weak strongSelf] latestCount 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 _ = members.totalCount
|
||||||
guard let strongSelf = strongSelf else { return }
|
guard let strongSelf = strongSelf else { return }
|
||||||
var updated = false
|
var updated = false
|
||||||
@ -411,7 +413,9 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
|
|
||||||
var navigationRightItems: [AnyComponentWithIdentity<Empty>] = []
|
var navigationRightItems: [AnyComponentWithIdentity<Empty>] = []
|
||||||
|
|
||||||
if context.state.isPictureInPictureSupported, context.state.videoIsPlayable {
|
// let videoIsPlayable = context.state.videoIsPlayable
|
||||||
|
|
||||||
|
if context.state.isPictureInPictureSupported /*, context.state.videoIsPlayable*/ {
|
||||||
navigationRightItems.append(AnyComponentWithIdentity(id: "pip", component: AnyComponent(Button(
|
navigationRightItems.append(AnyComponentWithIdentity(id: "pip", component: AnyComponent(Button(
|
||||||
content: AnyComponent(ZStack([
|
content: AnyComponent(ZStack([
|
||||||
AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle(
|
AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle(
|
||||||
@ -420,7 +424,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
))),
|
))),
|
||||||
AnyComponentWithIdentity(id: "a", component: AnyComponent(BundleIconComponent(
|
AnyComponentWithIdentity(id: "a", component: AnyComponent(BundleIconComponent(
|
||||||
name: "Call/pip",
|
name: "Call/pip",
|
||||||
tintColor: .white
|
tintColor: .white // .withAlphaComponent(context.state.videoIsPlayable ? 1.0 : 0.6)
|
||||||
)))
|
)))
|
||||||
]
|
]
|
||||||
)),
|
)),
|
||||||
@ -435,6 +439,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
).minSize(CGSize(width: 44.0, height: 44.0)))))
|
).minSize(CGSize(width: 44.0, height: 44.0)))))
|
||||||
}
|
}
|
||||||
var topLeftButton: AnyComponent<Empty>?
|
var topLeftButton: AnyComponent<Empty>?
|
||||||
|
|
||||||
if context.state.canManageCall {
|
if context.state.canManageCall {
|
||||||
let whiteColor = UIColor(white: 1.0, alpha: 1.0)
|
let whiteColor = UIColor(white: 1.0, alpha: 1.0)
|
||||||
topLeftButton = AnyComponent(Button(
|
topLeftButton = AnyComponent(Button(
|
||||||
@ -477,7 +482,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(id: nil, text: presentationData.strings.LiveStream_EditTitle, textColor: .primary, textLayout: .singleLine, textFont: .regular, badge: nil, icon: { theme in
|
items.append(.action(ContextMenuActionItem(id: nil, text: presentationData.strings.LiveStream_EditTitle, textColor: .primary, textLayout: .singleLine, textFont: .regular, badge: nil, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { [weak call, weak controller, weak state] _, a in
|
}, action: { [weak call, weak controller, weak state] _, dismissWithResult in
|
||||||
guard let call = call, let controller = controller, let state = state, let chatPeer = state.chatPeer else {
|
guard let call = call, let controller = controller, let state = state, let chatPeer = state.chatPeer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -507,12 +512,11 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
})
|
})
|
||||||
controller.present(editController, in: .window(.root))
|
controller.present(editController, in: .window(.root))
|
||||||
|
|
||||||
a(.default)
|
dismissWithResult(.default)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if let recordingStartTimestamp = state.recordingStartTimestamp {
|
if let recordingStartTimestamp = state.recordingStartTimestamp {
|
||||||
items.append(.custom(VoiceChatRecordingContextItem(timestamp: recordingStartTimestamp, action: { [weak call, weak controller] _, f in
|
items.append(.custom(VoiceChatRecordingContextItem(timestamp: recordingStartTimestamp, action: { [weak call, weak controller] _, dismissWithResult in
|
||||||
f(.dismissWithoutContent)
|
|
||||||
|
|
||||||
guard let call = call, let controller = controller else {
|
guard let call = call, let controller = controller else {
|
||||||
return
|
return
|
||||||
@ -547,6 +551,8 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
})*/
|
})*/
|
||||||
})])
|
})])
|
||||||
controller.present(alertController, in: .window(.root))
|
controller.present(alertController, in: .window(.root))
|
||||||
|
// TODO: спросить про dismissWithoutContent и default
|
||||||
|
dismissWithResult(.dismissWithoutContent)
|
||||||
}), false))
|
}), false))
|
||||||
} else {
|
} else {
|
||||||
let text = presentationData.strings.LiveStream_StartRecording
|
let text = presentationData.strings.LiveStream_StartRecording
|
||||||
@ -605,14 +611,34 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
a(.default)
|
a(.default)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(id: nil, text: presentationData.strings.VoiceChat_StopRecordingStop, textColor: .destructive, textLayout: .singleLine, textFont: .regular, badge: nil, icon: { theme in
|
items.append(.action(ContextMenuActionItem(id: nil, text: /*presentationData.strings.VoiceChat_StopRecordingStop*/"Stop Live Stream", textColor: .destructive, textLayout: .singleLine, textFont: .regular, badge: nil, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor, backgroundColor: nil)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor, backgroundColor: nil)
|
||||||
}, action: { [weak call] _, a in
|
}, action: { [weak call] _, a in
|
||||||
guard let call = call else {
|
guard let call = call else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let alertController = textAlertController(
|
||||||
let _ = call.leave(terminateIfPossible: true).start()
|
context: call.accountContext,
|
||||||
|
forceTheme: defaultDarkPresentationTheme,
|
||||||
|
title: nil,
|
||||||
|
text: presentationData.strings.VoiceChat_StopRecordingTitle,
|
||||||
|
actions: [
|
||||||
|
TextAlertAction(
|
||||||
|
type: .genericAction,
|
||||||
|
title: presentationData.strings.Common_Cancel,
|
||||||
|
action: {}
|
||||||
|
),
|
||||||
|
TextAlertAction(
|
||||||
|
type: .defaultAction,
|
||||||
|
title: presentationData.strings.VoiceChat_StopRecordingStop,
|
||||||
|
action: { [weak call] in
|
||||||
|
guard let call = call else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = call.leave(terminateIfPossible: true).start()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
controller.present(alertController, in: .window(.root))
|
||||||
|
|
||||||
a(.default)
|
a(.default)
|
||||||
})))
|
})))
|
||||||
@ -669,9 +695,10 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
let navigationComponent = NavigationBarComponent(
|
let navigationComponent = NavigationBarComponent(
|
||||||
topInset: environment.statusBarHeight,
|
topInset: environment.statusBarHeight,
|
||||||
sideInset: environment.safeInsets.left,
|
sideInset: environment.safeInsets.left,
|
||||||
|
backgroundVisible: isFullscreen,
|
||||||
leftItem: topLeftButton,
|
leftItem: topLeftButton,
|
||||||
rightItems: navigationRightItems,
|
rightItems: navigationRightItems,
|
||||||
centerItem: AnyComponent(StreamTitleComponent(text: state.peerTitle, isRecording: state.recordingStartTimestamp != nil, isActive: context.state.videoIsPlayable))
|
centerItem: AnyComponent(StreamTitleComponent(text: state.callTitle ?? state.peerTitle, isRecording: state.recordingStartTimestamp != nil, isActive: context.state.videoIsPlayable))
|
||||||
)
|
)
|
||||||
|
|
||||||
if context.state.storedIsFullscreen != isFullscreen {
|
if context.state.storedIsFullscreen != isFullscreen {
|
||||||
@ -685,15 +712,8 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
|
|
||||||
var infoItem: AnyComponent<Empty>?
|
var infoItem: AnyComponent<Empty>?
|
||||||
if let originInfo = context.state.originInfo {
|
if let originInfo = context.state.originInfo {
|
||||||
let memberCountString: String
|
|
||||||
if originInfo.memberCount == 0 {
|
|
||||||
memberCountString = environment.strings.LiveStream_NoViewers
|
|
||||||
} else {
|
|
||||||
memberCountString = environment.strings.LiveStream_ViewerCount(Int32(originInfo.memberCount))
|
|
||||||
}
|
|
||||||
infoItem = AnyComponent(OriginInfoComponent(
|
infoItem = AnyComponent(OriginInfoComponent(
|
||||||
title: state.callTitle ?? originInfo.title,
|
memberCount: originInfo.memberCount
|
||||||
subtitle: memberCountString
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
let availableSize = context.availableSize
|
let availableSize = context.availableSize
|
||||||
@ -723,7 +743,13 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
if isFullyDragged || state.initialOffset != 0 {
|
if isFullyDragged || state.initialOffset != 0 {
|
||||||
state.updateDismissOffset(value: 0.0, interactive: false)
|
state.updateDismissOffset(value: 0.0, interactive: false)
|
||||||
} else {
|
} else {
|
||||||
let _ = call.leave(terminateIfPossible: false)
|
activatePictureInPicture.invoke(Action {
|
||||||
|
guard let controller = controller() as? MediaStreamComponentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.dismiss(closing: false, manual: true)
|
||||||
|
})
|
||||||
|
// let _ = call.leave(terminateIfPossible: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -757,7 +783,11 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
context.add(dismissTapComponent
|
context.add(dismissTapComponent
|
||||||
.position(CGPoint(x: context.availableSize.width / 2, y: dismissTapAreaHeight / 2))
|
.position(CGPoint(x: context.availableSize.width / 2, y: dismissTapAreaHeight / 2))
|
||||||
.gesture(.tap {
|
.gesture(.tap {
|
||||||
_ = call.leave(terminateIfPossible: false)
|
guard let controller = controller() as? MediaStreamComponentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.dismiss(closing: false, manual: true)
|
||||||
|
// _ = call.leave(terminateIfPossible: false)
|
||||||
})
|
})
|
||||||
.gesture(.pan(onPanGesture))
|
.gesture(.pan(onPanGesture))
|
||||||
)
|
)
|
||||||
@ -955,10 +985,10 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
component: StreamSheetComponent(
|
component: StreamSheetComponent(
|
||||||
topComponent: AnyComponent(navigationComponent),
|
topComponent: AnyComponent(navigationComponent),
|
||||||
bottomButtonsRow: fullScreenToolbarComponent,
|
bottomButtonsRow: fullScreenToolbarComponent,
|
||||||
topOffset: context.availableSize.height - sheetHeight + context.state.dismissOffset,
|
topOffset: /*context.availableSize.height - sheetHeight +*/ max(context.state.dismissOffset, 0),
|
||||||
sheetHeight: max(sheetHeight - context.state.dismissOffset, sheetHeight),
|
sheetHeight: context.availableSize.height,// max(sheetHeight - context.state.dismissOffset, sheetHeight),
|
||||||
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
|
backgroundColor: /*isFullscreen ? .clear : */ (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
|
||||||
bottomPadding: 12,
|
bottomPadding: 0,
|
||||||
participantsCount: -1,
|
participantsCount: -1,
|
||||||
isFullyExtended: isFullyDragged,
|
isFullyExtended: isFullyDragged,
|
||||||
deviceCornerRadius: ((controller() as? MediaStreamComponentController)?.validLayout?.deviceMetrics.screenCornerRadius ?? 1) - 1,
|
deviceCornerRadius: ((controller() as? MediaStreamComponentController)?.validLayout?.deviceMetrics.screenCornerRadius ?? 1) - 1,
|
||||||
@ -1053,13 +1083,15 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
|||||||
self.view.clipsToBounds = false
|
self.view.clipsToBounds = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override public func viewWillAppear(_ animated: Bool) {
|
override public func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func viewDidLayoutSubviews() {
|
override public func viewDidLayoutSubviews() {
|
||||||
super.viewDidLayoutSubviews()
|
super.viewDidLayoutSubviews()
|
||||||
backgroundDimView.frame = .init(x: 0, y: -view.bounds.height * 3, width: view.bounds.width, height: view.bounds.height * 4)
|
let dimViewSide: CGFloat = max(view.bounds.width, view.bounds.height)
|
||||||
|
backgroundDimView.frame = .init(x: view.bounds.midX - dimViewSide / 2, y: -view.bounds.height * 3, width: dimViewSide, height: view.bounds.height * 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dismiss(closing: Bool, manual: Bool) {
|
public func dismiss(closing: Bool, manual: Bool) {
|
||||||
@ -1070,7 +1102,11 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
|||||||
|
|
||||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
self.view.layer.allowsGroupOpacity = true
|
self.view.layer.allowsGroupOpacity = true
|
||||||
self.view.layer.animateAlpha(from: 1.0, to: 1.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
// self.view.layer.animateAlpha(from: 1.0, to: 1.0, duration: 0.4, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
|
//
|
||||||
|
// })
|
||||||
|
self.backgroundDimView.layer.animateAlpha(from: 1.0, to: 0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.view.layer.animatePosition(from: self.view.center, to: CGPoint(x: self.view.center.x, y: self.view.bounds.maxY + self.view.bounds.height / 2), duration: 0.4, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
completion?()
|
completion?()
|
||||||
return
|
return
|
||||||
@ -1078,9 +1114,6 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
|||||||
strongSelf.view.layer.allowsGroupOpacity = false
|
strongSelf.view.layer.allowsGroupOpacity = false
|
||||||
strongSelf.dismissImpl(completion: completion)
|
strongSelf.dismissImpl(completion: completion)
|
||||||
})
|
})
|
||||||
self.backgroundDimView.layer.animateAlpha(from: 1.0, to: 0, duration: 0.3, removeOnCompletion: false)
|
|
||||||
self.view.layer.animatePosition(from: self.view.center, to: CGPoint(x: self.view.center.x, y: self.view.bounds.maxY + self.view.bounds.height / 2), duration: 0.4, completion: { _ in
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissImpl(completion: (() -> Void)? = nil) {
|
private func dismissImpl(completion: (() -> Void)? = nil) {
|
||||||
@ -1272,7 +1305,7 @@ final class StreamTitleComponent: Component {
|
|||||||
if !wasLive {
|
if !wasLive {
|
||||||
wasLive = true
|
wasLive = true
|
||||||
let anim = CAKeyframeAnimation(keyPath: "transform.scale")
|
let anim = CAKeyframeAnimation(keyPath: "transform.scale")
|
||||||
anim.values = [1.0, 1.4, 0.9, 1.0]
|
anim.values = [1.0, 1.12, 0.9, 1.0]
|
||||||
anim.keyTimes = [0, 0.5, 0.8, 1]
|
anim.keyTimes = [0, 0.5, 0.8, 1]
|
||||||
anim.duration = 0.4
|
anim.duration = 0.4
|
||||||
self.layer.add(anim, forKey: "transform")
|
self.layer.add(anim, forKey: "transform")
|
||||||
@ -1281,7 +1314,7 @@ final class StreamTitleComponent: Component {
|
|||||||
self.toggle(isLive: true) })
|
self.toggle(isLive: true) })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.backgroundColor = UIColor(red: 0.82, green: 0.26, blue: 0.37, alpha: 1)
|
self.backgroundColor = UIColor(red: 1, green: 0.176, blue: 0.333, alpha: 1)
|
||||||
stalledAnimatedGradient.opacity = 0
|
stalledAnimatedGradient.opacity = 0
|
||||||
stalledAnimatedGradient.removeAllAnimations()
|
stalledAnimatedGradient.removeAllAnimations()
|
||||||
} else {
|
} else {
|
||||||
@ -1300,21 +1333,87 @@ final class StreamTitleComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIView {
|
public final class View: UIView {
|
||||||
private let textView: ComponentHostView<Empty>
|
|
||||||
private var indicatorView: UIImageView?
|
private var indicatorView: UIImageView?
|
||||||
let liveIndicatorView = LiveIndicatorView()
|
let liveIndicatorView = LiveIndicatorView()
|
||||||
let titleLabel = UILabel()
|
let titleLabel = UILabel()
|
||||||
|
|
||||||
|
private let titleFadeLayer = CALayer()
|
||||||
|
|
||||||
private let trackingLayer: HierarchyTrackingLayer
|
private let trackingLayer: HierarchyTrackingLayer
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
private func updateTitleFadeLayer(textFrame: CGRect) {
|
||||||
self.textView = ComponentHostView<Empty>()
|
// titleLabel.backgroundColor = .red
|
||||||
|
guard let string = titleLabel.attributedText,
|
||||||
|
string.boundingRect(with: .init(width: .max, height: .max), context: nil).width > textFrame.width
|
||||||
|
else {
|
||||||
|
titleLabel.layer.mask = nil
|
||||||
|
titleLabel.frame = textFrame
|
||||||
|
self.titleLabel.textAlignment = .center
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isRTL: Bool = false
|
||||||
|
if let string = titleLabel.attributedText {
|
||||||
|
let coreTextLine = CTLineCreateWithAttributedString(string)
|
||||||
|
let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray
|
||||||
|
if glyphRuns.count > 0 {
|
||||||
|
let run = glyphRuns[0] as! CTRun
|
||||||
|
if CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) {
|
||||||
|
isRTL = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let gradientInset: CGFloat = 0
|
||||||
|
let gradientRadius: CGFloat = 50
|
||||||
|
|
||||||
|
let solidPartLayer = CALayer()
|
||||||
|
solidPartLayer.backgroundColor = UIColor.black.cgColor
|
||||||
|
|
||||||
|
let containerWidth: CGFloat = textFrame.width
|
||||||
|
let availableWidth: CGFloat = textFrame.width - gradientRadius
|
||||||
|
|
||||||
|
let extraSpace: CGFloat = 100
|
||||||
|
if isRTL {
|
||||||
|
let adjustForRTL: CGFloat = 12
|
||||||
|
|
||||||
|
let safeSolidWidth: CGFloat = containerWidth + adjustForRTL
|
||||||
|
solidPartLayer.frame = CGRect(
|
||||||
|
origin: CGPoint(x: max(containerWidth - availableWidth, gradientRadius), y: 0),
|
||||||
|
size: CGSize(width: safeSolidWidth, height: textFrame.height))
|
||||||
|
titleLabel.frame = CGRect(x: textFrame.minX - extraSpace, y: textFrame.minY, width: textFrame.width + extraSpace, height: textFrame.height)
|
||||||
|
} else {
|
||||||
|
solidPartLayer.frame = CGRect(
|
||||||
|
origin: .zero,
|
||||||
|
size: CGSize(width: availableWidth, height: textFrame.height))
|
||||||
|
titleLabel.frame = CGRect(origin: textFrame.origin, size: CGSize(width: textFrame.width + extraSpace, height: textFrame.height))
|
||||||
|
}
|
||||||
|
self.titleLabel.textAlignment = .natural
|
||||||
|
titleFadeLayer.addSublayer(solidPartLayer)
|
||||||
|
|
||||||
|
let gradientLayer = CAGradientLayer()
|
||||||
|
gradientLayer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
|
||||||
|
if isRTL {
|
||||||
|
gradientLayer.startPoint = CGPoint(x: 1, y: 0.5)
|
||||||
|
gradientLayer.endPoint = CGPoint(x: 0, y: 0.5)
|
||||||
|
gradientLayer.frame = CGRect(x: solidPartLayer.frame.minX - gradientRadius, y: 0, width: gradientRadius, height: textFrame.height)
|
||||||
|
} else {
|
||||||
|
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
|
||||||
|
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
|
||||||
|
gradientLayer.frame = CGRect(x: availableWidth + gradientInset, y: 0, width: gradientRadius, height: textFrame.height)
|
||||||
|
}
|
||||||
|
titleFadeLayer.addSublayer(gradientLayer)
|
||||||
|
titleFadeLayer.masksToBounds = false
|
||||||
|
|
||||||
|
titleFadeLayer.frame = titleLabel.bounds
|
||||||
|
titleLabel.layer.mask = titleFadeLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
self.trackingLayer = HierarchyTrackingLayer()
|
self.trackingLayer = HierarchyTrackingLayer()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
// self.addSubview(self.textView)
|
|
||||||
self.addSubview(self.titleLabel)
|
self.addSubview(self.titleLabel)
|
||||||
self.addSubview(self.liveIndicatorView)
|
self.addSubview(self.liveIndicatorView)
|
||||||
|
|
||||||
@ -1350,7 +1449,6 @@ final class StreamTitleComponent: Component {
|
|||||||
self.titleLabel.text = component.text
|
self.titleLabel.text = component.text
|
||||||
self.titleLabel.font = Font.semibold(17.0)
|
self.titleLabel.font = Font.semibold(17.0)
|
||||||
self.titleLabel.textColor = .white
|
self.titleLabel.textColor = .white
|
||||||
self.titleLabel.textAlignment = .center
|
|
||||||
self.titleLabel.numberOfLines = 1
|
self.titleLabel.numberOfLines = 1
|
||||||
self.titleLabel.invalidateIntrinsicContentSize()
|
self.titleLabel.invalidateIntrinsicContentSize()
|
||||||
|
|
||||||
@ -1385,7 +1483,7 @@ final class StreamTitleComponent: Component {
|
|||||||
let size = CGSize(width: textSize.width + sideInset * 2.0, height: textSize.height)
|
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)
|
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - textSize.height) / 2.0)), size: textSize)
|
||||||
// self.textView.frame = textFrame
|
// self.textView.frame = textFrame
|
||||||
self.titleLabel.frame = textFrame
|
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 - 22 / 2), size: .init(width: 40, height: 22))
|
||||||
self.liveIndicatorView.toggle(isLive: component.isActive)
|
self.liveIndicatorView.toggle(isLive: component.isActive)
|
||||||
@ -1413,16 +1511,20 @@ private final class NavigationBarComponent: CombinedComponent {
|
|||||||
let leftItem: AnyComponent<Empty>?
|
let leftItem: AnyComponent<Empty>?
|
||||||
let rightItems: [AnyComponentWithIdentity<Empty>]
|
let rightItems: [AnyComponentWithIdentity<Empty>]
|
||||||
let centerItem: AnyComponent<Empty>?
|
let centerItem: AnyComponent<Empty>?
|
||||||
|
let backgroundVisible: Bool
|
||||||
|
|
||||||
init(
|
init(
|
||||||
topInset: CGFloat,
|
topInset: CGFloat,
|
||||||
sideInset: CGFloat,
|
sideInset: CGFloat,
|
||||||
|
backgroundVisible: Bool,
|
||||||
leftItem: AnyComponent<Empty>?,
|
leftItem: AnyComponent<Empty>?,
|
||||||
rightItems: [AnyComponentWithIdentity<Empty>],
|
rightItems: [AnyComponentWithIdentity<Empty>],
|
||||||
centerItem: AnyComponent<Empty>?
|
centerItem: AnyComponent<Empty>?
|
||||||
) {
|
) {
|
||||||
self.topInset = 0 // topInset
|
self.topInset = 0 // topInset
|
||||||
self.sideInset = sideInset
|
self.sideInset = sideInset
|
||||||
|
self.backgroundVisible = backgroundVisible
|
||||||
|
|
||||||
self.leftItem = leftItem
|
self.leftItem = leftItem
|
||||||
self.rightItems = rightItems
|
self.rightItems = rightItems
|
||||||
self.centerItem = centerItem
|
self.centerItem = centerItem
|
||||||
@ -1449,6 +1551,7 @@ private final class NavigationBarComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
|
let background = Child(Rectangle.self)
|
||||||
let leftItem = Child(environment: Empty.self)
|
let leftItem = Child(environment: Empty.self)
|
||||||
let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
||||||
let centerItem = Child(environment: Empty.self)
|
let centerItem = Child(environment: Empty.self)
|
||||||
@ -1460,6 +1563,8 @@ private final class NavigationBarComponent: CombinedComponent {
|
|||||||
let contentHeight: CGFloat = 44.0
|
let contentHeight: CGFloat = 44.0
|
||||||
let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight)
|
let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight)
|
||||||
|
|
||||||
|
let background = background.update(component: Rectangle(color: UIColor(white: 0.0, alpha: context.component.backgroundVisible ? 0.5 : 0)), availableSize: CGSize(width: size.width, height: size.height), transition: context.transition)
|
||||||
|
|
||||||
let leftItem = context.component.leftItem.flatMap { leftItemComponent in
|
let leftItem = context.component.leftItem.flatMap { leftItemComponent in
|
||||||
return leftItem.update(
|
return leftItem.update(
|
||||||
component: leftItemComponent,
|
component: leftItemComponent,
|
||||||
@ -1493,6 +1598,10 @@ private final class NavigationBarComponent: CombinedComponent {
|
|||||||
availableWidth -= centerItem.size.width
|
availableWidth -= centerItem.size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.add(background
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
var centerLeftInset = sideInset
|
var centerLeftInset = sideInset
|
||||||
if let leftItem = leftItem {
|
if let leftItem = leftItem {
|
||||||
context.add(leftItem
|
context.add(leftItem
|
||||||
@ -1522,22 +1631,18 @@ private final class NavigationBarComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class OriginInfoComponent: CombinedComponent {
|
private final class OriginInfoComponent: CombinedComponent {
|
||||||
let title: String
|
let participantsCount: Int
|
||||||
let subtitle: String
|
|
||||||
|
private static var usingAnimatedCounter: Bool { true }
|
||||||
|
|
||||||
init(
|
init(
|
||||||
title: String,
|
memberCount: Int
|
||||||
subtitle: String
|
|
||||||
) {
|
) {
|
||||||
self.title = title
|
self.participantsCount = memberCount
|
||||||
self.subtitle = subtitle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: OriginInfoComponent, rhs: OriginInfoComponent) -> Bool {
|
static func ==(lhs: OriginInfoComponent, rhs: OriginInfoComponent) -> Bool {
|
||||||
if lhs.title != rhs.title {
|
if lhs.participantsCount != rhs.participantsCount {
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.subtitle != rhs.subtitle {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1545,38 +1650,63 @@ private final class OriginInfoComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
let title = Child(Text.self)
|
if usingAnimatedCounter {
|
||||||
let subtitle = Child(Text.self)
|
let viewerCounter = Child(ParticipantsComponent.self)
|
||||||
|
|
||||||
return { context in
|
|
||||||
let spacing: CGFloat = 0.0
|
|
||||||
|
|
||||||
let title = title.update(
|
return { context in
|
||||||
component: Text(
|
// let spacing: CGFloat = 0.0
|
||||||
text: context.component.title, font: Font.semibold(17.0), color: .white),
|
|
||||||
availableSize: context.availableSize,
|
let viewerCounter = viewerCounter.update(
|
||||||
transition: context.transition
|
component: ParticipantsComponent(
|
||||||
)
|
count: context.component.participantsCount,
|
||||||
|
showsSubtitle: true,
|
||||||
|
fontSize: 24
|
||||||
|
),
|
||||||
|
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
|
||||||
|
var size = CGSize(width: viewerCounter.size.width, height: viewerCounter.size.height)
|
||||||
|
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: viewerCounter.size.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let subtitle = Child(Text.self)
|
||||||
|
|
||||||
let subtitle = subtitle.update(
|
return { context in
|
||||||
component: Text(
|
// let spacing: CGFloat = 0.0
|
||||||
text: context.component.subtitle, font: Font.regular(14.0), color: .white),
|
|
||||||
availableSize: context.availableSize,
|
let memberCount = context.component.participantsCount
|
||||||
transition: context.transition
|
let memberCountString: String
|
||||||
)
|
if memberCount == 0 {
|
||||||
|
memberCountString = "no viewers"
|
||||||
var size = CGSize(width: max(title.size.width, subtitle.size.width), height: title.size.height + spacing + subtitle.size.height)
|
} else {
|
||||||
size.width = min(size.width, context.availableSize.width)
|
memberCountString = memberCount > 0 ? presentationStringsFormattedNumber(Int32(memberCount), ",") : ""
|
||||||
size.height = min(size.height, context.availableSize.height)
|
}
|
||||||
|
|
||||||
context.add(title
|
let subtitle = subtitle.update(
|
||||||
.position(CGPoint(x: size.width / 2.0, y: title.size.height / 2.0))
|
component: Text(
|
||||||
)
|
text: memberCountString, font: Font.regular(14.0), color: .white),
|
||||||
context.add(subtitle
|
availableSize: context.availableSize,
|
||||||
.position(CGPoint(x: size.width / 2.0, y: title.size.height + spacing + subtitle.size.height / 2.0))
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
return size
|
var size = CGSize(width: subtitle.size.width, height: subtitle.size.height)
|
||||||
|
size.width = min(size.width, context.availableSize.width)
|
||||||
|
size.height = min(size.height, context.availableSize.height)
|
||||||
|
|
||||||
|
context.add(subtitle
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: subtitle.size.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1635,7 +1765,7 @@ private final class ToolbarComponent: CombinedComponent {
|
|||||||
let contentHeight: CGFloat = 44.0
|
let contentHeight: CGFloat = 44.0
|
||||||
let size = CGSize(width: context.availableSize.width, height: contentHeight + context.component.bottomInset)
|
let size = CGSize(width: context.availableSize.width, height: contentHeight + context.component.bottomInset)
|
||||||
|
|
||||||
let background = background.update(component: Rectangle(color: UIColor(white: 0.0, alpha: 0)), availableSize: CGSize(width: size.width, height: size.height), transition: context.transition)
|
let background = background.update(component: Rectangle(color: UIColor(white: 0.0, alpha: 0.5)), availableSize: CGSize(width: size.width, height: size.height), transition: context.transition)
|
||||||
|
|
||||||
let leftItem = context.component.leftItem.flatMap { leftItemComponent in
|
let leftItem = context.component.leftItem.flatMap { leftItemComponent in
|
||||||
return leftItem.update(
|
return leftItem.update(
|
||||||
@ -1659,10 +1789,11 @@ private final class ToolbarComponent: CombinedComponent {
|
|||||||
availableWidth -= rightItem.size.width
|
availableWidth -= rightItem.size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let temporaryOffsetForSmallerSubtitle: CGFloat = 12
|
||||||
let centerItem = context.component.centerItem.flatMap { centerItemComponent in
|
let centerItem = context.component.centerItem.flatMap { centerItemComponent in
|
||||||
return centerItem.update(
|
return centerItem.update(
|
||||||
component: centerItemComponent,
|
component: centerItemComponent,
|
||||||
availableSize: CGSize(width: availableWidth, height: contentHeight),
|
availableSize: CGSize(width: availableWidth, height: contentHeight - temporaryOffsetForSmallerSubtitle / 2),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1693,7 +1824,7 @@ private final class ToolbarComponent: CombinedComponent {
|
|||||||
let maxCenterInset = max(centerLeftInset, centerRightInset)
|
let maxCenterInset = max(centerLeftInset, centerRightInset)
|
||||||
if let centerItem = centerItem {
|
if let centerItem = centerItem {
|
||||||
context.add(centerItem
|
context.add(centerItem
|
||||||
.position(CGPoint(x: maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0, y: contentHeight / 2.0))
|
.position(CGPoint(x: maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0, y: contentHeight / 2.0 - temporaryOffsetForSmallerSubtitle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -214,7 +214,8 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
loadingBlurView.layer.add(anim, forKey: "opacity")
|
loadingBlurView.layer.add(anim, forKey: "opacity")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadingBlurView.layer.zPosition = 999
|
loadingBlurView.layer.zPosition = 998
|
||||||
|
self.noSignalView?.layer.zPosition = loadingBlurView.layer.zPosition + 1
|
||||||
if shimmerBorderLayer.superlayer == nil {
|
if shimmerBorderLayer.superlayer == nil {
|
||||||
loadingBlurView.contentView.layer.addSublayer(shimmerBorderLayer)
|
loadingBlurView.contentView.layer.addSublayer(shimmerBorderLayer)
|
||||||
}
|
}
|
||||||
@ -230,9 +231,10 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
|
borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
|
||||||
borderMask.strokeColor = UIColor.white.withAlphaComponent(0.7).cgColor
|
borderMask.strokeColor = UIColor.white.withAlphaComponent(0.7).cgColor
|
||||||
borderMask.lineWidth = 3
|
borderMask.lineWidth = 3
|
||||||
|
borderMask.compositingFilter = "softLightBlendMode"
|
||||||
shimmerBorderLayer.mask = borderMask
|
shimmerBorderLayer.mask = borderMask
|
||||||
|
|
||||||
borderShimmer = .init()
|
borderShimmer = StandaloneShimmerEffect()
|
||||||
borderShimmer.layer = shimmerBorderLayer
|
borderShimmer.layer = shimmerBorderLayer
|
||||||
borderShimmer.updateHorizontal(background: .clear, foreground: .white)
|
borderShimmer.updateHorizontal(background: .clear, foreground: .white)
|
||||||
loadingBlurView.alpha = 1
|
loadingBlurView.alpha = 1
|
||||||
@ -314,7 +316,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
UIView.animate(withDuration: 0.3) {
|
UIView.animate(withDuration: 0.3) {
|
||||||
videoBlurView.alpha = 1
|
videoBlurView.alpha = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
self.maskGradientLayer.type = .radial
|
self.maskGradientLayer.type = .radial
|
||||||
self.maskGradientLayer.colors = [UIColor(rgb: 0x000000, alpha: 0.5).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
|
self.maskGradientLayer.colors = [UIColor(rgb: 0x000000, alpha: 0.5).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
|
||||||
self.maskGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
self.maskGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
||||||
@ -409,13 +410,18 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
} else if component.isFullscreen {
|
} else if component.isFullscreen {
|
||||||
if fullScreenBackgroundPlaceholder.superview == nil {
|
if fullScreenBackgroundPlaceholder.superview == nil {
|
||||||
insertSubview(fullScreenBackgroundPlaceholder, at: 0)
|
insertSubview(fullScreenBackgroundPlaceholder, at: 0)
|
||||||
|
transition.animateAlpha(view: fullScreenBackgroundPlaceholder, from: 0, to: 1)
|
||||||
}
|
}
|
||||||
fullScreenBackgroundPlaceholder.backgroundColor = UIColor.black.withAlphaComponent(0.5)
|
fullScreenBackgroundPlaceholder.backgroundColor = UIColor.black.withAlphaComponent(0.5)
|
||||||
} else {
|
} else {
|
||||||
fullScreenBackgroundPlaceholder.removeFromSuperview()
|
transition.animateAlpha(view: fullScreenBackgroundPlaceholder, from: 1, to: 0, completion: { didComplete in
|
||||||
|
if didComplete {
|
||||||
|
self.fullScreenBackgroundPlaceholder.removeFromSuperview()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize)
|
fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize)
|
||||||
|
// fullScreenBackgroundPlaceholder.isHidden = true
|
||||||
let videoInset: CGFloat
|
let videoInset: CGFloat
|
||||||
if !component.isFullscreen {
|
if !component.isFullscreen {
|
||||||
videoInset = 16
|
videoInset = 16
|
||||||
@ -556,6 +562,8 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
self.noSignalView = noSignalView
|
self.noSignalView = noSignalView
|
||||||
// TODO: above blurred animation
|
// TODO: above blurred animation
|
||||||
self.addSubview(noSignalView)
|
self.addSubview(noSignalView)
|
||||||
|
noSignalView.layer.zPosition = loadingBlurView.layer.zPosition + 1
|
||||||
|
|
||||||
noSignalView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
noSignalView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -95,12 +95,12 @@ final class StreamSheetComponent: CombinedComponent {
|
|||||||
override func draw(_ rect: CGRect) {
|
override func draw(_ rect: CGRect) {
|
||||||
super.draw(rect)
|
super.draw(rect)
|
||||||
// Debug interactive area
|
// Debug interactive area
|
||||||
// guard let context = UIGraphicsGetCurrentContext() else { return }
|
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||||
// context.setFillColor(UIColor.red.cgColor)
|
context.setFillColor(UIColor.red.withAlphaComponent(0.3).cgColor)
|
||||||
// overlayComponentsFrames.forEach { frame in
|
overlayComponentsFrames.forEach { frame in
|
||||||
// context.addRect(frame)
|
context.addRect(frame)
|
||||||
// context.fillPath()
|
context.fillPath()
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +172,8 @@ final class StreamSheetComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// TODO: replace
|
||||||
|
let isFullscreen = context.component.participantsCount == -1
|
||||||
|
|
||||||
context.add(background
|
context.add(background
|
||||||
.position(CGPoint(x: size.width / 2.0, y: topOffset + context.component.sheetHeight / 2))
|
.position(CGPoint(x: size.width / 2.0, y: topOffset + context.component.sheetHeight / 2))
|
||||||
@ -182,7 +184,7 @@ final class StreamSheetComponent: CombinedComponent {
|
|||||||
|
|
||||||
if let topItem = topItem {
|
if let topItem = topItem {
|
||||||
context.add(topItem
|
context.add(topItem
|
||||||
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + 32))
|
.position(CGPoint(x: topItem.size.width / 2.0, y: topOffset + (isFullscreen ? topItem.size.height / 2.0 : 32)))
|
||||||
)
|
)
|
||||||
(context.view as? StreamSheetComponent.View)?.overlayComponentsFrames.append(.init(x: 0, y: topOffset, width: topItem.size.width, height: topItem.size.height))
|
(context.view as? StreamSheetComponent.View)?.overlayComponentsFrames.append(.init(x: 0, y: topOffset, width: topItem.size.width, height: topItem.size.height))
|
||||||
}
|
}
|
||||||
@ -297,16 +299,21 @@ final class ParticipantsComponent: Component {
|
|||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: ComponentFlow.EmptyComponentState, environment: ComponentFlow.Environment<ComponentFlow.Empty>, transition: ComponentFlow.Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: ComponentFlow.EmptyComponentState, environment: ComponentFlow.Environment<ComponentFlow.Empty>, transition: ComponentFlow.Transition) -> CGSize {
|
||||||
view.counter.update(
|
view.counter.update(
|
||||||
countString: count > 0 ? presentationStringsFormattedNumber(Int32(count), ",") : "",
|
countString: self.count > 0 ? presentationStringsFormattedNumber(Int32(count), ",") : "",
|
||||||
subtitle: count > 0 ? "watching" : "no viewers"
|
subtitle: self.showsSubtitle ? (self.count > 0 ? "watching" : "no viewers") : "",
|
||||||
|
fontSize: self.fontSize
|
||||||
)// environment.strings.LiveStream_NoViewers)
|
)// environment.strings.LiveStream_NoViewers)
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
private let count: Int
|
private let count: Int
|
||||||
|
private let showsSubtitle: Bool
|
||||||
|
private let fontSize: CGFloat
|
||||||
|
|
||||||
init(count: Int) {
|
init(count: Int, showsSubtitle: Bool = true, fontSize: CGFloat = 48) {
|
||||||
self.count = count
|
self.count = count
|
||||||
|
self.showsSubtitle = showsSubtitle
|
||||||
|
self.fontSize = fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: UIView {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user