From c302b7d4a56d817ee9adf59a1a72800d2d347c4e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 25 Jun 2023 04:34:34 +0200 Subject: [PATCH] Various fixes --- .../TelegramNotices/Sources/Notices.swift | 1 + .../Components/LottieComponent/BUILD | 1 + .../Sources/LottieComponent.swift | 18 +- .../Sources/MediaEditorComposerEntity.swift | 79 +- .../Sources/MediaEditorValues.swift | 3 + .../Sources/MediaEditorScreen.swift | 12 +- .../Sources/ContextResultPanelComponent.swift | 2 + .../Components/ShareWithPeersScreen/BUILD | 5 +- .../Sources/PeerListItemComponent.swift | 688 +++++++++--------- .../Sources/ShareWithPeersScreen.swift | 133 +++- .../Stories/PeerListItemComponent/BUILD | 1 + .../Sources/PeerListItemComponent.swift | 31 +- .../StoryItemSetViewListComponent.swift | 2 + 13 files changed, 583 insertions(+), 393 deletions(-) diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index b7fd91b2fa..27ede1a209 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -174,6 +174,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case chatWallpaperDarkPreviewTip = 40 case displayChatListContacts = 41 case displayChatListStoriesTooltip = 42 + case storiesPrivacyTooltip = 43 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) diff --git a/submodules/TelegramUI/Components/LottieComponent/BUILD b/submodules/TelegramUI/Components/LottieComponent/BUILD index 9303a5908f..f6b690b81c 100644 --- a/submodules/TelegramUI/Components/LottieComponent/BUILD +++ b/submodules/TelegramUI/Components/LottieComponent/BUILD @@ -16,6 +16,7 @@ swift_library( "//submodules/rlottie:RLottieBinding", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AppBundle", + "//submodules/GZip", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift b/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift index d41ac96359..a2ac72134f 100644 --- a/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift +++ b/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift @@ -6,6 +6,7 @@ import HierarchyTrackingLayer import RLottieBinding import SwiftSignalKit import AppBundle +import GZip public final class LottieComponent: Component { public typealias EnvironmentType = Empty @@ -50,6 +51,8 @@ public final class LottieComponent: Component { override public func load(_ f: @escaping (Data, String?) -> Void) -> Disposable { if let url = getAppBundle().url(forResource: self.name, withExtension: "json"), let data = try? Data(contentsOf: url) { f(data, url.path) + } else if let url = getAppBundle().url(forResource: self.name, withExtension: "tgs"), let data = try? Data(contentsOf: URL(fileURLWithPath: url.path)), let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) { + f(unpackedData, url.path) } return EmptyDisposable @@ -62,12 +65,12 @@ public final class LottieComponent: Component { } public let content: Content - public let color: UIColor + public let color: UIColor? public let startingPosition: StartingPosition public init( content: Content, - color: UIColor, + color: UIColor? = nil, startingPosition: StartingPosition = .end ) { self.content = content @@ -274,7 +277,12 @@ public final class LottieComponent: Component { } animationInstance.renderFrame(with: Int32(self.currentFrame % Int(animationInstance.frameCount)), into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(currentDisplaySize.width), height: Int32(currentDisplaySize.height), bytesPerRow: Int32(context.bytesPerRow)) - self.currentTemplateFrameImage = context.generateImage()?.withRenderingMode(.alwaysTemplate) + + var image = context.generateImage() + if let _ = self.component?.color { + image = image?.withRenderingMode(.alwaysTemplate) + } + self.currentTemplateFrameImage = image self.image = self.currentTemplateFrameImage if let output = self.output, let currentTemplateFrameImage = self.currentTemplateFrameImage { @@ -311,8 +319,8 @@ public final class LottieComponent: Component { self.updateImage() } - if self.tintColor != component.color { - transition.setTintColor(view: self, color: component.color) + if let color = component.color, self.tintColor != color { + transition.setTintColor(view: self, color: color) } return availableSize diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index 4619f09758..197b6f5390 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -267,36 +267,57 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { tintColor = .white } - let processFrame: (Double, Int, (Int) -> AnimatedStickerFrame?) -> Void = { [weak self] duration, frameCount, takeFrame in + let processFrame: (Double?, Int?, Int?, (Int) -> AnimatedStickerFrame?) -> Void = { [weak self] duration, frameCount, frameRate, takeFrame in guard let strongSelf = self else { return } - let relativeTime = currentTime - floor(currentTime / duration) * duration - var t = relativeTime / duration - t = max(0.0, t) - t = min(1.0, t) - - let startFrame: Double = 0 - let endFrame = Double(frameCount) - - let frameOffset = Int(Double(startFrame) * (1.0 - t) + Double(endFrame - 1) * t) - let lowerBound: Int = 0 - let upperBound = frameCount - 1 - let frameIndex = max(lowerBound, min(upperBound, frameOffset)) - - let currentFrameIndex = strongSelf.currentFrameIndex - if currentFrameIndex != frameIndex { - let previousFrameIndex = currentFrameIndex - strongSelf.currentFrameIndex = frameIndex + var frameAdvancement: Int = 0 + if let duration, let frameCount, frameCount > 0 { + let relativeTime = currentTime - floor(currentTime / duration) * duration + var t = relativeTime / duration + t = max(0.0, t) + t = min(1.0, t) - var delta = 1 - if let previousFrameIndex = previousFrameIndex { - delta = max(1, frameIndex - previousFrameIndex) + let startFrame: Double = 0 + let endFrame = Double(frameCount) + + let frameOffset = Int(Double(startFrame) * (1.0 - t) + Double(endFrame - 1) * t) + let lowerBound: Int = 0 + let upperBound = frameCount - 1 + let frameIndex = max(lowerBound, min(upperBound, frameOffset)) + + let currentFrameIndex = strongSelf.currentFrameIndex + if currentFrameIndex != frameIndex { + let previousFrameIndex = currentFrameIndex + strongSelf.currentFrameIndex = frameIndex + + var delta = 1 + if let previousFrameIndex = previousFrameIndex { + delta = max(1, frameIndex - previousFrameIndex) + } + frameAdvancement = delta } + } else if let frameRate, frameRate > 0 { + let frameTime = 1.0 / Double(frameRate) + let frameIndex = Int(floor(currentTime / frameTime)) - let frame = takeFrame(delta) - - if let frame { + let currentFrameIndex = strongSelf.currentFrameIndex + if currentFrameIndex != frameIndex { + let previousFrameIndex = currentFrameIndex + strongSelf.currentFrameIndex = frameIndex + + var delta = 1 + if let previousFrameIndex = previousFrameIndex { + delta = max(1, frameIndex - previousFrameIndex) + } + frameAdvancement = delta + } + } + + if frameAdvancement == 0 && strongSelf.image != nil { + completion(strongSelf.image) + } else { + if let frame = takeFrame(max(1, frameAdvancement)) { var imagePixelBuffer: CVPixelBuffer? if let pixelBuffer = strongSelf.imagePixelBuffer { imagePixelBuffer = pixelBuffer @@ -327,8 +348,6 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { } else { completion(nil) } - } else { - completion(strongSelf.image) } } @@ -341,12 +360,12 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { return } - guard let frameSource, let duration = strongSelf.totalDuration, let frameCount = strongSelf.frameCount else { + guard let frameSource else { completion(nil) return } - processFrame(duration, frameCount, { delta in + processFrame(strongSelf.totalDuration, strongSelf.frameCount, strongSelf.frameRate, { delta in var frame: AnimatedStickerFrame? frameSource.syncWith { frameSource in for i in 0 ..< delta { @@ -365,12 +384,12 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { return } - guard let frameSource, let duration = strongSelf.totalDuration, let frameCount = strongSelf.frameCount else { + guard let frameSource else { completion(nil) return } - processFrame(duration, frameCount, { delta in + processFrame(strongSelf.totalDuration, strongSelf.frameCount, strongSelf.frameRate, { delta in var frame: AnimatedStickerFrame? frameSource.syncWith { frameSource in for i in 0 ..< delta { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index 3e246222bc..e0c2215e44 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -746,6 +746,9 @@ public extension MediaEditorValues { if self.drawing != nil { return true } + if !self.entities.isEmpty { + return true + } return false } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 22e28a75f7..3f39b22d10 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1731,9 +1731,15 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if case let .draft(draft, _) = subject, let privacy = draft.privacy { controller.state.privacy = privacy } else if !controller.isEditingStory { - let _ = (mediaEditorStoredState(engine: controller.context.engine) - |> deliverOnMainQueue).start(next: { [weak controller] state in - if let controller, let privacy = state?.privacy { + let _ = combineLatest( + queue: Queue.mainQueue(), + mediaEditorStoredState(engine: controller.context.engine), + controller.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.context.account.peerId)) + ).start(next: { [weak controller] state, peer in + if let controller, var privacy = state?.privacy { + if case let .user(user) = peer, !user.isPremium && privacy.timeout != 86400 { + privacy = MediaEditorResultPrivacy(privacy: privacy.privacy, timeout: 86400, archive: false) + } controller.state.privacy = privacy } }) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift index 2e27824caf..62e55b96ec 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift @@ -245,6 +245,7 @@ final class ContextResultPanelComponent: Component { peer: peer, subtitle: peer.addressName.flatMap { "@\($0)" }, subtitleAccessory: .none, + presence: nil, selectionState: .none, hasNext: index != peers.count - 1, action: { [weak self] peer in @@ -313,6 +314,7 @@ final class ContextResultPanelComponent: Component { peer: nil, subtitle: "BBBBBBB", subtitleAccessory: .none, + presence: nil, selectionState: .none, hasNext: true, action: { _ in diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD index af39190090..c6f08dae1a 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD @@ -32,8 +32,9 @@ swift_library( "//submodules/Components/BundleIconComponent", "//submodules/AvatarNode", "//submodules/CheckNode", - "//submodules/PeerPresenceStatusManager", - "//submodules/LocalizedPeerData" + "//submodules/LocalizedPeerData", + "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", + "//submodules/TelegramUI/Components/LottieComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/PeerListItemComponent.swift index 14792c839d..fdafc66196 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/PeerListItemComponent.swift @@ -1,344 +1,344 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import ComponentFlow -import SwiftSignalKit -import AccountContext -import TelegramCore -import MultilineTextComponent -import AvatarNode -import TelegramPresentationData -import CheckNode -import TelegramStringFormatting -import PeerPresenceStatusManager - -private let avatarFont = avatarPlaceholderFont(size: 15.0) - -final class PeerListItemComponent: Component { - enum SelectionState: Equatable { - case none - case editing(isSelected: Bool, isTinted: Bool) - } - - let context: AccountContext - let theme: PresentationTheme - let strings: PresentationStrings - let sideInset: CGFloat - let title: String - let peer: EnginePeer? - let subtitle: String? - let presence: EnginePeer.Presence? - let selectionState: SelectionState - let hasNext: Bool - let action: (EnginePeer) -> Void - - init( - context: AccountContext, - theme: PresentationTheme, - strings: PresentationStrings, - sideInset: CGFloat, - title: String, - peer: EnginePeer?, - subtitle: String?, - presence: EnginePeer.Presence?, - selectionState: SelectionState, - hasNext: Bool, - action: @escaping (EnginePeer) -> Void - ) { - self.context = context - self.theme = theme - self.strings = strings - self.sideInset = sideInset - self.title = title - self.peer = peer - self.subtitle = subtitle - self.presence = presence - self.selectionState = selectionState - self.hasNext = hasNext - self.action = action - } - - static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.sideInset != rhs.sideInset { - return false - } - if lhs.title != rhs.title { - return false - } - if lhs.peer != rhs.peer { - return false - } - if lhs.subtitle != rhs.subtitle { - return false - } - if lhs.presence != rhs.presence { - return false - } - if lhs.selectionState != rhs.selectionState { - return false - } - if lhs.hasNext != rhs.hasNext { - return false - } - return true - } - - final class View: UIView { - private let containerButton: HighlightTrackingButton - - private let title = ComponentView() - private let label = ComponentView() - private let separatorLayer: SimpleLayer - private let avatarNode: AvatarNode - - private var checkLayer: CheckLayer? - - private var component: PeerListItemComponent? - private weak var state: EmptyComponentState? - - private var presenceManager: PeerPresenceStatusManager? - - override init(frame: CGRect) { - self.separatorLayer = SimpleLayer() - - self.containerButton = HighlightTrackingButton() - - self.avatarNode = AvatarNode(font: avatarFont) - self.avatarNode.isLayerBacked = true - - super.init(frame: frame) - - self.layer.addSublayer(self.separatorLayer) - self.addSubview(self.containerButton) - self.containerButton.layer.addSublayer(self.avatarNode.layer) - - self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func pressed() { - guard let component = self.component, let peer = component.peer else { - return - } - component.action(peer) - } - - func update(component: PeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - let animationHint = transition.userData(ShareWithPeersScreenComponent.AnimationHint.self) - var synchronousLoad = false - if let animationHint, animationHint.contentReloaded { - synchronousLoad = true - } - - let themeUpdated = self.component?.theme !== component.theme - - var hasSelectionUpdated = false - if let previousComponent = self.component { - switch previousComponent.selectionState { - case .none: - if case .none = component.selectionState { - } else { - hasSelectionUpdated = true - } - case .editing: - if case .editing = component.selectionState { - } else { - hasSelectionUpdated = true - } - } - } - - if let presence = component.presence { - let presenceManager: PeerPresenceStatusManager - if let current = self.presenceManager { - presenceManager = current - } else { - presenceManager = PeerPresenceStatusManager(update: { [weak self] in - self?.state?.updated(transition: .immediate) - }) - self.presenceManager = presenceManager - } - presenceManager.reset(presence: presence) - } else { - if self.presenceManager != nil { - self.presenceManager = nil - } - } - - self.component = component - self.state = state - - let contextInset: CGFloat = 0.0 - - let height: CGFloat = 60.0 - let verticalInset: CGFloat = 1.0 - var leftInset: CGFloat = 62.0 + component.sideInset - let rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset - var avatarLeftInset: CGFloat = component.sideInset + 10.0 - - if case let .editing(isSelected, isTinted) = component.selectionState { - leftInset += 44.0 - avatarLeftInset += 44.0 - let checkSize: CGFloat = 22.0 - - let checkLayer: CheckLayer - if let current = self.checkLayer { - checkLayer = current - if themeUpdated { - var theme = CheckNodeTheme(theme: component.theme, style: .plain) - if isTinted { - theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5) - } - checkLayer.theme = theme - } - checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate) - } else { - var theme = CheckNodeTheme(theme: component.theme, style: .plain) - if isTinted { - theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5) - } - checkLayer = CheckLayer(theme: theme) - self.checkLayer = checkLayer - self.containerButton.layer.addSublayer(checkLayer) - checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)) - checkLayer.setSelected(isSelected, animated: false) - checkLayer.setNeedsDisplay() - } - transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: floor((54.0 - checkSize) * 0.5), y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))) - } else { - if let checkLayer = self.checkLayer { - self.checkLayer = nil - transition.setPosition(layer: checkLayer, position: CGPoint(x: -checkLayer.bounds.width * 0.5, y: checkLayer.position.y), completion: { [weak checkLayer] _ in - checkLayer?.removeFromSuperlayer() - }) - } - } - - let avatarSize: CGFloat = 40.0 - - let avatarFrame = CGRect(origin: CGPoint(x: avatarLeftInset, y: floor((height - verticalInset * 2.0 - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)) - if self.avatarNode.bounds.isEmpty { - self.avatarNode.frame = avatarFrame - } else { - transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame) - } - if let peer = component.peer { - let clipStyle: AvatarNodeClipStyle - if case let .channel(channel) = peer, channel.flags.contains(.isForum) { - clipStyle = .roundedRect - } else { - clipStyle = .round - } - self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) - } - - let labelData: (String, Bool) - - if let presence = component.presence { - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(timestamp)) - } else if let subtitle = component.subtitle { - labelData = (subtitle, false) - } else if case .legacyGroup = component.peer { - labelData = (component.strings.Group_Status, false) - } else if case let .channel(channel) = component.peer { - if case .group = channel.info { - labelData = (component.strings.Group_Status, false) - } else { - labelData = (component.strings.Channel_Status, false) - } - } else { - labelData = (component.strings.Group_Status, false) - } - - let labelSize = self.label.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) - ) - - let previousTitleFrame = self.title.view?.frame - var previousTitleContents: UIView? - if hasSelectionUpdated && !"".isEmpty { - previousTitleContents = self.title.view?.snapshotView(afterScreenUpdates: false) - } - - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) - ) - - let titleSpacing: CGFloat = 1.0 - let centralContentHeight: CGFloat = titleSize.height + labelSize.height + titleSpacing - - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize) - if let titleView = self.title.view { - if titleView.superview == nil { - titleView.isUserInteractionEnabled = false - self.containerButton.addSubview(titleView) - } - titleView.frame = titleFrame - if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x { - transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true) - } - - if let previousTitleFrame, let previousTitleContents, previousTitleFrame.size != titleSize { - previousTitleContents.frame = CGRect(origin: previousTitleFrame.origin, size: previousTitleFrame.size) - self.addSubview(previousTitleContents) - - transition.setFrame(view: previousTitleContents, frame: CGRect(origin: titleFrame.origin, size: previousTitleFrame.size)) - transition.setAlpha(view: previousTitleContents, alpha: 0.0, completion: { [weak previousTitleContents] _ in - previousTitleContents?.removeFromSuperview() - }) - transition.animateAlpha(view: titleView, from: 0.0, to: 1.0) - } - } - if let labelView = self.label.view { - if labelView.superview == nil { - labelView.isUserInteractionEnabled = false - self.containerButton.addSubview(labelView) - } - transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing), size: labelSize)) - } - - if themeUpdated { - self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor - } - transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel))) - self.separatorLayer.isHidden = !component.hasNext - - let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0)) - transition.setFrame(view: self.containerButton, frame: containerFrame) - - return CGSize(width: availableSize.width, height: height) - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} +//import Foundation +//import UIKit +//import Display +//import AsyncDisplayKit +//import ComponentFlow +//import SwiftSignalKit +//import AccountContext +//import TelegramCore +//import MultilineTextComponent +//import AvatarNode +//import TelegramPresentationData +//import CheckNode +//import TelegramStringFormatting +//import PeerPresenceStatusManager +// +//private let avatarFont = avatarPlaceholderFont(size: 15.0) +// +//final class PeerListItemComponent: Component { +// enum SelectionState: Equatable { +// case none +// case editing(isSelected: Bool, isTinted: Bool) +// } +// +// let context: AccountContext +// let theme: PresentationTheme +// let strings: PresentationStrings +// let sideInset: CGFloat +// let title: String +// let peer: EnginePeer? +// let subtitle: String? +// let presence: EnginePeer.Presence? +// let selectionState: SelectionState +// let hasNext: Bool +// let action: (EnginePeer) -> Void +// +// init( +// context: AccountContext, +// theme: PresentationTheme, +// strings: PresentationStrings, +// sideInset: CGFloat, +// title: String, +// peer: EnginePeer?, +// subtitle: String?, +// presence: EnginePeer.Presence?, +// selectionState: SelectionState, +// hasNext: Bool, +// action: @escaping (EnginePeer) -> Void +// ) { +// self.context = context +// self.theme = theme +// self.strings = strings +// self.sideInset = sideInset +// self.title = title +// self.peer = peer +// self.subtitle = subtitle +// self.presence = presence +// self.selectionState = selectionState +// self.hasNext = hasNext +// self.action = action +// } +// +// static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool { +// if lhs.context !== rhs.context { +// return false +// } +// if lhs.theme !== rhs.theme { +// return false +// } +// if lhs.strings !== rhs.strings { +// return false +// } +// if lhs.sideInset != rhs.sideInset { +// return false +// } +// if lhs.title != rhs.title { +// return false +// } +// if lhs.peer != rhs.peer { +// return false +// } +// if lhs.subtitle != rhs.subtitle { +// return false +// } +// if lhs.presence != rhs.presence { +// return false +// } +// if lhs.selectionState != rhs.selectionState { +// return false +// } +// if lhs.hasNext != rhs.hasNext { +// return false +// } +// return true +// } +// +// final class View: UIView { +// private let containerButton: HighlightTrackingButton +// +// private let title = ComponentView() +// private let label = ComponentView() +// private let separatorLayer: SimpleLayer +// private let avatarNode: AvatarNode +// +// private var checkLayer: CheckLayer? +// +// private var component: PeerListItemComponent? +// private weak var state: EmptyComponentState? +// +// private var presenceManager: PeerPresenceStatusManager? +// +// override init(frame: CGRect) { +// self.separatorLayer = SimpleLayer() +// +// self.containerButton = HighlightTrackingButton() +// +// self.avatarNode = AvatarNode(font: avatarFont) +// self.avatarNode.isLayerBacked = true +// +// super.init(frame: frame) +// +// self.layer.addSublayer(self.separatorLayer) +// self.addSubview(self.containerButton) +// self.containerButton.layer.addSublayer(self.avatarNode.layer) +// +// self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// @objc private func pressed() { +// guard let component = self.component, let peer = component.peer else { +// return +// } +// component.action(peer) +// } +// +// func update(component: PeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { +// let animationHint = transition.userData(ShareWithPeersScreenComponent.AnimationHint.self) +// var synchronousLoad = false +// if let animationHint, animationHint.contentReloaded { +// synchronousLoad = true +// } +// +// let themeUpdated = self.component?.theme !== component.theme +// +// var hasSelectionUpdated = false +// if let previousComponent = self.component { +// switch previousComponent.selectionState { +// case .none: +// if case .none = component.selectionState { +// } else { +// hasSelectionUpdated = true +// } +// case .editing: +// if case .editing = component.selectionState { +// } else { +// hasSelectionUpdated = true +// } +// } +// } +// +// if let presence = component.presence { +// let presenceManager: PeerPresenceStatusManager +// if let current = self.presenceManager { +// presenceManager = current +// } else { +// presenceManager = PeerPresenceStatusManager(update: { [weak self] in +// self?.state?.updated(transition: .immediate) +// }) +// self.presenceManager = presenceManager +// } +// presenceManager.reset(presence: presence) +// } else { +// if self.presenceManager != nil { +// self.presenceManager = nil +// } +// } +// +// self.component = component +// self.state = state +// +// let contextInset: CGFloat = 0.0 +// +// let height: CGFloat = 60.0 +// let verticalInset: CGFloat = 1.0 +// var leftInset: CGFloat = 62.0 + component.sideInset +// let rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset +// var avatarLeftInset: CGFloat = component.sideInset + 10.0 +// +// if case let .editing(isSelected, isTinted) = component.selectionState { +// leftInset += 44.0 +// avatarLeftInset += 44.0 +// let checkSize: CGFloat = 22.0 +// +// let checkLayer: CheckLayer +// if let current = self.checkLayer { +// checkLayer = current +// if themeUpdated { +// var theme = CheckNodeTheme(theme: component.theme, style: .plain) +// if isTinted { +// theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5) +// } +// checkLayer.theme = theme +// } +// checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate) +// } else { +// var theme = CheckNodeTheme(theme: component.theme, style: .plain) +// if isTinted { +// theme.backgroundColor = theme.backgroundColor.mixedWith(component.theme.list.itemBlocksBackgroundColor, alpha: 0.5) +// } +// checkLayer = CheckLayer(theme: theme) +// self.checkLayer = checkLayer +// self.containerButton.layer.addSublayer(checkLayer) +// checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)) +// checkLayer.setSelected(isSelected, animated: false) +// checkLayer.setNeedsDisplay() +// } +// transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: floor((54.0 - checkSize) * 0.5), y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))) +// } else { +// if let checkLayer = self.checkLayer { +// self.checkLayer = nil +// transition.setPosition(layer: checkLayer, position: CGPoint(x: -checkLayer.bounds.width * 0.5, y: checkLayer.position.y), completion: { [weak checkLayer] _ in +// checkLayer?.removeFromSuperlayer() +// }) +// } +// } +// +// let avatarSize: CGFloat = 40.0 +// +// let avatarFrame = CGRect(origin: CGPoint(x: avatarLeftInset, y: floor((height - verticalInset * 2.0 - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)) +// if self.avatarNode.bounds.isEmpty { +// self.avatarNode.frame = avatarFrame +// } else { +// transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame) +// } +// if let peer = component.peer { +// let clipStyle: AvatarNodeClipStyle +// if case let .channel(channel) = peer, channel.flags.contains(.isForum) { +// clipStyle = .roundedRect +// } else { +// clipStyle = .round +// } +// self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) +// } +// +// let labelData: (String, Bool) +// +// if let presence = component.presence { +// let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 +// labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(timestamp)) +// } else if let subtitle = component.subtitle { +// labelData = (subtitle, false) +// } else if case .legacyGroup = component.peer { +// labelData = (component.strings.Group_Status, false) +// } else if case let .channel(channel) = component.peer { +// if case .group = channel.info { +// labelData = (component.strings.Group_Status, false) +// } else { +// labelData = (component.strings.Channel_Status, false) +// } +// } else { +// labelData = (component.strings.Group_Status, false) +// } +// +// let labelSize = self.label.update( +// transition: .immediate, +// component: AnyComponent(MultilineTextComponent( +// text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor)) +// )), +// environment: {}, +// containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) +// ) +// +// let previousTitleFrame = self.title.view?.frame +// var previousTitleContents: UIView? +// if hasSelectionUpdated && !"".isEmpty { +// previousTitleContents = self.title.view?.snapshotView(afterScreenUpdates: false) +// } +// +// let titleSize = self.title.update( +// transition: .immediate, +// component: AnyComponent(MultilineTextComponent( +// text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) +// )), +// environment: {}, +// containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) +// ) +// +// let titleSpacing: CGFloat = 1.0 +// let centralContentHeight: CGFloat = titleSize.height + labelSize.height + titleSpacing +// +// let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize) +// if let titleView = self.title.view { +// if titleView.superview == nil { +// titleView.isUserInteractionEnabled = false +// self.containerButton.addSubview(titleView) +// } +// titleView.frame = titleFrame +// if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x { +// transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true) +// } +// +// if let previousTitleFrame, let previousTitleContents, previousTitleFrame.size != titleSize { +// previousTitleContents.frame = CGRect(origin: previousTitleFrame.origin, size: previousTitleFrame.size) +// self.addSubview(previousTitleContents) +// +// transition.setFrame(view: previousTitleContents, frame: CGRect(origin: titleFrame.origin, size: previousTitleFrame.size)) +// transition.setAlpha(view: previousTitleContents, alpha: 0.0, completion: { [weak previousTitleContents] _ in +// previousTitleContents?.removeFromSuperview() +// }) +// transition.animateAlpha(view: titleView, from: 0.0, to: 1.0) +// } +// } +// if let labelView = self.label.view { +// if labelView.superview == nil { +// labelView.isUserInteractionEnabled = false +// self.containerButton.addSubview(labelView) +// } +// transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing), size: labelSize)) +// } +// +// if themeUpdated { +// self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor +// } +// transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel))) +// self.separatorLayer.isHidden = !component.hasNext +// +// let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0)) +// transition.setFrame(view: self.containerButton, frame: containerFrame) +// +// return CGSize(width: availableSize.width, height: height) +// } +// } +// +// func makeView() -> View { +// return View(frame: CGRect()) +// } +// +// func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { +// return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) +// } +//} diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 54f1cec19c..6b0c80d7bf 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -18,6 +18,8 @@ import AnimatedCounterComponent import TokenListTextField import AvatarNode import LocalizedPeerData +import PeerListItemComponent +import LottieComponent final class ShareWithPeersScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -223,6 +225,10 @@ final class ShareWithPeersScreenComponent: Component { private let navigationTextField = ComponentView() private let textFieldSeparatorLayer: SimpleLayer + private let emptyResultsTitle = ComponentView() + private let emptyResultsText = ComponentView() + private let emptyResultsAnimation = ComponentView() + private let scrollView: ScrollView private let scrollContentClippingView: SparseContainerView private let scrollContentView: UIView @@ -262,8 +268,6 @@ final class ShareWithPeersScreenComponent: Component { return self.searchStateContext?.stateValue ?? self.defaultStateValue } - private var isDisplayingSearch: Bool = false - override init(frame: CGRect) { self.dimView = UIView() @@ -605,10 +609,12 @@ final class ShareWithPeersScreenComponent: Component { context: component.context, theme: environment.theme, strings: environment.strings, + style: .generic, sideInset: itemLayout.sideInset, title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), peer: peer, subtitle: nil, + subtitleAccessory: .none, presence: stateValue.presences[peer.id], selectionState: .editing(isSelected: self.selectedPeers.contains(peer.id), isTinted: false), hasNext: true, @@ -672,6 +678,103 @@ final class ShareWithPeersScreenComponent: Component { for id in removeSectionHeaderIds { self.visibleSectionHeaders.removeValue(forKey: id) } + + let fadeTransition = Transition.easeInOut(duration: 0.25) + if let searchStateContext = self.searchStateContext, case let .search(query) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty { + let sideInset: CGFloat = 44.0 + let emptyAnimationHeight = 148.0 + let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0 + let bottomInset: CGFloat = max(environment.safeInsets.bottom, environment.inputHeight) + let visibleHeight = visibleFrame.height + let emptyAnimationSpacing: CGFloat = 8.0 + let emptyTextSpacing: CGFloat = 8.0 + + let emptyResultsTitleSize = self.emptyResultsTitle.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: environment.strings.Contacts_Search_NoResults, font: Font.semibold(17.0), textColor: environment.theme.list.itemSecondaryTextColor)), + horizontalAlignment: .center + ) + ), + environment: {}, + containerSize: visibleFrame.size + ) + let emptyResultsTextSize = self.emptyResultsText.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: environment.strings.Contacts_Search_NoResultsQueryDescription(query).string, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + ) + ), + environment: {}, + containerSize: CGSize(width: visibleFrame.width - sideInset * 2.0, height: visibleFrame.height) + ) + let emptyResultsAnimationSize = self.emptyResultsAnimation.update( + transition: .immediate, + component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "ChatListNoResults") + )), + environment: {}, + containerSize: CGSize(width: emptyAnimationHeight, height: emptyAnimationHeight) + ) + + let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyResultsTitleSize.height + emptyResultsTextSize.height + emptyTextSpacing + let emptyAnimationY = topInset + floorToScreenPixels((visibleHeight - topInset - bottomInset - emptyTotalHeight) / 2.0) + + let emptyResultsAnimationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((visibleFrame.width - emptyResultsAnimationSize.width) / 2.0), y: emptyAnimationY), size: emptyResultsAnimationSize) + + let emptyResultsTitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((visibleFrame.width - emptyResultsTitleSize.width) / 2.0), y: emptyResultsAnimationFrame.maxY + emptyAnimationSpacing), size: emptyResultsTitleSize) + + let emptyResultsTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((visibleFrame.width - emptyResultsTextSize.width) / 2.0), y: emptyResultsTitleFrame.maxY + emptyTextSpacing), size: emptyResultsTextSize) + + if let view = self.emptyResultsAnimation.view as? LottieComponent.View { + if view.superview == nil { + view.alpha = 0.0 + fadeTransition.setAlpha(view: view, alpha: 1.0) + self.scrollView.addSubview(view) + view.playOnce() + } + view.bounds = CGRect(origin: .zero, size: emptyResultsAnimationFrame.size) + transition.setPosition(view: view, position: emptyResultsAnimationFrame.center) + } + if let view = self.emptyResultsTitle.view { + if view.superview == nil { + view.alpha = 0.0 + fadeTransition.setAlpha(view: view, alpha: 1.0) + self.scrollView.addSubview(view) + } + view.bounds = CGRect(origin: .zero, size: emptyResultsTitleFrame.size) + transition.setPosition(view: view, position: emptyResultsTitleFrame.center) + } + if let view = self.emptyResultsText.view { + if view.superview == nil { + view.alpha = 0.0 + fadeTransition.setAlpha(view: view, alpha: 1.0) + self.scrollView.addSubview(view) + } + view.bounds = CGRect(origin: .zero, size: emptyResultsTextFrame.size) + transition.setPosition(view: view, position: emptyResultsTextFrame.center) + } + } else { + if let view = self.emptyResultsAnimation.view { + fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + if let view = self.emptyResultsTitle.view { + fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + if let view = self.emptyResultsText.view { + fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + } } func animateIn() { @@ -896,10 +999,12 @@ final class ShareWithPeersScreenComponent: Component { context: component.context, theme: environment.theme, strings: environment.strings, + style: .generic, sideInset: sideInset, title: "Name", peer: nil, subtitle: nil, + subtitleAccessory: .none, presence: nil, selectionState: .editing(isSelected: false, isTinted: false), hasNext: true, @@ -1130,7 +1235,7 @@ final class ShareWithPeersScreenComponent: Component { } self.ignoreScrolling = false self.updateScrolling(transition: contentTransition) - + return availableSize } } @@ -1284,15 +1389,27 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { self.readySubject.set(true) }) case let .search(query): - self.stateDisposable = (context.engine.contacts.searchContacts(query: query) - |> deliverOnMainQueue).start(next: { [weak self] peers, presences in + self.stateDisposable = (context.engine.contacts.searchLocalPeers(query: query) + |> deliverOnMainQueue).start(next: { [weak self] peers in guard let self else { return } - + let state = State( - peers: peers.filter { $0.id != context.account.peerId }, - presences: presences + peers: peers.compactMap { $0.peer }.filter { peer in + if case let .user(user) = peer { + if user.id == context.account.peerId { + return false + } else if user.botInfo != nil { + return false + } else { + return true + } + } else { + return false + } + }, + presences: [:] ) self.stateValue = state self.stateSubject.set(.single(state)) diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD index 5ac20783c9..807efc98a9 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/CheckNode", "//submodules/TelegramStringFormatting", "//submodules/AppBundle", + "//submodules/PeerPresenceStatusManager", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift index b2d08aed27..b739db750b 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift @@ -12,6 +12,7 @@ import TelegramPresentationData import CheckNode import TelegramStringFormatting import AppBundle +import PeerPresenceStatusManager private let avatarFont = avatarPlaceholderFont(size: 15.0) private let readIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/MenuReadIcon"), color: .white)?.withRenderingMode(.alwaysTemplate) @@ -49,6 +50,7 @@ public final class PeerListItemComponent: Component { let peer: EnginePeer? let subtitle: String? let subtitleAccessory: SubtitleAccessory + let presence: EnginePeer.Presence? let selectionState: SelectionState let hasNext: Bool let action: (EnginePeer) -> Void @@ -63,6 +65,7 @@ public final class PeerListItemComponent: Component { peer: EnginePeer?, subtitle: String?, subtitleAccessory: SubtitleAccessory, + presence: EnginePeer.Presence?, selectionState: SelectionState, hasNext: Bool, action: @escaping (EnginePeer) -> Void @@ -76,6 +79,7 @@ public final class PeerListItemComponent: Component { self.peer = peer self.subtitle = subtitle self.subtitleAccessory = subtitleAccessory + self.presence = presence self.selectionState = selectionState self.hasNext = hasNext self.action = action @@ -109,6 +113,9 @@ public final class PeerListItemComponent: Component { if lhs.subtitleAccessory != rhs.subtitleAccessory { return false } + if lhs.presence != rhs.presence { + return false + } if lhs.selectionState != rhs.selectionState { return false } @@ -132,6 +139,8 @@ public final class PeerListItemComponent: Component { private var component: PeerListItemComponent? private weak var state: EmptyComponentState? + private var presenceManager: PeerPresenceStatusManager? + public var avatarFrame: CGRect { return self.avatarNode.frame } @@ -203,6 +212,23 @@ public final class PeerListItemComponent: Component { } } + if let presence = component.presence { + let presenceManager: PeerPresenceStatusManager + if let current = self.presenceManager { + presenceManager = current + } else { + presenceManager = PeerPresenceStatusManager(update: { [weak self] in + self?.state?.updated(transition: .immediate) + }) + self.presenceManager = presenceManager + } + presenceManager.reset(presence: presence) + } else { + if self.presenceManager != nil { + self.presenceManager = nil + } + } + self.component = component self.state = state @@ -288,7 +314,10 @@ public final class PeerListItemComponent: Component { } let labelData: (String, Bool) - if let subtitle = component.subtitle { + if let presence = component.presence { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(timestamp)) + } else if let subtitle = component.subtitle { labelData = (subtitle, false) } else { labelData = ("", false) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 3207764c66..987543b58b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -436,6 +436,7 @@ final class StoryItemSetViewListComponent: Component { peer: item.peer, subtitle: dateText, subtitleAccessory: .checks, + presence: nil, selectionState: .none, hasNext: index != viewListState.totalCount - 1, action: { [weak self] peer in @@ -635,6 +636,7 @@ final class StoryItemSetViewListComponent: Component { peer: nil, subtitle: "BBBBBBB", subtitleAccessory: .checks, + presence: nil, selectionState: .none, hasNext: true, action: { _ in