This commit is contained in:
Ali 2023-06-20 20:03:16 +03:00
parent cf2b1f0de5
commit 6aa3e47ee8
13 changed files with 180 additions and 107 deletions

View File

@ -1435,7 +1435,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
transition.updateAlpha(node: self.setByYouNode, alpha: 0.7)
self.setByYouNode.attributedText = NSAttributedString(string: photoTitle, font: Font.regular(12.0), textColor: UIColor.white)
let setByYouSize = self.setByYouNode.updateLayout(size)
self.setByYouNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - setByYouSize.width) / 2.0), y: 17.0), size: setByYouSize)
self.setByYouNode.frame = CGRect(origin: CGPoint(x: size.width - setByYouSize.width - 14.0, y: size.height - setByYouSize.height - 58.0), size: setByYouSize)
self.setByYouNode.isUserInteractionEnabled = hasLink
} else {
transition.updateAlpha(node: self.setByYouNode, alpha: 0.0)
@ -1445,7 +1445,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if let fallbackImageSignal = fallbackImageSignal {
self.setByYouImageNode.setSignal(fallbackImageSignal)
transition.updateAlpha(node: self.setByYouImageNode, alpha: 1.0)
self.setByYouImageNode.frame = CGRect(origin: CGPoint(x: self.setByYouNode.frame.minX - 32.0, y: 11.0), size: CGSize(width: 28.0, height: 28.0))
self.setByYouImageNode.frame = CGRect(origin: CGPoint(x: self.setByYouNode.frame.minX - 32.0, y: self.setByYouNode.frame.minY - 7.0), size: CGSize(width: 28.0, height: 28.0))
} else {
transition.updateAlpha(node: self.setByYouImageNode, alpha: 0.0)
}

View File

@ -376,6 +376,7 @@ swift_library(
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent",
"//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode",
"//submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode",
"//submodules/Utils/VolumeButtons",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],

View File

@ -0,0 +1,35 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatAvatarNavigationNode",
module_name = "ChatAvatarNavigationNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/AvatarNode",
"//submodules/ContextUI",
"//submodules/TelegramPresentationData",
"//submodules/TelegramUniversalVideoContent",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/GalleryUI",
"//submodules/Components/HierarchyTrackingLayer",
"//submodules/AccountContext",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/AvatarVideoNode",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",
],
)

View File

