mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Stories
This commit is contained in:
parent
cc07967f9f
commit
d282dfcfe5
@ -545,6 +545,12 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
public final class EmojiTextAttachmentView: UIView {
|
||||
private let contentLayer: InlineStickerItemLayer
|
||||
|
||||
public var isActive: Bool = true {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init(context: AccountContext, userLocation: MediaResourceUserLocation, emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor, pointSize: CGSize) {
|
||||
self.init(
|
||||
context: .account(context),
|
||||
|
@ -146,6 +146,26 @@ public final class LottieComponent: Component {
|
||||
|
||||
private var currentTemplateFrameImage: UIImage?
|
||||
|
||||
public var externalShouldPlay: Bool? {
|
||||
didSet {
|
||||
if self.externalShouldPlay != oldValue {
|
||||
self.visibilityUpdated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isEffectivelyVisible: Bool {
|
||||
if !self.isVisible {
|
||||
return false
|
||||
}
|
||||
if let externalShouldPlay = self.externalShouldPlay {
|
||||
if !externalShouldPlay {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public weak var output: UIImageView? {
|
||||
didSet {
|
||||
if let output = self.output, let currentTemplateFrameImage = self.currentTemplateFrameImage {
|
||||
@ -190,10 +210,14 @@ public final class LottieComponent: Component {
|
||||
}
|
||||
|
||||
private func visibilityUpdated() {
|
||||
if self.isVisible {
|
||||
if self.isEffectivelyVisible {
|
||||
if self.scheduledPlayOnce {
|
||||
self.playOnce()
|
||||
} else {
|
||||
self.displayLink?.isPaused = false
|
||||
}
|
||||
} else {
|
||||
self.displayLink?.isPaused = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +228,7 @@ public final class LottieComponent: Component {
|
||||
self.scheduledPlayOnce = true
|
||||
return
|
||||
}
|
||||
if !self.isVisible {
|
||||
if !self.isEffectivelyVisible {
|
||||
self.scheduledPlayOnce = true
|
||||
return
|
||||
}
|
||||
|
@ -919,6 +919,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
public var openCurrentDate: (() -> Void)?
|
||||
public var paneDidScroll: (() -> Void)?
|
||||
public var emptyAction: (() -> Void)?
|
||||
|
||||
public var ensureRectVisible: ((UIView, CGRect) -> Void)?
|
||||
|
||||
private weak var currentGestureItem: SparseItemGridDisplayItem?
|
||||
|
||||
@ -1116,6 +1118,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
self.itemInteraction.hiddenMedia = Set([itemId.id])
|
||||
if let items = self.items, let item = items.items.first(where: { $0.id == AnyHashable(itemId.id) }) {
|
||||
self.itemGrid.ensureItemVisible(index: item.index, anyAmount: anyAmount)
|
||||
|
||||
if !anyAmount {
|
||||
var foundItemLayer: SparseItemGridLayer?
|
||||
self.itemGrid.forEachVisibleItem { item in
|
||||
guard let itemLayer = item.layer as? ItemLayer else {
|
||||
return
|
||||
}
|
||||
if let listItem = itemLayer.item, listItem.story.id == itemId.id {
|
||||
foundItemLayer = itemLayer
|
||||
}
|
||||
}
|
||||
if let foundItemLayer {
|
||||
self.ensureRectVisible?(self.view, self.itemGrid.frameForItem(layer: foundItemLayer))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.itemInteraction.hiddenMedia = Set()
|
||||
|
@ -280,6 +280,10 @@ final class StoryItemContentComponent: Component {
|
||||
if self.progressMode != progressMode {
|
||||
self.progressMode = progressMode
|
||||
self.updateProgressMode(update: true)
|
||||
|
||||
if let component = self.component, !self.overlaysView.bounds.isEmpty {
|
||||
self.updateOverlays(component: component, size: self.overlaysView.bounds.size, synchronousLoad: false, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,6 +487,22 @@ final class StoryItemContentComponent: Component {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func updateOverlays(component: StoryItemContentComponent, size: CGSize, synchronousLoad: Bool, transition: Transition) {
|
||||
self.overlaysView.update(
|
||||
context: component.context,
|
||||
strings: component.strings,
|
||||
peer: component.peer,
|
||||
story: component.item,
|
||||
availableReactions: component.availableReactions,
|
||||
entityFiles: component.entityFiles,
|
||||
size: size,
|
||||
isCaptureProtected: component.item.isForwardingDisabled,
|
||||
attemptSynchronous: synchronousLoad,
|
||||
isActive: self.progressMode == .play,
|
||||
transition: transition
|
||||
)
|
||||
}
|
||||
|
||||
func update(component: StoryItemContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StoryContentItem.Environment>, transition: Transition) -> CGSize {
|
||||
let previousItem = self.component?.item
|
||||
|
||||
@ -610,18 +630,7 @@ final class StoryItemContentComponent: Component {
|
||||
attemptSynchronous: synchronousLoad,
|
||||
transition: transition
|
||||
)
|
||||
self.overlaysView.update(
|
||||
context: component.context,
|
||||
strings: component.strings,
|
||||
peer: component.peer,
|
||||
story: component.item,
|
||||
availableReactions: component.availableReactions,
|
||||
entityFiles: component.entityFiles,
|
||||
size: availableSize,
|
||||
isCaptureProtected: component.item.isForwardingDisabled,
|
||||
attemptSynchronous: synchronousLoad,
|
||||
transition: transition
|
||||
)
|
||||
self.updateOverlays(component: component, size: availableSize, synchronousLoad: synchronousLoad, transition: transition)
|
||||
applyState = true
|
||||
if self.imageView.isContentLoaded {
|
||||
self.contentLoaded = true
|
||||
|
@ -19,6 +19,38 @@ import AnimatedCountLabelNode
|
||||
import LottieComponent
|
||||
import LottieComponentResourceContent
|
||||
|
||||
public final class StaticStoryItemOverlaysView: UIImageView {
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
}
|
||||
|
||||
public func update(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
story: EngineStoryItem,
|
||||
availableReactions: StoryAvailableReactions?,
|
||||
entityFiles: [MediaId: TelegramMediaFile]
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
override public func draw(_ rect: CGRect) {
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = context
|
||||
}
|
||||
}
|
||||
|
||||
final class StoryItemOverlaysView: UIView {
|
||||
static let counterFont: UIFont = {
|
||||
return Font.with(size: 17.0, design: .camera, weight: .semibold, traits: .monospacedNumbers)
|
||||
@ -105,7 +137,8 @@ final class StoryItemOverlaysView: UIView {
|
||||
availableReactions: StoryAvailableReactions?,
|
||||
entityFiles: [MediaId: TelegramMediaFile],
|
||||
synchronous: Bool,
|
||||
size: CGSize
|
||||
size: CGSize,
|
||||
isActive: Bool
|
||||
) {
|
||||
var transition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
|
||||
if self.reaction == nil {
|
||||
@ -291,9 +324,11 @@ final class StoryItemOverlaysView: UIView {
|
||||
stickerTransition.setPosition(view: customEmojiView, position: stickerFrame.center)
|
||||
stickerTransition.setBounds(view: customEmojiView, bounds: CGRect(origin: CGPoint(), size: stickerFrame.size))
|
||||
stickerTransition.setScale(view: customEmojiView, scale: stickerScale)
|
||||
|
||||
customEmojiView.isActive = isActive
|
||||
}
|
||||
|
||||
if let directStickerView = self.directStickerView?.view {
|
||||
if let directStickerView = self.directStickerView?.view as? LottieComponent.View {
|
||||
var stickerTransition = transition
|
||||
if directStickerView.superview == nil {
|
||||
self.addSubview(directStickerView)
|
||||
@ -314,6 +349,8 @@ final class StoryItemOverlaysView: UIView {
|
||||
stickerTransition.setPosition(view: directStickerView, position: stickerFrame.center)
|
||||
stickerTransition.setBounds(view: directStickerView, bounds: CGRect(origin: CGPoint(), size: stickerFrame.size))
|
||||
stickerTransition.setScale(view: directStickerView, scale: stickerScale)
|
||||
|
||||
directStickerView.externalShouldPlay = isActive
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,6 +389,7 @@ final class StoryItemOverlaysView: UIView {
|
||||
size: CGSize,
|
||||
isCaptureProtected: Bool,
|
||||
attemptSynchronous: Bool,
|
||||
isActive: Bool,
|
||||
transition: Transition
|
||||
) {
|
||||
var nextId = 0
|
||||
@ -400,7 +438,8 @@ final class StoryItemOverlaysView: UIView {
|
||||
availableReactions: availableReactions,
|
||||
entityFiles: entityFiles,
|
||||
synchronous: attemptSynchronous,
|
||||
size: targetFrame.size
|
||||
size: targetFrame.size,
|
||||
isActive: isActive
|
||||
)
|
||||
|
||||
nextId += 1
|
||||
|
@ -5280,8 +5280,26 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
if component.slice.peer.id == component.context.account.peerId {
|
||||
self.performMyMoreAction(sourceView: sourceView, gesture: gesture)
|
||||
} else if case let .channel(channel) = component.slice.peer, channel.hasPermission(.sendSomething) {
|
||||
self.performMyChannelMoreAction(sourceView: sourceView, gesture: gesture)
|
||||
} else if case let .channel(channel) = component.slice.peer {
|
||||
var canPerformStoryActions = false
|
||||
|
||||
if channel.hasPermission(.editStories) {
|
||||
canPerformStoryActions = true
|
||||
} else if component.slice.item.storyItem.isMy && channel.hasPermission(.postStories) {
|
||||
canPerformStoryActions = true
|
||||
}
|
||||
|
||||
if channel.hasPermission(.deleteStories) {
|
||||
canPerformStoryActions = true
|
||||
} else if component.slice.item.storyItem.isMy && channel.hasPermission(.postStories) {
|
||||
canPerformStoryActions = true
|
||||
}
|
||||
|
||||
if canPerformStoryActions {
|
||||
self.performMyChannelMoreAction(sourceView: sourceView, gesture: gesture)
|
||||
} else {
|
||||
self.performOtherMoreAction(sourceView: sourceView, gesture: gesture)
|
||||
}
|
||||
} else {
|
||||
self.performOtherMoreAction(sourceView: sourceView, gesture: gesture)
|
||||
}
|
||||
@ -6055,20 +6073,22 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextStealthMode, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if accountUser.isPremium {
|
||||
self.sendMessageContext.requestStealthMode(view: self)
|
||||
} else {
|
||||
self.presentStealthModeUpgradeScreen()
|
||||
}
|
||||
})))
|
||||
if case .user = component.slice.peer {
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextStealthMode, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if accountUser.isPremium {
|
||||
self.sendMessageContext.requestStealthMode(view: self)
|
||||
} else {
|
||||
self.presentStealthModeUpgradeScreen()
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
if !component.slice.peer.isService && component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) {
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in
|
||||
|
@ -362,7 +362,8 @@ private final class PeerInfoPendingPane {
|
||||
hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void,
|
||||
parentController: ViewController?,
|
||||
openMediaCalendar: @escaping () -> Void,
|
||||
paneDidScroll: @escaping () -> Void
|
||||
paneDidScroll: @escaping () -> Void,
|
||||
ensureRectVisible: @escaping (UIView, CGRect) -> Void
|
||||
) {
|
||||
let captureProtected = data.peer?.isCopyProtectionEnabled ?? false
|
||||
let paneNode: PeerInfoPaneNode
|
||||
@ -376,6 +377,9 @@ private final class PeerInfoPendingPane {
|
||||
visualPaneNode.paneDidScroll = {
|
||||
paneDidScroll()
|
||||
}
|
||||
visualPaneNode.ensureRectVisible = { sourceView, rect in
|
||||
ensureRectVisible(sourceView, rect)
|
||||
}
|
||||
case .media:
|
||||
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected)
|
||||
paneNode = visualPaneNode
|
||||
@ -487,6 +491,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
var openMediaCalendar: (() -> Void)?
|
||||
var paneDidScroll: (() -> Void)?
|
||||
|
||||
var ensurePaneRectVisible: ((UIView, CGRect) -> Void)?
|
||||
|
||||
private var currentAvailablePanes: [PeerInfoPaneKey]?
|
||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
|
||||
@ -820,6 +826,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
},
|
||||
paneDidScroll: { [weak self] in
|
||||
self?.paneDidScroll?()
|
||||
},
|
||||
ensureRectVisible: { [weak self] sourceView, rect in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.ensurePaneRectVisible?(self.view, sourceView.convert(rect, to: self.view))
|
||||
}
|
||||
)
|
||||
self.pendingPanes[key] = pane
|
||||
|
@ -2982,6 +2982,35 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.paneContainerNode.ensurePaneRectVisible = { [weak self] sourceView, rect in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let localRect = sourceView.convert(rect, to: self.view)
|
||||
if !self.view.bounds.insetBy(dx: -1000.0, dy: 0.0).contains(localRect) {
|
||||
guard let (_, navigationHeight) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.headerNode.isAvatarExpanded {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
|
||||
self.headerNode.updateIsAvatarExpanded(false, transition: transition)
|
||||
self.updateNavigationExpansionPresentation(isExpanded: false, animated: true)
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
let contentOffset = self.scrollNode.view.contentOffset
|
||||
let paneAreaExpansionFinalPoint: CGFloat = self.paneContainerNode.frame.minY - navigationHeight
|
||||
if contentOffset.y < paneAreaExpansionFinalPoint - CGFloat.ulpOfOne {
|
||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: paneAreaExpansionFinalPoint), animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.paneContainerNode.openMediaCalendar = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user