diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8c8b5d7293..075a3a5af3 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5956,7 +5956,7 @@ Sorry for the inconvenience."; "LiveStream.RecordingInProgress" = "Live stream is being recorded"; "VoiceChat.StopRecordingTitle" = "Stop Recording?"; -"VoiceChat.StopRecordingStop" = "Stop"; +"VoiceChat.StopRecordingStop" = "Stop Recording"; "VoiceChat.RecordingSaved" = "Audio saved to **Saved Messages**."; diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index dd197c7e08..a70d214822 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -46,6 +46,9 @@ public enum ContextMenuActionItemTextColor { public enum ContextMenuActionResult { case `default` case dismissWithoutContent + /// Temporary + static var safeStreamRecordingDismissWithoutContent: ContextMenuActionResult { .dismissWithoutContent } + case custom(ContainedViewLayoutTransition) } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index e1cb4444da..2d7528c7a4 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -403,16 +403,19 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C private let getController: () -> ContextControllerProtocol? private let item: ContextMenuCustomItem + private let requestDismiss: (ContextMenuActionResult) -> Void private var presentationData: PresentationData? private var itemNode: ContextMenuCustomNode? init( getController: @escaping () -> ContextControllerProtocol?, - item: ContextMenuCustomItem + item: ContextMenuCustomItem, + requestDismiss: @escaping (ContextMenuActionResult) -> Void ) { self.getController = getController self.item = item + self.requestDismiss = requestDismiss super.init() } @@ -433,7 +436,12 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C presentationData: presentationData, getController: self.getController, actionSelected: { result in - let _ = result + switch result { + case .dismissWithoutContent/* where ContextMenuActionResult.safeStreamRecordingDismissWithoutContent == .dismissWithoutContent*/: + self.requestDismiss(result) + + default: break + } } ) self.itemNode = itemNode @@ -505,7 +513,8 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack return Item( node: ContextControllerActionsListCustomItemNode( getController: getController, - item: customItem + item: customItem, + requestDismiss: requestDismiss ), separatorNode: ASDisplayNode() ) diff --git a/submodules/ShimmerEffect/Sources/ShimmerEffect.swift b/submodules/ShimmerEffect/Sources/ShimmerEffect.swift index 1a35997e17..f094ffa613 100644 --- a/submodules/ShimmerEffect/Sources/ShimmerEffect.swift +++ b/submodules/ShimmerEffect/Sources/ShimmerEffect.swift @@ -500,7 +500,7 @@ public final class StandaloneShimmerEffect { let colorSpace = CGColorSpaceCreateDeviceRGB() 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() @@ -533,14 +533,28 @@ public final class StandaloneShimmerEffect { layer.contents = image.cgImage if layer.animation(forKey: "shimmer") == nil { + var delay: TimeInterval { 1.6 } let animation = CABasicAnimation(keyPath: "contentsRect.origin.x") - animation.fromValue = 1.0 as NSNumber - animation.toValue = -1.0 as NSNumber + animation.fromValue = NSNumber(floatLiteral: delay) + animation.toValue = NSNumber(floatLiteral: -delay) animation.isAdditive = true animation.repeatCount = .infinity - animation.duration = 0.8 - animation.beginTime = layer.convertTime(1.0, from: nil) + animation.duration = 0.8 * delay + animation.timingFunction = .init(name: .easeInEaseOut) +// animation.beginTime = layer.convertTime(1.0, from: nil) 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")*/ } } } diff --git a/submodules/TelegramCallsUI/Sources/Components/AnimatedCounterView.swift b/submodules/TelegramCallsUI/Sources/Components/AnimatedCounterView.swift index 969baac579..10bc86ecb5 100644 --- a/submodules/TelegramCallsUI/Sources/Components/AnimatedCounterView.swift +++ b/submodules/TelegramCallsUI/Sources/Components/AnimatedCounterView.swift @@ -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) } - func update(countString: String, subtitle: String) { + func update(countString: String, subtitle: String, fontSize: CGFloat = 48.0) { self.setupGradientAnimations() let text: String = countString - self.countLabel.fontSize = 48 - self.countLabel.attributedText = NSAttributedString(string: text, font: Font.with(size: 48, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white) + 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: 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 } diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index 99a888fe66..a607300f84 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -170,7 +170,9 @@ public final class MediaStreamComponent: CombinedComponent { var updated = false // TODO: remove debug timer // 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 guard let strongSelf = strongSelf else { return } var updated = false @@ -411,7 +413,9 @@ public final class MediaStreamComponent: CombinedComponent { var navigationRightItems: [AnyComponentWithIdentity] = [] - 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( content: AnyComponent(ZStack([ AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle( @@ -420,7 +424,7 @@ public final class MediaStreamComponent: CombinedComponent { ))), AnyComponentWithIdentity(id: "a", component: AnyComponent(BundleIconComponent( 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))))) } var topLeftButton: AnyComponent? + if context.state.canManageCall { let whiteColor = UIColor(white: 1.0, alpha: 1.0) 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 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 { return } @@ -507,12 +512,11 @@ public final class MediaStreamComponent: CombinedComponent { }) controller.present(editController, in: .window(.root)) - a(.default) + dismissWithResult(.default) }))) if let recordingStartTimestamp = state.recordingStartTimestamp { - items.append(.custom(VoiceChatRecordingContextItem(timestamp: recordingStartTimestamp, action: { [weak call, weak controller] _, f in - f(.dismissWithoutContent) + items.append(.custom(VoiceChatRecordingContextItem(timestamp: recordingStartTimestamp, action: { [weak call, weak controller] _, dismissWithResult in guard let call = call, let controller = controller else { return @@ -547,6 +551,8 @@ public final class MediaStreamComponent: CombinedComponent { })*/ })]) controller.present(alertController, in: .window(.root)) + // TODO: спросить про dismissWithoutContent и default + dismissWithResult(.dismissWithoutContent) }), false)) } else { let text = presentationData.strings.LiveStream_StartRecording @@ -605,14 +611,34 @@ public final class MediaStreamComponent: CombinedComponent { 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) }, action: { [weak call] _, a in guard let call = call else { return } - - let _ = call.leave(terminateIfPossible: true).start() + let alertController = textAlertController( + 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) }))) @@ -669,9 +695,10 @@ public final class MediaStreamComponent: CombinedComponent { let navigationComponent = NavigationBarComponent( topInset: environment.statusBarHeight, sideInset: environment.safeInsets.left, + backgroundVisible: isFullscreen, leftItem: topLeftButton, 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 { @@ -685,15 +712,8 @@ public final class MediaStreamComponent: CombinedComponent { var infoItem: AnyComponent? 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( - title: state.callTitle ?? originInfo.title, - subtitle: memberCountString + memberCount: originInfo.memberCount )) } let availableSize = context.availableSize @@ -723,7 +743,13 @@ public final class MediaStreamComponent: CombinedComponent { if isFullyDragged || state.initialOffset != 0 { state.updateDismissOffset(value: 0.0, interactive: false) } 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 { @@ -757,7 +783,11 @@ public final class MediaStreamComponent: CombinedComponent { context.add(dismissTapComponent .position(CGPoint(x: context.availableSize.width / 2, y: dismissTapAreaHeight / 2)) .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)) ) @@ -955,10 +985,10 @@ public final class MediaStreamComponent: CombinedComponent { component: StreamSheetComponent( topComponent: AnyComponent(navigationComponent), bottomButtonsRow: fullScreenToolbarComponent, - topOffset: context.availableSize.height - sheetHeight + context.state.dismissOffset, - sheetHeight: max(sheetHeight - context.state.dismissOffset, sheetHeight), - backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor), - bottomPadding: 12, + topOffset: /*context.availableSize.height - sheetHeight +*/ max(context.state.dismissOffset, 0), + sheetHeight: context.availableSize.height,// max(sheetHeight - context.state.dismissOffset, sheetHeight), + backgroundColor: /*isFullscreen ? .clear : */ (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor), + bottomPadding: 0, participantsCount: -1, isFullyExtended: isFullyDragged, deviceCornerRadius: ((controller() as? MediaStreamComponentController)?.validLayout?.deviceMetrics.screenCornerRadius ?? 1) - 1, @@ -1053,13 +1083,15 @@ public final class MediaStreamComponentController: ViewControllerComponentContai self.view.clipsToBounds = false } + override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override public func 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) { @@ -1070,7 +1102,11 @@ public final class MediaStreamComponentController: ViewControllerComponentContai override public func dismiss(completion: (() -> Void)? = nil) { 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 { completion?() return @@ -1078,9 +1114,6 @@ public final class MediaStreamComponentController: ViewControllerComponentContai strongSelf.view.layer.allowsGroupOpacity = false 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) { @@ -1272,7 +1305,7 @@ final class StreamTitleComponent: Component { if !wasLive { wasLive = true 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.duration = 0.4 self.layer.add(anim, forKey: "transform") @@ -1281,7 +1314,7 @@ final class StreamTitleComponent: Component { self.toggle(isLive: true) }) 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.removeAllAnimations() } else { @@ -1300,21 +1333,87 @@ final class StreamTitleComponent: Component { } public final class View: UIView { - private let textView: ComponentHostView private var indicatorView: UIImageView? let liveIndicatorView = LiveIndicatorView() let titleLabel = UILabel() + private let titleFadeLayer = CALayer() + private let trackingLayer: HierarchyTrackingLayer - override init(frame: CGRect) { - self.textView = ComponentHostView() + private func updateTitleFadeLayer(textFrame: CGRect) { + // 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() super.init(frame: frame) -// self.addSubview(self.textView) self.addSubview(self.titleLabel) self.addSubview(self.liveIndicatorView) @@ -1350,7 +1449,6 @@ final class StreamTitleComponent: Component { self.titleLabel.text = component.text self.titleLabel.font = Font.semibold(17.0) self.titleLabel.textColor = .white - self.titleLabel.textAlignment = .center self.titleLabel.numberOfLines = 1 self.titleLabel.invalidateIntrinsicContentSize() @@ -1385,7 +1483,7 @@ final class StreamTitleComponent: Component { 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 - 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)) self.liveIndicatorView.toggle(isLive: component.isActive) @@ -1413,16 +1511,20 @@ private final class NavigationBarComponent: CombinedComponent { let leftItem: AnyComponent? let rightItems: [AnyComponentWithIdentity] let centerItem: AnyComponent? + let backgroundVisible: Bool init( topInset: CGFloat, sideInset: CGFloat, + backgroundVisible: Bool, leftItem: AnyComponent?, rightItems: [AnyComponentWithIdentity], centerItem: AnyComponent? ) { self.topInset = 0 // topInset self.sideInset = sideInset + self.backgroundVisible = backgroundVisible + self.leftItem = leftItem self.rightItems = rightItems self.centerItem = centerItem @@ -1449,6 +1551,7 @@ private final class NavigationBarComponent: CombinedComponent { } static var body: Body { + let background = Child(Rectangle.self) let leftItem = Child(environment: Empty.self) let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) let centerItem = Child(environment: Empty.self) @@ -1460,6 +1563,8 @@ private final class NavigationBarComponent: CombinedComponent { let contentHeight: CGFloat = 44.0 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 return leftItem.update( component: leftItemComponent, @@ -1493,6 +1598,10 @@ private final class NavigationBarComponent: CombinedComponent { availableWidth -= centerItem.size.width } + context.add(background + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + ) + var centerLeftInset = sideInset if let leftItem = leftItem { context.add(leftItem @@ -1522,22 +1631,18 @@ private final class NavigationBarComponent: CombinedComponent { } private final class OriginInfoComponent: CombinedComponent { - let title: String - let subtitle: String + let participantsCount: Int + + private static var usingAnimatedCounter: Bool { true } init( - title: String, - subtitle: String + memberCount: Int ) { - self.title = title - self.subtitle = subtitle + self.participantsCount = memberCount } static func ==(lhs: OriginInfoComponent, rhs: OriginInfoComponent) -> Bool { - if lhs.title != rhs.title { - return false - } - if lhs.subtitle != rhs.subtitle { + if lhs.participantsCount != rhs.participantsCount { return false } @@ -1545,38 +1650,63 @@ private final class OriginInfoComponent: CombinedComponent { } static var body: Body { - let title = Child(Text.self) - let subtitle = Child(Text.self) - - return { context in - let spacing: CGFloat = 0.0 + if usingAnimatedCounter { + let viewerCounter = Child(ParticipantsComponent.self) - let title = title.update( - component: Text( - text: context.component.title, font: Font.semibold(17.0), color: .white), - availableSize: context.availableSize, - transition: context.transition - ) + return { context in +// let spacing: CGFloat = 0.0 + + let viewerCounter = viewerCounter.update( + 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( - component: Text( - text: context.component.subtitle, font: Font.regular(14.0), color: .white), - availableSize: context.availableSize, - transition: context.transition - ) - - var size = CGSize(width: max(title.size.width, subtitle.size.width), height: title.size.height + spacing + subtitle.size.height) - size.width = min(size.width, context.availableSize.width) - size.height = min(size.height, context.availableSize.height) - - context.add(title - .position(CGPoint(x: size.width / 2.0, y: title.size.height / 2.0)) - ) - context.add(subtitle - .position(CGPoint(x: size.width / 2.0, y: title.size.height + spacing + subtitle.size.height / 2.0)) - ) - - return size + return { context in +// let spacing: CGFloat = 0.0 + + let memberCount = context.component.participantsCount + let memberCountString: String + if memberCount == 0 { + memberCountString = "no viewers" + } else { + memberCountString = memberCount > 0 ? presentationStringsFormattedNumber(Int32(memberCount), ",") : "" + } + + let subtitle = subtitle.update( + component: Text( + text: memberCountString, font: Font.regular(14.0), color: .white), + availableSize: context.availableSize, + transition: context.transition + ) + + 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 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 return leftItem.update( @@ -1659,10 +1789,11 @@ private final class ToolbarComponent: CombinedComponent { availableWidth -= rightItem.size.width } + let temporaryOffsetForSmallerSubtitle: CGFloat = 12 let centerItem = context.component.centerItem.flatMap { centerItemComponent in return centerItem.update( component: centerItemComponent, - availableSize: CGSize(width: availableWidth, height: contentHeight), + availableSize: CGSize(width: availableWidth, height: contentHeight - temporaryOffsetForSmallerSubtitle / 2), transition: context.transition ) } @@ -1693,7 +1824,7 @@ private final class ToolbarComponent: CombinedComponent { let maxCenterInset = max(centerLeftInset, centerRightInset) if let centerItem = 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)) ) } diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift index 744c10ebad..9b4195c509 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift @@ -214,7 +214,8 @@ final class MediaStreamVideoComponent: Component { 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 { loadingBlurView.contentView.layer.addSublayer(shimmerBorderLayer) } @@ -230,9 +231,10 @@ final class MediaStreamVideoComponent: Component { 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 = .init() + borderShimmer = StandaloneShimmerEffect() borderShimmer.layer = shimmerBorderLayer borderShimmer.updateHorizontal(background: .clear, foreground: .white) loadingBlurView.alpha = 1 @@ -314,7 +316,6 @@ final class MediaStreamVideoComponent: Component { UIView.animate(withDuration: 0.3) { videoBlurView.alpha = 1 } - self.maskGradientLayer.type = .radial 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) @@ -409,13 +410,18 @@ final class MediaStreamVideoComponent: Component { } else if component.isFullscreen { if fullScreenBackgroundPlaceholder.superview == nil { insertSubview(fullScreenBackgroundPlaceholder, at: 0) + transition.animateAlpha(view: fullScreenBackgroundPlaceholder, from: 0, to: 1) } fullScreenBackgroundPlaceholder.backgroundColor = UIColor.black.withAlphaComponent(0.5) } 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.isHidden = true let videoInset: CGFloat if !component.isFullscreen { videoInset = 16 @@ -556,6 +562,8 @@ final class MediaStreamVideoComponent: Component { self.noSignalView = noSignalView // TODO: above blurred animation self.addSubview(noSignalView) + noSignalView.layer.zPosition = loadingBlurView.layer.zPosition + 1 + noSignalView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } diff --git a/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift b/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift index 3e248c8da6..af82c8bb55 100644 --- a/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/StreamSheetComponent.swift @@ -95,12 +95,12 @@ final class StreamSheetComponent: CombinedComponent { override func draw(_ rect: CGRect) { super.draw(rect) // Debug interactive area -// guard let context = UIGraphicsGetCurrentContext() else { return } -// context.setFillColor(UIColor.red.cgColor) -// overlayComponentsFrames.forEach { frame in -// context.addRect(frame) -// context.fillPath() -// } + guard let context = UIGraphicsGetCurrentContext() else { return } + context.setFillColor(UIColor.red.withAlphaComponent(0.3).cgColor) + overlayComponentsFrames.forEach { frame in + context.addRect(frame) + context.fillPath() + } } } @@ -172,6 +172,8 @@ final class StreamSheetComponent: CombinedComponent { transition: context.transition ) } + // TODO: replace + let isFullscreen = context.component.participantsCount == -1 context.add(background .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 { 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)) } @@ -297,16 +299,21 @@ final class ParticipantsComponent: Component { func update(view: View, availableSize: CGSize, state: ComponentFlow.EmptyComponentState, environment: ComponentFlow.Environment, transition: ComponentFlow.Transition) -> CGSize { view.counter.update( - countString: count > 0 ? presentationStringsFormattedNumber(Int32(count), ",") : "", - subtitle: count > 0 ? "watching" : "no viewers" + countString: self.count > 0 ? presentationStringsFormattedNumber(Int32(count), ",") : "", + subtitle: self.showsSubtitle ? (self.count > 0 ? "watching" : "no viewers") : "", + fontSize: self.fontSize )// environment.strings.LiveStream_NoViewers) return availableSize } 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.showsSubtitle = showsSubtitle + self.fontSize = fontSize } final class View: UIView {