@ -16,18 +16,23 @@ import AccountContext
import ComponentFlow
import EmojiStatusComponent
import AvatarVideoNode
import AvatarStoryIndicatorComponent
import ComponentDisplayAdapters
private let normalFont = avatarPlaceholderFont(size: 16.0)
private let smallFont = avatarPlaceholderFont(size: 12.0)
final class ChatAvatarNavigationNode: ASDisplayNode {
public final class ChatAvatarNavigationNode: ASDisplayNode {
private var context: AccountContext?
private let containerNode: ContextControllerSourceNode
let avatarNode: AvatarNode
public let avatarNode: AvatarNode
private var avatarVideoNode: AvatarVideoNode?
let statusView: ComponentView<Empty>
public private(set) var avatarStoryView: ComponentView<Empty>?
public var hasUnseenStories: Bool?
public let statusView: ComponentView<Empty>
private var cachedDataDisposable = MetaDisposable()
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
@ -42,8 +47,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
}
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
var contextActionIsEnabled: Bool = false {
public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
public var contextActionIsEnabled: Bool = false {
didSet {
if self.contextActionIsEnabled != oldValue {
self.containerNode.isGestureEnabled = self.contextActionIsEnabled
@ -51,7 +56,7 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
}
override init() {
override public init() {
self.containerNode = ContextControllerSourceNode()
self.containerNode.isGestureEnabled = false
self.avatarNode = AvatarNode(font: normalFont)
@ -71,13 +76,17 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0)).offsetBy(dx: 10.0, dy: 1.0)
self.avatarNode.frame = self.containerNode.bounds
#if DEBUG
self.hasUnseenStories = true
#endif
}
deinit {
self.cachedDataDisposable.dispose()
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
self.view.isOpaque = false
}
@ -190,14 +199,49 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
public func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme) {
if let hasUnseenStories = self.hasUnseenStories {
let avatarStoryView: ComponentView<Empty>
if let current = self.avatarStoryView {
avatarStoryView = current
} else {
avatarStoryView = ComponentView()
self.avatarStoryView = avatarStoryView
}
let _ = avatarStoryView.update(
transition: Transition(transition),
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: hasUnseenStories,
isDarkTheme: theme.overallDarkAppearance,
activeLineWidth: 1.0,
inactiveLineWidth: 1.0
)),
environment: {},
containerSize: self.avatarNode.bounds.insetBy(dx: 2.0, dy: 2.0).size
)
if let avatarStoryComponentView = avatarStoryView.view {
if avatarStoryComponentView.superview == nil {
self.containerNode.view.insertSubview(avatarStoryComponentView, at: 0)
}
avatarStoryComponentView.frame = self.avatarNode.frame
}
} else {
if let avatarStoryView = self.avatarStoryView {
self.avatarStoryView = nil
avatarStoryView.view?.removeFromSuperview()
}
}
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 37.0, height: 37.0)
}
func onLayout() {
public func onLayout() {
}
final class SnapshotState {
public final class SnapshotState {
fileprivate let snapshotView: UIView?
fileprivate init(snapshotView: UIView?) {
@ -205,14 +249,14 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
}
func prepareSnapshotState() -> SnapshotState {
public func prepareSnapshotState() -> SnapshotState {
let snapshotView = self.avatarNode.view.snapshotView(afterScreenUpdates: false)
return SnapshotState(
snapshotView: snapshotView
)
}
func animateFromSnapshot(_ snapshotState: SnapshotState) {
public func animateFromSnapshot(_ snapshotState: SnapshotState) {
self.avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true)

View File

@ -11,11 +11,20 @@ swift_library(
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/Display",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/LocalizedPeerData",
"//submodules/PhotoResources",
"//submodules/TelegramStringFormatting",
"//submodules/TextFormat",
"//submodules/InvisibleInkDustNode",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TelegramUI/Components/AnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
],
visibility = [
"//visibility:public",

View File

@ -1038,7 +1038,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
guard let self else {
return
}
self.view.insertSubview(view, aboveSubview: self.itemGrid.view)
self.addToTransitionSurface(view: view)
}
),
destinationRect: self.itemGrid.view.convert(itemRect, to: self.view),

View File

@ -552,6 +552,10 @@ private final class StoryContainerScreenComponent: Component {
focusedItemPromise.set(.single(nil))
})
} else {
if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let transitionOut = component.transitionOut(slice.peer.id, slice.item.id) {
transitionOut.completed()
}
let transition: Transition
if self.dismissWithoutTransitionOut {
transition = Transition(animation: .curve(duration: 0.5, curve: .spring))

View File

@ -536,6 +536,9 @@ public final class StoryContentContextImpl: StoryContentContext {
}
var sortedItems: [EngineStorySubscriptions.Item] = []
if !startedWithUnseen, let accountItem = storySubscriptions.accountItem, accountItem.storyCount != 0 {
sortedItems.append(accountItem)
}
for peerId in self.fixedSubscriptionOrder {
if let index = storySubscriptions.items.firstIndex(where: { $0.peer.id == peerId }) {
sortedItems.append(storySubscriptions.items[index])
@ -606,13 +609,48 @@ public final class StoryContentContextImpl: StoryContentContext {
}
}
if let (focusedPeerId, _) = self.focusedItem, focusedPeerId == self.context.account.peerId {
let centralPeerContext = PeerContext(context: self.context, peerId: self.context.account.peerId, focusedId: nil, loadIds: loadIds)
var centralIndex: Int?
if let (focusedPeerId, _) = self.focusedItem {
if let index = subscriptionItems.firstIndex(where: { $0.peer.id == focusedPeerId }) {
centralIndex = index
}
}
if centralIndex == nil {
if !subscriptionItems.isEmpty {
centralIndex = 0
}
}
if let centralIndex {
let centralPeerContext: PeerContext
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: subscriptionItems[centralIndex].peer.id) {
centralPeerContext = existingContext
} else {
centralPeerContext = PeerContext(context: self.context, peerId: subscriptionItems[centralIndex].peer.id, focusedId: nil, loadIds: loadIds)
}
var previousPeerContext: PeerContext?
if centralIndex != 0 {
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: subscriptionItems[centralIndex - 1].peer.id) {
previousPeerContext = existingContext
} else {
previousPeerContext = PeerContext(context: self.context, peerId: subscriptionItems[centralIndex - 1].peer.id, focusedId: nil, loadIds: loadIds)
}
}
var nextPeerContext: PeerContext?
if centralIndex != subscriptionItems.count - 1 {
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: subscriptionItems[centralIndex + 1].peer.id) {
nextPeerContext = existingContext
} else {
nextPeerContext = PeerContext(context: self.context, peerId: subscriptionItems[centralIndex + 1].peer.id, focusedId: nil, loadIds: loadIds)
}
}
let pendingState = StateContext(
centralPeerContext: centralPeerContext,
previousPeerContext: nil,
nextPeerContext: nil
previousPeerContext: previousPeerContext,
nextPeerContext: nextPeerContext
)
self.pendingState = pendingState
self.pendingStateReadyDisposable = (pendingState.updated.get()
@ -637,74 +675,6 @@ public final class StoryContentContextImpl: StoryContentContext {
self.updateState()
})
})
} else {
var centralIndex: Int?
if let (focusedPeerId, _) = self.focusedItem {
if let index = subscriptionItems.firstIndex(where: { $0.peer.id == focusedPeerId }) {
centralIndex = index
}
}
if centralIndex == nil {
if !subscriptionItems.isEmpty {
centralIndex = 0
}
}
if let centralIndex {
let centralPeerContext: PeerContext
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: subscriptionItems[centralIndex].peer.id) {
centralPeerContext = existingContext
} else {
centralPeerContext = PeerContext(context: self.context, peerId: subscriptionItems[centralIndex].peer.id, focusedId: nil, loadIds: loadIds)
}
var previousPeerContext: PeerContext?
if centralIndex != 0 {
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: subscriptionItems[centralIndex - 1].peer.id) {
previousPeerContext = existingContext
} else {
previousPeerContext = PeerContext(context: self.context, peerId: subscriptionItems[centralIndex - 1].peer.id, focusedId: nil, loadIds: loadIds)
}
}
var nextPeerContext: PeerContext?
if centralIndex != subscriptionItems.count - 1 {
if let currentState = self.currentState, let existingContext = currentState.findPeerContext(id: subscriptionItems[centralIndex + 1].peer.id) {
nextPeerContext = existingContext
} else {
nextPeerContext = PeerContext(context: self.context, peerId: subscriptionItems[centralIndex + 1].peer.id, focusedId: nil, loadIds: loadIds)
}
}
let pendingState = StateContext(
centralPeerContext: centralPeerContext,
previousPeerContext: previousPeerContext,
nextPeerContext: nextPeerContext
)
self.pendingState = pendingState
self.pendingStateReadyDisposable = (pendingState.updated.get()
|> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in
guard let self, let pendingState, self.pendingState === pendingState, pendingState.isReady else {
return
}
self.pendingState = nil
self.pendingStateReadyDisposable?.dispose()
self.pendingStateReadyDisposable = nil
self.currentState = pendingState
self.updateState()
self.currentStateUpdatedDisposable?.dispose()
self.currentStateUpdatedDisposable = (pendingState.updated.get()
|> deliverOnMainQueue).start(next: { [weak self, weak pendingState] _ in
guard let self, let pendingState, self.currentState === pendingState else {
return
}
self.updateState()
})
})
}
}
}
}

