mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
9d8777443f
commit
a6d7f6d45d
@ -20,6 +20,8 @@ swift_library(
|
||||
"//submodules/Emoji:Emoji",
|
||||
"//submodules/TinyThumbnail:TinyThumbnail",
|
||||
"//submodules/FastBlur:FastBlur",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -10,6 +10,8 @@ import AppBundle
|
||||
import AccountContext
|
||||
import Emoji
|
||||
import Accelerate
|
||||
import ComponentFlow
|
||||
import AvatarStoryIndicatorComponent
|
||||
|
||||
private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed()
|
||||
private let phoneIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/PhoneIcon"), color: .white)
|
||||
@ -224,6 +226,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x72d5fd)
|
||||
]
|
||||
|
||||
public final class ContentNode: ASDisplayNode {
|
||||
public var font: UIFont {
|
||||
didSet {
|
||||
if oldValue.pointSize != font.pointSize {
|
||||
@ -246,7 +249,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
public var editOverlayNode: AvatarEditOverlayNode?
|
||||
|
||||
private let imageReadyDisposable = MetaDisposable()
|
||||
private var state: AvatarNodeState = .empty
|
||||
fileprivate var state: AvatarNodeState = .empty
|
||||
|
||||
public var unroundedImage: UIImage?
|
||||
private var currentImage: UIImage?
|
||||
@ -322,19 +325,6 @@ public final class AvatarNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
override public var frame: CGRect {
|
||||
get {
|
||||
return super.frame
|
||||
} set(value) {
|
||||
let updateImage = !value.size.equalTo(super.frame.size)
|
||||
super.frame = value
|
||||
|
||||
if updateImage {
|
||||
self.updateSize(size: value.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateSize(size: CGSize) {
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.editOverlayNode?.frame = self.imageNode.frame
|
||||
@ -672,29 +662,238 @@ public final class AvatarNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func asyncLayout(_ node: AvatarNode?) -> (_ context: AccountContext, _ peer: EnginePeer, _ font: UIFont) -> () -> AvatarNode? {
|
||||
let currentState = node?.state
|
||||
let createNode = node == nil
|
||||
return { [weak node] context, peer, font in
|
||||
let state: AvatarNodeState = .peerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage, .round)
|
||||
if currentState != state {
|
||||
public let contentNode: ContentNode
|
||||
private var storyIndicatorTheme: PresentationTheme?
|
||||
private var storyIndicator: ComponentView<Empty>?
|
||||
|
||||
public struct StoryStats: Equatable {
|
||||
public var totalCount: Int
|
||||
public var unseenCount: Int
|
||||
public var hasUnseenCloseFriendsItems: Bool
|
||||
|
||||
public init(
|
||||
totalCount: Int,
|
||||
unseenCount: Int,
|
||||
hasUnseenCloseFriendsItems: Bool
|
||||
) {
|
||||
self.totalCount = totalCount
|
||||
self.unseenCount = unseenCount
|
||||
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
|
||||
}
|
||||
var createdNode: AvatarNode?
|
||||
if createNode {
|
||||
createdNode = AvatarNode(font: font)
|
||||
}
|
||||
return {
|
||||
let updatedNode: AvatarNode?
|
||||
if let createdNode = createdNode {
|
||||
updatedNode = createdNode
|
||||
|
||||
public private(set) var storyStats: StoryStats?
|
||||
|
||||
public var font: UIFont {
|
||||
get {
|
||||
return self.contentNode.font
|
||||
} set(value) {
|
||||
self.contentNode.font = value
|
||||
}
|
||||
}
|
||||
|
||||
public var editOverlayNode: AvatarEditOverlayNode? {
|
||||
get {
|
||||
return self.contentNode.editOverlayNode
|
||||
} set(value) {
|
||||
self.contentNode.editOverlayNode = value
|
||||
}
|
||||
}
|
||||
|
||||
public var unroundedImage: UIImage? {
|
||||
get {
|
||||
return self.contentNode.unroundedImage
|
||||
} set(value) {
|
||||
self.contentNode.unroundedImage = value
|
||||
}
|
||||
}
|
||||
|
||||
public var badgeView: AvatarBadgeView? {
|
||||
get {
|
||||
return self.contentNode.badgeView
|
||||
} set(value) {
|
||||
self.contentNode.badgeView = value
|
||||
}
|
||||
}
|
||||
|
||||
public var ready: Signal<Void, NoError> {
|
||||
return self.contentNode.ready
|
||||
}
|
||||
|
||||
public var imageNode: ImageNode {
|
||||
return self.contentNode.imageNode
|
||||
}
|
||||
|
||||
public init(font: UIFont) {
|
||||
self.contentNode = ContentNode(font: font)
|
||||
|
||||
super.init()
|
||||
|
||||
self.onDidLoad { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let storyIndicatorTheme = self.storyIndicatorTheme {
|
||||
self.updateStoryIndicator(theme: storyIndicatorTheme, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
self.addSubnode(self.contentNode)
|
||||
}
|
||||
|
||||
override public var frame: CGRect {
|
||||
get {
|
||||
return super.frame
|
||||
} set(value) {
|
||||
let updateImage = !value.size.equalTo(super.frame.size)
|
||||
super.frame = value
|
||||
|
||||
if updateImage {
|
||||
self.updateSize(size: value.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func nodeDidLoad() {
|
||||
super.nodeDidLoad()
|
||||
}
|
||||
|
||||
public func updateSize(size: CGSize) {
|
||||
self.contentNode.position = CGRect(origin: CGPoint(), size: size).center
|
||||
self.contentNode.bounds = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.contentNode.updateSize(size: size)
|
||||
|
||||
if let storyIndicatorTheme = self.storyIndicatorTheme {
|
||||
self.updateStoryIndicator(theme: storyIndicatorTheme, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public func playArchiveAnimation() {
|
||||
self.contentNode.playArchiveAnimation()
|
||||
}
|
||||
|
||||
public func setPeer(
|
||||
context: AccountContext,
|
||||
account: Account? = nil,
|
||||
theme: PresentationTheme,
|
||||
peer: EnginePeer?,
|
||||
authorOfMessage: MessageReference? = nil,
|
||||
overrideImage: AvatarNodeImageOverride? = nil,
|
||||
emptyColor: UIColor? = nil,
|
||||
clipStyle: AvatarNodeClipStyle = .round,
|
||||
synchronousLoad: Bool = false,
|
||||
displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0),
|
||||
storeUnrounded: Bool = false
|
||||
) {
|
||||
self.contentNode.setPeer(
|
||||
context: context,
|
||||
account: account,
|
||||
theme: theme,
|
||||
peer: peer,
|
||||
authorOfMessage: authorOfMessage,
|
||||
overrideImage: overrideImage,
|
||||
emptyColor: emptyColor,
|
||||
clipStyle: clipStyle,
|
||||
synchronousLoad: synchronousLoad,
|
||||
displayDimensions: displayDimensions,
|
||||
storeUnrounded: storeUnrounded
|
||||
)
|
||||
}
|
||||
|
||||
public func setCustomLetters(_ letters: [String], explicitColor: AvatarNodeColorOverride? = nil, icon: AvatarNodeExplicitIcon? = nil) {
|
||||
self.contentNode.setCustomLetters(letters, explicitColor: explicitColor, icon: icon)
|
||||
}
|
||||
|
||||
public func setStoryStats(storyStats: StoryStats?, theme: PresentationTheme, transition: Transition) {
|
||||
if self.storyStats != storyStats || self.storyIndicatorTheme !== theme {
|
||||
self.storyStats = storyStats
|
||||
self.storyIndicatorTheme = theme
|
||||
|
||||
self.updateStoryIndicator(theme: theme, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private struct StoryIndicatorParams {
|
||||
let lineWidth: CGFloat
|
||||
let indicatorSize: CGSize
|
||||
let avatarScale: CGFloat
|
||||
|
||||
init(lineWidth: CGFloat, indicatorSize: CGSize, avatarScale: CGFloat) {
|
||||
self.lineWidth = lineWidth
|
||||
self.indicatorSize = indicatorSize
|
||||
self.avatarScale = avatarScale
|
||||
}
|
||||
}
|
||||
|
||||
private func storyIndicatorParams(size: CGSize) -> StoryIndicatorParams {
|
||||
let lineWidth: CGFloat = 2.0
|
||||
|
||||
return StoryIndicatorParams(
|
||||
lineWidth: lineWidth,
|
||||
indicatorSize: CGSize(width: size.width - lineWidth * 4.0, height: size.height - lineWidth * 4.0),
|
||||
avatarScale: (size.width - lineWidth * 4.0) / size.width
|
||||
)
|
||||
}
|
||||
|
||||
private func updateStoryIndicator(theme: PresentationTheme, transition: Transition) {
|
||||
if !self.isNodeLoaded {
|
||||
return
|
||||
}
|
||||
if self.bounds.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
self.storyIndicatorTheme = theme
|
||||
|
||||
let size = self.bounds.size
|
||||
|
||||
if let storyStats = self.storyStats {
|
||||
let indicatorParams = self.storyIndicatorParams(size: size)
|
||||
|
||||
let storyIndicator: ComponentView<Empty>
|
||||
var indicatorTransition = transition
|
||||
if let current = self.storyIndicator {
|
||||
storyIndicator = current
|
||||
} else {
|
||||
updatedNode = node
|
||||
indicatorTransition = transition.withAnimation(.none)
|
||||
storyIndicator = ComponentView()
|
||||
self.storyIndicator = storyIndicator
|
||||
}
|
||||
if let updatedNode = updatedNode {
|
||||
return updatedNode
|
||||
let _ = storyIndicator.update(
|
||||
transition: indicatorTransition,
|
||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||
hasUnseen: storyStats.unseenCount != 0,
|
||||
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriendsItems,
|
||||
theme: theme,
|
||||
activeLineWidth: indicatorParams.lineWidth,
|
||||
inactiveLineWidth: indicatorParams.lineWidth,
|
||||
isGlassBackground: false,
|
||||
counters: AvatarStoryIndicatorComponent.Counters(
|
||||
totalCount: storyStats.totalCount,
|
||||
unseenCount: storyStats.unseenCount
|
||||
)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: indicatorParams.indicatorSize
|
||||
)
|
||||
if let storyIndicatorView = storyIndicator.view {
|
||||
if storyIndicatorView.superview == nil {
|
||||
self.view.addSubview(storyIndicatorView)
|
||||
}
|
||||
indicatorTransition.setFrame(view: storyIndicatorView, frame: CGRect(origin: CGPoint(x: (size.width - indicatorParams.indicatorSize.width) * 0.5, y: (size.height - indicatorParams.indicatorSize.height) * 0.5), size: indicatorParams.indicatorSize))
|
||||
}
|
||||
transition.setScale(view: self.contentNode.view, scale: indicatorParams.avatarScale)
|
||||
} else {
|
||||
return nil
|
||||
transition.setScale(view: self.contentNode.view, scale: 1.0)
|
||||
if let storyIndicator = self.storyIndicator {
|
||||
self.storyIndicator = nil
|
||||
if let storyIndicatorView = storyIndicator.view {
|
||||
transition.setAlpha(view: storyIndicatorView, alpha: 0.0, completion: { [weak storyIndicatorView] _ in
|
||||
storyIndicatorView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,6 +327,8 @@ final class MutableMessageHistoryView {
|
||||
|
||||
private var userId: Int64?
|
||||
|
||||
fileprivate var peerStoryStats: [PeerId: PeerStoryStats] = [:]
|
||||
|
||||
init(
|
||||
postbox: PostboxImpl,
|
||||
orderStatistics: MessageHistoryViewOrderStatistics,
|
||||
@ -388,6 +390,7 @@ final class MutableMessageHistoryView {
|
||||
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
|
||||
|
||||
self.render(postbox: postbox)
|
||||
let _ = self.updateStoryStats(postbox: postbox)
|
||||
}
|
||||
|
||||
private func reset(postbox: PostboxImpl) {
|
||||
@ -411,6 +414,8 @@ final class MutableMessageHistoryView {
|
||||
}
|
||||
}
|
||||
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
|
||||
|
||||
let _ = self.updateStoryStats(postbox: postbox)
|
||||
}
|
||||
|
||||
func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
|
||||
@ -907,6 +912,11 @@ final class MutableMessageHistoryView {
|
||||
if hasChanges {
|
||||
self.render(postbox: postbox)
|
||||
}
|
||||
if hasChanges || !transaction.currentStoryTopItemEvents.isEmpty || !transaction.storyPeerStatesEvents.isEmpty {
|
||||
if self.updateStoryStats(postbox: postbox) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
|
||||
return hasChanges
|
||||
}
|
||||
@ -920,6 +930,26 @@ final class MutableMessageHistoryView {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateStoryStats(postbox: PostboxImpl) -> Bool {
|
||||
//TODO:optimize refresh
|
||||
var peerStoryStats: [PeerId: PeerStoryStats] = [:]
|
||||
if case let .loaded(state) = self.sampledState {
|
||||
for entry in state.entries {
|
||||
if let author = entry.message.author {
|
||||
if let value = fetchPeerStoryStats(postbox: postbox, peerId: author.id) {
|
||||
peerStoryStats[author.id] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.peerStoryStats != peerStoryStats {
|
||||
self.peerStoryStats = peerStoryStats
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func firstHole() -> (MessageHistoryViewHole, MessageHistoryViewRelativeHoleDirection, Int, Int64?)? {
|
||||
switch self.sampledState {
|
||||
case let .loading(loadingSample):
|
||||
@ -965,6 +995,7 @@ public final class MessageHistoryView {
|
||||
public let isLoading: Bool
|
||||
public let isLoadingEarlier: Bool
|
||||
public let isAddedToChatList: Bool
|
||||
public let peerStoryStats: [PeerId: PeerStoryStats]
|
||||
|
||||
public init(tagMask: MessageTags?, namespaces: MessageIdNamespaces, entries: [MessageHistoryEntry], holeEarlier: Bool, holeLater: Bool, isLoading: Bool) {
|
||||
self.tagMask = tagMask
|
||||
@ -983,6 +1014,7 @@ public final class MessageHistoryView {
|
||||
self.isLoading = isLoading
|
||||
self.isLoadingEarlier = true
|
||||
self.isAddedToChatList = false
|
||||
self.peerStoryStats = [:]
|
||||
}
|
||||
|
||||
init(_ mutableView: MutableMessageHistoryView) {
|
||||
@ -1207,6 +1239,7 @@ public final class MessageHistoryView {
|
||||
}
|
||||
|
||||
self.entries = entries
|
||||
self.peerStoryStats = mutableView.peerStoryStats
|
||||
}
|
||||
|
||||
public init(base: MessageHistoryView, fixed combinedReadStates: MessageHistoryViewReadState?, transient transientReadStates: MessageHistoryViewReadState?) {
|
||||
@ -1225,6 +1258,7 @@ public final class MessageHistoryView {
|
||||
self.isLoading = base.isLoading
|
||||
self.isLoadingEarlier = base.isLoadingEarlier
|
||||
self.isAddedToChatList = base.isAddedToChatList
|
||||
self.peerStoryStats = base.peerStoryStats
|
||||
|
||||
if let combinedReadStates = combinedReadStates {
|
||||
switch combinedReadStates {
|
||||
|
@ -50,11 +50,10 @@ final class StoryTopItemsTable: Table {
|
||||
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
|
||||
}
|
||||
|
||||
private let sharedKey = ValueBoxKey(length: 8 + 4)
|
||||
|
||||
private func key(_ key: Key) -> ValueBoxKey {
|
||||
self.sharedKey.setInt64(0, value: key.peerId.toInt64())
|
||||
return self.sharedKey
|
||||
let keyValue = ValueBoxKey(length: 8)
|
||||
keyValue.setInt64(0, value: key.peerId.toInt64())
|
||||
return keyValue
|
||||
}
|
||||
|
||||
public func get(peerId: PeerId) -> Entry? {
|
||||
@ -115,12 +114,11 @@ final class StoryItemsTable: Table {
|
||||
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
|
||||
}
|
||||
|
||||
private let sharedKey = ValueBoxKey(length: 8 + 4)
|
||||
|
||||
private func key(_ key: Key) -> ValueBoxKey {
|
||||
self.sharedKey.setInt64(0, value: key.peerId.toInt64())
|
||||
self.sharedKey.setInt32(8, value: key.id)
|
||||
return self.sharedKey
|
||||
let keyValue = ValueBoxKey(length: 8 + 4)
|
||||
keyValue.setInt64(0, value: key.peerId.toInt64())
|
||||
keyValue.setInt32(8, value: key.id)
|
||||
return keyValue
|
||||
}
|
||||
|
||||
private func lowerBound(peerId: PeerId) -> ValueBoxKey {
|
||||
@ -159,6 +157,8 @@ final class StoryItemsTable: Table {
|
||||
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), values: { key, value in
|
||||
let id = key.getInt32(8)
|
||||
|
||||
assert(peerId.toInt64() == key.getInt64(0))
|
||||
|
||||
let entry: CodableEntry
|
||||
var expirationTimestamp: Int32?
|
||||
|
||||
|
@ -1127,7 +1127,7 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32, asPi
|
||||
return .complete()
|
||||
}
|
||||
|
||||
#if DEBUG && true
|
||||
#if DEBUG && false
|
||||
if "".isEmpty {
|
||||
return .complete()
|
||||
}
|
||||
@ -1156,7 +1156,7 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32, asPi
|
||||
|
||||
account.stateManager.injectStoryUpdates(updates: [.read(peerId: peerId, maxId: id)])
|
||||
|
||||
#if DEBUG && false
|
||||
#if DEBUG && true
|
||||
if "".isEmpty {
|
||||
return .complete()
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public final class ChatControllerInteraction {
|
||||
public enum OpenPeerSource {
|
||||
case `default`
|
||||
case reaction
|
||||
case groupParticipant
|
||||
case groupParticipant(storyStats: PeerStoryStats?, avatarHeaderNode: ASDisplayNode?)
|
||||
}
|
||||
|
||||
public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
|
||||
|
@ -1426,7 +1426,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
}
|
||||
|
||||
leftInfoView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
contentContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
|
||||
for transitionViewImpl in transitionViewsImpl {
|
||||
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
||||
|
@ -56,7 +56,7 @@ private func calculateCircleIntersection(center: CGPoint, otherCenter: CGPoint,
|
||||
return (point1Angle, point2Angle)
|
||||
}
|
||||
|
||||
private func calculateMergingCircleShape(center: CGPoint, leftCenter: CGPoint?, rightCenter: CGPoint?, radius: CGFloat, totalCount: Int, unseenCount: Int, isSeen: Bool) -> CGPath {
|
||||
private func calculateMergingCircleShape(center: CGPoint, leftCenter: CGPoint?, rightCenter: CGPoint?, radius: CGFloat, totalCount: Int, unseenCount: Int, isSeen: Bool, segmentFraction: CGFloat) -> CGPath {
|
||||
let leftAngles = leftCenter.flatMap { calculateCircleIntersection(center: center, otherCenter: $0, radius: radius) }
|
||||
let rightAngles = rightCenter.flatMap { calculateCircleIntersection(center: center, otherCenter: $0, radius: radius) }
|
||||
|
||||
@ -95,7 +95,7 @@ private func calculateMergingCircleShape(center: CGPoint, leftCenter: CGPoint?,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let segmentSpacing: CGFloat = 4.0
|
||||
let segmentSpacing: CGFloat = 4.0 * segmentFraction
|
||||
let segmentSpacingAngle: CGFloat = segmentSpacing / radius
|
||||
let segmentAngle = (2.0 * CGFloat.pi - segmentSpacingAngle * CGFloat(segmentCount)) / CGFloat(segmentCount)
|
||||
for i in 0 ..< segmentCount {
|
||||
@ -111,7 +111,9 @@ private func calculateMergingCircleShape(center: CGPoint, leftCenter: CGPoint?,
|
||||
}
|
||||
}
|
||||
|
||||
let startAngle = segmentSpacingAngle * 0.5 - CGFloat.pi * 0.5 + CGFloat(i) * (segmentSpacingAngle + segmentAngle)
|
||||
var startAngle = segmentSpacingAngle * 0.5 - CGFloat.pi * 0.5 + CGFloat(i) * (segmentSpacingAngle + segmentAngle)
|
||||
startAngle += (1.0 - segmentFraction) * CGFloat.pi * 2.0 * 0.25
|
||||
|
||||
let endAngle = startAngle + segmentAngle
|
||||
path.move(to: CGPoint(x: center.x + cos(startAngle) * radius, y: center.y + sin(startAngle) * radius))
|
||||
path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
|
||||
@ -765,8 +767,8 @@ public final class StoryPeerListItemComponent: Component {
|
||||
}
|
||||
Transition.immediate.setShapeLayerPath(layer: self.avatarShapeLayer, path: avatarPath)
|
||||
|
||||
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: true))
|
||||
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: false))
|
||||
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeSeenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: true, segmentFraction: component.expandedAlphaFraction))
|
||||
Transition.immediate.setShapeLayerPath(layer: self.indicatorShapeUnseenLayer, path: calculateMergingCircleShape(center: indicatorCenter, leftCenter: mappedLeftCenter, rightCenter: mappedRightCenter, radius: indicatorRadius - indicatorLineUnseenWidth * 0.5, totalCount: component.totalCount, unseenCount: component.unseenCount, isSeen: false, segmentFraction: component.expandedAlphaFraction))
|
||||
|
||||
//TODO:localize
|
||||
let titleString: String
|
||||
@ -810,7 +812,7 @@ public final class StoryPeerListItemComponent: Component {
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleString, font: Font.regular(11.0), textColor: component.theme.list.itemPrimaryTextColor)),
|
||||
text: .plain(NSAttributedString(string: titleString, font: Font.regular(11.0), textColor: (component.unseenCount != 0 || component.peer.id == component.context.account.peerId) ? component.theme.list.itemPrimaryTextColor : component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.5))),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
environment: {},
|
||||
@ -840,7 +842,7 @@ public final class StoryPeerListItemComponent: Component {
|
||||
self.progressLayer = progressLayer
|
||||
self.indicatorMaskUnseenLayer.addSublayer(progressLayer)
|
||||
}
|
||||
let progressFrame = CGRect(origin: CGPoint(), size: indicatorFrame.size).insetBy(dx: 2.0, dy: 2.0)
|
||||
let progressFrame = CGRect(origin: CGPoint(), size: indicatorFrame.size).insetBy(dx: 4.0, dy: 4.0)
|
||||
progressTransition.setFrame(layer: progressLayer, frame: progressFrame)
|
||||
|
||||
switch ringAnimation {
|
||||
@ -851,9 +853,9 @@ public final class StoryPeerListItemComponent: Component {
|
||||
} else {
|
||||
progressTransition = .easeInOut(duration: 0.3)
|
||||
}
|
||||
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, value: .progress(progress), transition: progressTransition)
|
||||
progressLayer.update(size: progressFrame.size, lineWidth: indicatorLineUnseenWidth, value: .progress(progress), transition: progressTransition)
|
||||
case .loading:
|
||||
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, value: .indefinite, transition: transition)
|
||||
progressLayer.update(size: progressFrame.size, lineWidth: indicatorLineUnseenWidth, value: .indefinite, transition: transition)
|
||||
}
|
||||
self.indicatorShapeSeenLayer.opacity = 0.0
|
||||
self.indicatorShapeUnseenLayer.opacity = 0.0
|
||||
|
@ -1162,7 +1162,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
))
|
||||
}, openPeer: { [weak self] peer, navigation, fromMessage, source in
|
||||
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: source == .reaction ? fromMessage?.id : nil, expandAvatar: source == .groupParticipant)
|
||||
var expandAvatar = false
|
||||
if case let .groupParticipant(storyStats, avatarHeaderNode) = source {
|
||||
if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNode {
|
||||
self?.openStories(peerId: peer.id, avatarHeaderNode: avatarHeaderNode)
|
||||
return
|
||||
} else {
|
||||
expandAvatar = true
|
||||
}
|
||||
}
|
||||
var fromReactionMessageId: MessageId?
|
||||
if case .reaction = source {
|
||||
fromReactionMessageId = fromMessage?.id
|
||||
}
|
||||
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: fromReactionMessageId, expandAvatar: expandAvatar)
|
||||
}, openPeerMention: { [weak self] name in
|
||||
self?.openPeerMention(name)
|
||||
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
|
||||
@ -16998,6 +17011,72 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
|
||||
private func openStories(peerId: EnginePeer.Id, avatarHeaderNode: ChatMessageAvatarHeaderNode) {
|
||||
let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: peerId, singlePeer: true)
|
||||
let _ = (storyContent.state
|
||||
|> filter { $0.slice != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak avatarHeaderNode] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||
if let avatarHeaderNode {
|
||||
transitionIn = StoryContainerScreen.TransitionIn(
|
||||
sourceView: avatarHeaderNode.avatarNode.view,
|
||||
sourceRect: avatarHeaderNode.avatarNode.view.bounds,
|
||||
sourceCornerRadius: avatarHeaderNode.avatarNode.view.bounds.width * 0.5,
|
||||
sourceIsAvatar: false
|
||||
)
|
||||
avatarHeaderNode.avatarNode.isHidden = true
|
||||
}
|
||||
|
||||
let storyContainerScreen = StoryContainerScreen(
|
||||
context: self.context,
|
||||
content: storyContent,
|
||||
transitionIn: transitionIn,
|
||||
transitionOut: { peerId, _ in
|
||||
guard let avatarHeaderNode else {
|
||||
return nil
|
||||
}
|
||||
let destinationView = avatarHeaderNode.avatarNode.view
|
||||
return StoryContainerScreen.TransitionOut(
|
||||
destinationView: destinationView,
|
||||
transitionView: StoryContainerScreen.TransitionView(
|
||||
makeView: { [weak destinationView] in
|
||||
let parentView = UIView()
|
||||
if let copyView = destinationView?.snapshotContentTree(unhide: true) {
|
||||
parentView.addSubview(copyView)
|
||||
}
|
||||
return parentView
|
||||
},
|
||||
updateView: { copyView, state, transition in
|
||||
guard let view = copyView.subviews.first else {
|
||||
return
|
||||
}
|
||||
let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress)
|
||||
transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
|
||||
transition.setScale(view: view, scale: size.width / state.destinationSize.width)
|
||||
},
|
||||
insertCloneTransitionView: nil
|
||||
),
|
||||
destinationRect: destinationView.bounds,
|
||||
destinationCornerRadius: destinationView.bounds.width * 0.5,
|
||||
destinationIsAvatar: false,
|
||||
completed: { [weak avatarHeaderNode] in
|
||||
guard let avatarHeaderNode else {
|
||||
return
|
||||
}
|
||||
avatarHeaderNode.avatarNode.isHidden = false
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
self.push(storyContainerScreen)
|
||||
})
|
||||
}
|
||||
|
||||
private func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) {
|
||||
let _ = self.presentVoiceMessageDiscardAlert(action: {
|
||||
let disposable: MetaDisposable
|
||||
|
@ -101,7 +101,7 @@ func chatHistoryEntriesForView(
|
||||
|
||||
if let maybeJoinMessage = joinMessage {
|
||||
if message.timestamp > maybeJoinMessage.timestamp, (!view.holeEarlier || count > 0) {
|
||||
entries.append(.MessageEntry(maybeJoinMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false)))
|
||||
entries.append(.MessageEntry(maybeJoinMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil)))
|
||||
joinMessage = nil
|
||||
}
|
||||
}
|
||||
@ -182,7 +182,7 @@ func chatHistoryEntriesForView(
|
||||
} else {
|
||||
selection = .none
|
||||
}
|
||||
groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false, isCentered: false), entry.location))
|
||||
groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }), entry.location))
|
||||
} else {
|
||||
let selection: ChatHistoryMessageSelection
|
||||
if let selectedMessages = selectedMessages {
|
||||
@ -190,7 +190,7 @@ func chatHistoryEntriesForView(
|
||||
} else {
|
||||
selection = .none
|
||||
}
|
||||
entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: false)))
|
||||
entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] })))
|
||||
}
|
||||
} else {
|
||||
let selection: ChatHistoryMessageSelection
|
||||
@ -199,7 +199,7 @@ func chatHistoryEntriesForView(
|
||||
} else {
|
||||
selection = .none
|
||||
}
|
||||
entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: false)))
|
||||
entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] })))
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +222,7 @@ func chatHistoryEntriesForView(
|
||||
}
|
||||
|
||||
if let maybeJoinMessage = joinMessage, !view.holeLater {
|
||||
entries.append(.MessageEntry(maybeJoinMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false)))
|
||||
entries.append(.MessageEntry(maybeJoinMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil)))
|
||||
joinMessage = nil
|
||||
}
|
||||
|
||||
@ -283,12 +283,12 @@ func chatHistoryEntriesForView(
|
||||
if messages.count > 1, let groupInfo = messages[0].groupInfo {
|
||||
var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)] = []
|
||||
for message in messages {
|
||||
groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false, isCentered: false), nil))
|
||||
groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }), nil))
|
||||
}
|
||||
entries.insert(.MessageGroupEntry(groupInfo, groupMessages, presentationData), at: 0)
|
||||
} else {
|
||||
if !hasTopicCreated {
|
||||
entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id], isPlaying: false, isCentered: false)), at: 0)
|
||||
entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id], isPlaying: false, isCentered: false, authorStoryStats: messages[0].author.flatMap { view.peerStoryStats[$0.id] })), at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,7 +362,7 @@ func chatHistoryEntriesForView(
|
||||
if !dynamicAdMessages.isEmpty {
|
||||
assert(entries.sorted() == entries)
|
||||
for message in dynamicAdMessages {
|
||||
entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false)))
|
||||
entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil)))
|
||||
}
|
||||
entries.sort()
|
||||
}
|
||||
@ -396,7 +396,7 @@ func chatHistoryEntriesForView(
|
||||
associatedStories: message.associatedStories
|
||||
)
|
||||
nextAdMessageId += 1
|
||||
entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false)))
|
||||
entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil)))
|
||||
}
|
||||
}
|
||||
} else if includeSearchEntry {
|
||||
|
@ -18,14 +18,16 @@ public struct ChatMessageEntryAttributes: Equatable {
|
||||
var updatingMedia: ChatUpdatingMessageMedia?
|
||||
var isPlaying: Bool
|
||||
var isCentered: Bool
|
||||
var authorStoryStats: PeerStoryStats?
|
||||
|
||||
init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?, isPlaying: Bool, isCentered: Bool) {
|
||||
init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?, isPlaying: Bool, isCentered: Bool, authorStoryStats: PeerStoryStats?) {
|
||||
self.rank = rank
|
||||
self.isContact = isContact
|
||||
self.contentTypeHint = contentTypeHint
|
||||
self.updatingMedia = updatingMedia
|
||||
self.isPlaying = isPlaying
|
||||
self.isCentered = isCentered
|
||||
self.authorStoryStats = authorStoryStats
|
||||
}
|
||||
|
||||
public init() {
|
||||
@ -35,6 +37,7 @@ public struct ChatMessageEntryAttributes: Equatable {
|
||||
self.updatingMedia = nil
|
||||
self.isPlaying = false
|
||||
self.isCentered = false
|
||||
self.authorStoryStats = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,8 +374,9 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
|
||||
let presentationData: ChatPresentationData
|
||||
let context: AccountContext
|
||||
let controllerInteraction: ChatControllerInteraction
|
||||
let storyStats: PeerStoryStats?
|
||||
|
||||
init(timestamp: Int32, peerId: PeerId, peer: Peer?, messageReference: MessageReference?, message: Message, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction) {
|
||||
init(timestamp: Int32, peerId: PeerId, peer: Peer?, messageReference: MessageReference?, message: Message, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, storyStats: PeerStoryStats?) {
|
||||
self.peerId = peerId
|
||||
self.peer = peer
|
||||
self.messageReference = messageReference
|
||||
@ -395,6 +396,7 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
|
||||
self.context = context
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.id = ListViewItemNode.HeaderId(space: 1, id: Id(peerId: peerId, timestampId: dateHeaderTimestampId(timestamp: timestamp)))
|
||||
self.storyStats = storyStats
|
||||
}
|
||||
|
||||
let stickDirection: ListViewItemHeaderStickDirection = .top
|
||||
@ -414,14 +416,15 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
|
||||
}
|
||||
|
||||
func node(synchronousLoad: Bool) -> ListViewItemHeaderNode {
|
||||
return ChatMessageAvatarHeaderNode(peerId: self.peerId, peer: self.peer, messageReference: self.messageReference, adMessageId: self.adMessageId, presentationData: self.presentationData, context: self.context, controllerInteraction: self.controllerInteraction, synchronousLoad: synchronousLoad)
|
||||
return ChatMessageAvatarHeaderNode(peerId: self.peerId, peer: self.peer, messageReference: self.messageReference, adMessageId: self.adMessageId, presentationData: self.presentationData, context: self.context, controllerInteraction: self.controllerInteraction, storyStats: self.storyStats, synchronousLoad: synchronousLoad)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
|
||||
guard let node = node as? ChatMessageAvatarHeaderNode, let next = next as? ChatMessageAvatarHeader else {
|
||||
guard let node = node as? ChatMessageAvatarHeaderNode else {
|
||||
return
|
||||
}
|
||||
node.updatePresentationData(next.presentationData, context: next.context)
|
||||
node.updatePresentationData(self.presentationData, context: self.context)
|
||||
node.updateStoryStats(storyStats: self.storyStats, theme: self.presentationData.theme.theme, force: false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,6 +436,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
private let context: AccountContext
|
||||
private var presentationData: ChatPresentationData
|
||||
private let controllerInteraction: ChatControllerInteraction
|
||||
private var storyStats: PeerStoryStats?
|
||||
|
||||
private let peerId: PeerId
|
||||
private let messageReference: MessageReference?
|
||||
@ -440,7 +444,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
private let adMessageId: EngineMessage.Id?
|
||||
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let avatarNode: AvatarNode
|
||||
let avatarNode: AvatarNode
|
||||
private var avatarVideoNode: AvatarVideoNode?
|
||||
|
||||
private var cachedDataDisposable = MetaDisposable()
|
||||
@ -459,7 +463,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(peerId: PeerId, peer: Peer?, messageReference: MessageReference?, adMessageId: EngineMessage.Id?, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, synchronousLoad: Bool) {
|
||||
init(peerId: PeerId, peer: Peer?, messageReference: MessageReference?, adMessageId: EngineMessage.Id?, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, storyStats: PeerStoryStats?, synchronousLoad: Bool) {
|
||||
self.peerId = peerId
|
||||
self.peer = peer
|
||||
self.messageReference = messageReference
|
||||
@ -467,6 +471,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
self.presentationData = presentationData
|
||||
self.context = context
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.storyStats = storyStats
|
||||
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
|
||||
@ -483,6 +488,10 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
self.setPeer(context: context, theme: presentationData.theme.theme, synchronousLoad: synchronousLoad, peer: peer, authorOfMessage: messageReference, emptyColor: .black)
|
||||
}
|
||||
|
||||
if let storyStats {
|
||||
self.updateStoryStats(storyStats: storyStats, theme: presentationData.theme.theme, force: true)
|
||||
}
|
||||
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self, let peer = strongSelf.peer else {
|
||||
return
|
||||
@ -596,6 +605,18 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updateStoryStats(storyStats: PeerStoryStats?, theme: PresentationTheme, force: Bool) {
|
||||
if self.storyStats != storyStats || self.presentationData.theme.theme !== theme || force {
|
||||
self.avatarNode.setStoryStats(storyStats: storyStats.flatMap { storyStats in
|
||||
return AvatarNode.StoryStats(
|
||||
totalCount: storyStats.totalCount,
|
||||
unseenCount: storyStats.unseenCount,
|
||||
hasUnseenCloseFriendsItems: false
|
||||
)
|
||||
}, theme: theme, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
@ -603,10 +624,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
|
||||
if self.presentationData !== presentationData {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(x: leftInset + 3.0, y: 0.0), size: CGSize(width: 38.0, height: 38.0))
|
||||
@ -663,7 +685,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, .default)
|
||||
} else {
|
||||
self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, .groupParticipant)
|
||||
self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, .groupParticipant(storyStats: self.storyStats, avatarHeaderNode: self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -406,7 +406,15 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
|
||||
if hasAvatar {
|
||||
if let effectiveAuthor = effectiveAuthor {
|
||||
avatarHeader = ChatMessageAvatarHeader(timestamp: content.index.timestamp, peerId: effectiveAuthor.id, peer: effectiveAuthor, messageReference: MessageReference(message), message: message, presentationData: presentationData, context: context, controllerInteraction: controllerInteraction)
|
||||
let storyStats: PeerStoryStats?
|
||||
switch content {
|
||||
case let .message(_, _, _, attributes, _):
|
||||
storyStats = attributes.authorStoryStats
|
||||
case let .group(messages):
|
||||
storyStats = messages.first?.3.authorStoryStats
|
||||
}
|
||||
|
||||
avatarHeader = ChatMessageAvatarHeader(timestamp: content.index.timestamp, peerId: effectiveAuthor.id, peer: effectiveAuthor, messageReference: MessageReference(message), message: message, presentationData: presentationData, context: context, controllerInteraction: controllerInteraction, storyStats: storyStats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user