View File

@ -98,6 +98,7 @@ import StoryContainerScreen
import StoryContentComponent
import MoreHeaderButton
import VolumeButtons
import ChatAvatarNavigationNode
#if DEBUG
import os.signpost
@ -561,6 +562,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var powerSavingMonitoringDisposable: Disposable?
private var avatarNode: ChatAvatarNavigationNode?
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) {
let _ = ChatControllerCount.modify { value in
return value + 1
@ -4687,7 +4690,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
strongSelf.presentInGlobalOverlay(contextController)
}
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
self.avatarNode = avatarNode
avatarNode.updateStoryView(transition: .immediate, theme: self.presentationData.theme)
case .feed:
chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}

View File

@ -24,6 +24,7 @@ import ChatTitleView
import ChatInputNode
import ChatEntityKeyboardInputNode
import ChatControllerInteraction
import ChatAvatarNavigationNode
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
let itemNode: OverlayMediaItemNode

View File

@ -16,26 +16,26 @@ import TextNodeWithEntities
import AnimationCache
import MultiAnimationRenderer
enum ChatMessageReplyInfoType {
public enum ChatMessageReplyInfoType {
case bubble(incoming: Bool)
case standalone
}
class ChatMessageReplyInfoNode: ASDisplayNode {
class Arguments {
let presentationData: ChatPresentationData
let strings: PresentationStrings
let context: AccountContext
let type: ChatMessageReplyInfoType
let message: Message?
let story: StoryId?
let parentMessage: Message
let constrainedSize: CGSize
let animationCache: AnimationCache?
let animationRenderer: MultiAnimationRenderer?
let associatedData: ChatMessageItemAssociatedData
public class ChatMessageReplyInfoNode: ASDisplayNode {
public class Arguments {
public let presentationData: ChatPresentationData
public let strings: PresentationStrings
public let context: AccountContext
public let type: ChatMessageReplyInfoType
public let message: Message?
public let story: StoryId?
public let parentMessage: Message
public let constrainedSize: CGSize
public let animationCache: AnimationCache?
public let animationRenderer: MultiAnimationRenderer?
public let associatedData: ChatMessageItemAssociatedData
init(
public init(
presentationData: ChatPresentationData,
strings: PresentationStrings,
context: AccountContext,
@ -62,7 +62,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
}
}
var visibility: Bool = false {
public var visibility: Bool = false {
didSet {
if self.visibility != oldValue {
self.textNode?.visibilityRect = self.visibility ? CGRect.infinite : nil
@ -79,7 +79,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
private var previousMediaReference: AnyMediaReference?
private var expiredStoryIconView: UIImageView?
override init() {
override public init() {
self.contentNode = ASDisplayNode()
self.contentNode.isUserInteractionEnabled = false
self.contentNode.displaysAsynchronously = false
@ -97,7 +97,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
self.contentNode.addSubnode(self.lineNode)
}
class func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode) {
public static func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode) {
let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode)
let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode)
let imageNodeLayout = TransformImageNode.asyncLayout(maybeNode?.imageNode)
@ -583,7 +583,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
}
}
func mediaTransitionView() -> UIView? {
public func mediaTransitionView() -> UIView? {
if let imageNode = self.imageNode {
return imageNode.view
}

View File

@ -33,6 +33,7 @@ import AvatarVideoNode
import PeerInfoVisualMediaPaneNode
import AvatarStoryIndicatorComponent
import ComponentDisplayAdapters
import ChatAvatarNavigationNode
enum PeerInfoHeaderButtonKey: Hashable {
case message

View File

@ -90,6 +90,7 @@ import PeerInfoVisualMediaPaneNode
import PeerInfoStoryGridScreen
import StoryContainerScreen
import StoryContentComponent
import ChatAvatarNavigationNode
enum PeerInfoAvatarEditingMode {
case generic