This commit is contained in:
Ali 2023-07-07 18:55:23 +04:00
parent 9d8777443f
commit a6d7f6d45d
13 changed files with 804 additions and 454 deletions

View File

@ -20,6 +20,8 @@ swift_library(
"//submodules/Emoji:Emoji", "//submodules/Emoji:Emoji",
"//submodules/TinyThumbnail:TinyThumbnail", "//submodules/TinyThumbnail:TinyThumbnail",
"//submodules/FastBlur:FastBlur", "//submodules/FastBlur:FastBlur",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

File diff suppressed because it is too large Load Diff

View File

@ -327,6 +327,8 @@ final class MutableMessageHistoryView {
private var userId: Int64? private var userId: Int64?
fileprivate var peerStoryStats: [PeerId: PeerStoryStats] = [:]
init( init(
postbox: PostboxImpl, postbox: PostboxImpl,
orderStatistics: MessageHistoryViewOrderStatistics, orderStatistics: MessageHistoryViewOrderStatistics,
@ -388,6 +390,7 @@ final class MutableMessageHistoryView {
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles) self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
self.render(postbox: postbox) self.render(postbox: postbox)
let _ = self.updateStoryStats(postbox: postbox)
} }
private func reset(postbox: PostboxImpl) { private func reset(postbox: PostboxImpl) {
@ -411,6 +414,8 @@ final class MutableMessageHistoryView {
} }
} }
self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles) self.sampledState = self.state.sample(postbox: postbox, clipHoles: self.clipHoles)
let _ = self.updateStoryStats(postbox: postbox)
} }
func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
@ -907,6 +912,11 @@ final class MutableMessageHistoryView {
if hasChanges { if hasChanges {
self.render(postbox: postbox) self.render(postbox: postbox)
} }
if hasChanges || !transaction.currentStoryTopItemEvents.isEmpty || !transaction.storyPeerStatesEvents.isEmpty {
if self.updateStoryStats(postbox: postbox) {
hasChanges = true
}
}
return hasChanges 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?)? { func firstHole() -> (MessageHistoryViewHole, MessageHistoryViewRelativeHoleDirection, Int, Int64?)? {
switch self.sampledState { switch self.sampledState {
case let .loading(loadingSample): case let .loading(loadingSample):
@ -965,6 +995,7 @@ public final class MessageHistoryView {
public let isLoading: Bool public let isLoading: Bool
public let isLoadingEarlier: Bool public let isLoadingEarlier: Bool
public let isAddedToChatList: Bool public let isAddedToChatList: Bool
public let peerStoryStats: [PeerId: PeerStoryStats]
public init(tagMask: MessageTags?, namespaces: MessageIdNamespaces, entries: [MessageHistoryEntry], holeEarlier: Bool, holeLater: Bool, isLoading: Bool) { public init(tagMask: MessageTags?, namespaces: MessageIdNamespaces, entries: [MessageHistoryEntry], holeEarlier: Bool, holeLater: Bool, isLoading: Bool) {
self.tagMask = tagMask self.tagMask = tagMask
@ -983,6 +1014,7 @@ public final class MessageHistoryView {
self.isLoading = isLoading self.isLoading = isLoading
self.isLoadingEarlier = true self.isLoadingEarlier = true
self.isAddedToChatList = false self.isAddedToChatList = false
self.peerStoryStats = [:]
} }
init(_ mutableView: MutableMessageHistoryView) { init(_ mutableView: MutableMessageHistoryView) {
@ -1207,6 +1239,7 @@ public final class MessageHistoryView {
} }
self.entries = entries self.entries = entries
self.peerStoryStats = mutableView.peerStoryStats
} }
public init(base: MessageHistoryView, fixed combinedReadStates: MessageHistoryViewReadState?, transient transientReadStates: MessageHistoryViewReadState?) { public init(base: MessageHistoryView, fixed combinedReadStates: MessageHistoryViewReadState?, transient transientReadStates: MessageHistoryViewReadState?) {
@ -1225,6 +1258,7 @@ public final class MessageHistoryView {
self.isLoading = base.isLoading self.isLoading = base.isLoading
self.isLoadingEarlier = base.isLoadingEarlier self.isLoadingEarlier = base.isLoadingEarlier
self.isAddedToChatList = base.isAddedToChatList self.isAddedToChatList = base.isAddedToChatList
self.peerStoryStats = base.peerStoryStats
if let combinedReadStates = combinedReadStates { if let combinedReadStates = combinedReadStates {
switch combinedReadStates { switch combinedReadStates {

View File

@ -50,11 +50,10 @@ final class StoryTopItemsTable: Table {
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
} }
private let sharedKey = ValueBoxKey(length: 8 + 4)
private func key(_ key: Key) -> ValueBoxKey { private func key(_ key: Key) -> ValueBoxKey {
self.sharedKey.setInt64(0, value: key.peerId.toInt64()) let keyValue = ValueBoxKey(length: 8)
return self.sharedKey keyValue.setInt64(0, value: key.peerId.toInt64())
return keyValue
} }
public func get(peerId: PeerId) -> Entry? { public func get(peerId: PeerId) -> Entry? {
@ -115,12 +114,11 @@ final class StoryItemsTable: Table {
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false) return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
} }
private let sharedKey = ValueBoxKey(length: 8 + 4)
private func key(_ key: Key) -> ValueBoxKey { private func key(_ key: Key) -> ValueBoxKey {
self.sharedKey.setInt64(0, value: key.peerId.toInt64()) let keyValue = ValueBoxKey(length: 8 + 4)
self.sharedKey.setInt32(8, value: key.id) keyValue.setInt64(0, value: key.peerId.toInt64())
return self.sharedKey keyValue.setInt32(8, value: key.id)
return keyValue
} }
private func lowerBound(peerId: PeerId) -> ValueBoxKey { 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 self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId), end: self.upperBound(peerId: peerId), values: { key, value in
let id = key.getInt32(8) let id = key.getInt32(8)
assert(peerId.toInt64() == key.getInt64(0))
let entry: CodableEntry let entry: CodableEntry
var expirationTimestamp: Int32? var expirationTimestamp: Int32?

View File

@ -1127,7 +1127,7 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32, asPi
return .complete() return .complete()
} }
#if DEBUG && true #if DEBUG && false
if "".isEmpty { if "".isEmpty {
return .complete() 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)]) account.stateManager.injectStoryUpdates(updates: [.read(peerId: peerId, maxId: id)])
#if DEBUG && false #if DEBUG && true
if "".isEmpty { if "".isEmpty {
return .complete() return .complete()
} }

View File

@ -79,7 +79,7 @@ public final class ChatControllerInteraction {
public enum OpenPeerSource { public enum OpenPeerSource {
case `default` case `default`
case reaction case reaction
case groupParticipant case groupParticipant(storyStats: PeerStoryStats?, avatarHeaderNode: ASDisplayNode?)
} }
public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool

View File

@ -1426,7 +1426,8 @@ public final class StoryItemSetContainerComponent: Component {
transitionViewImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) 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 { for transitionViewImpl in transitionViewsImpl {
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame) transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)

View File

@ -56,7 +56,7 @@ private func calculateCircleIntersection(center: CGPoint, otherCenter: CGPoint,
return (point1Angle, point2Angle) 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 leftAngles = leftCenter.flatMap { calculateCircleIntersection(center: center, otherCenter: $0, radius: radius) }
let rightAngles = rightCenter.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 { } else {
let segmentSpacing: CGFloat = 4.0 let segmentSpacing: CGFloat = 4.0 * segmentFraction
let segmentSpacingAngle: CGFloat = segmentSpacing / radius let segmentSpacingAngle: CGFloat = segmentSpacing / radius
let segmentAngle = (2.0 * CGFloat.pi - segmentSpacingAngle * CGFloat(segmentCount)) / CGFloat(segmentCount) let segmentAngle = (2.0 * CGFloat.pi - segmentSpacingAngle * CGFloat(segmentCount)) / CGFloat(segmentCount)
for i in 0 ..< 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 let endAngle = startAngle + segmentAngle
path.move(to: CGPoint(x: center.x + cos(startAngle) * radius, y: center.y + sin(startAngle) * radius)) 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) 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.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.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)) 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 //TODO:localize
let titleString: String let titleString: String
@ -810,7 +812,7 @@ public final class StoryPeerListItemComponent: Component {
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( 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 maximumNumberOfLines: 1
)), )),
environment: {}, environment: {},
@ -840,7 +842,7 @@ public final class StoryPeerListItemComponent: Component {
self.progressLayer = progressLayer self.progressLayer = progressLayer
self.indicatorMaskUnseenLayer.addSublayer(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) progressTransition.setFrame(layer: progressLayer, frame: progressFrame)
switch ringAnimation { switch ringAnimation {
@ -851,9 +853,9 @@ public final class StoryPeerListItemComponent: Component {
} else { } else {
progressTransition = .easeInOut(duration: 0.3) 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: 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.indicatorShapeSeenLayer.opacity = 0.0
self.indicatorShapeUnseenLayer.opacity = 0.0 self.indicatorShapeUnseenLayer.opacity = 0.0

View File

@ -1162,7 +1162,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
)) ))
}, openPeer: { [weak self] peer, navigation, fromMessage, source in }, 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 }, openPeerMention: { [weak self] name in
self?.openPeerMention(name) self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in }, 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) { private func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) {
let _ = self.presentVoiceMessageDiscardAlert(action: { let _ = self.presentVoiceMessageDiscardAlert(action: {
let disposable: MetaDisposable let disposable: MetaDisposable

View File

@ -101,7 +101,7 @@ func chatHistoryEntriesForView(
if let maybeJoinMessage = joinMessage { if let maybeJoinMessage = joinMessage {
if message.timestamp > maybeJoinMessage.timestamp, (!view.holeEarlier || count > 0) { 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 joinMessage = nil
} }
} }
@ -182,7 +182,7 @@ func chatHistoryEntriesForView(
} else { } else {
selection = .none 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 { } else {
let selection: ChatHistoryMessageSelection let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages { if let selectedMessages = selectedMessages {
@ -190,7 +190,7 @@ func chatHistoryEntriesForView(
} else { } else {
selection = .none 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 { } else {
let selection: ChatHistoryMessageSelection let selection: ChatHistoryMessageSelection
@ -199,7 +199,7 @@ func chatHistoryEntriesForView(
} else { } else {
selection = .none 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 { 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 joinMessage = nil
} }
@ -283,12 +283,12 @@ func chatHistoryEntriesForView(
if messages.count > 1, let groupInfo = messages[0].groupInfo { if messages.count > 1, let groupInfo = messages[0].groupInfo {
var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)] = [] var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)] = []
for message in messages { 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) entries.insert(.MessageGroupEntry(groupInfo, groupMessages, presentationData), at: 0)
} else { } else {
if !hasTopicCreated { 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 { if !dynamicAdMessages.isEmpty {
assert(entries.sorted() == entries) assert(entries.sorted() == entries)
for message in dynamicAdMessages { 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() entries.sort()
} }
@ -396,7 +396,7 @@ func chatHistoryEntriesForView(
associatedStories: message.associatedStories associatedStories: message.associatedStories
) )
nextAdMessageId += 1 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 { } else if includeSearchEntry {

View File

@ -18,14 +18,16 @@ public struct ChatMessageEntryAttributes: Equatable {
var updatingMedia: ChatUpdatingMessageMedia? var updatingMedia: ChatUpdatingMessageMedia?
var isPlaying: Bool var isPlaying: Bool
var isCentered: 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.rank = rank
self.isContact = isContact self.isContact = isContact
self.contentTypeHint = contentTypeHint self.contentTypeHint = contentTypeHint
self.updatingMedia = updatingMedia self.updatingMedia = updatingMedia
self.isPlaying = isPlaying self.isPlaying = isPlaying
self.isCentered = isCentered self.isCentered = isCentered
self.authorStoryStats = authorStoryStats
} }
public init() { public init() {
@ -35,6 +37,7 @@ public struct ChatMessageEntryAttributes: Equatable {
self.updatingMedia = nil self.updatingMedia = nil
self.isPlaying = false self.isPlaying = false
self.isCentered = false self.isCentered = false
self.authorStoryStats = nil
} }
} }

View File

@ -374,8 +374,9 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
let presentationData: ChatPresentationData let presentationData: ChatPresentationData
let context: AccountContext let context: AccountContext
let controllerInteraction: ChatControllerInteraction 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.peerId = peerId
self.peer = peer self.peer = peer
self.messageReference = messageReference self.messageReference = messageReference
@ -395,6 +396,7 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
self.context = context self.context = context
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction
self.id = ListViewItemNode.HeaderId(space: 1, id: Id(peerId: peerId, timestampId: dateHeaderTimestampId(timestamp: timestamp))) self.id = ListViewItemNode.HeaderId(space: 1, id: Id(peerId: peerId, timestampId: dateHeaderTimestampId(timestamp: timestamp)))
self.storyStats = storyStats
} }
let stickDirection: ListViewItemHeaderStickDirection = .top let stickDirection: ListViewItemHeaderStickDirection = .top
@ -414,14 +416,15 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
} }
func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { 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?) { 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 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 let context: AccountContext
private var presentationData: ChatPresentationData private var presentationData: ChatPresentationData
private let controllerInteraction: ChatControllerInteraction private let controllerInteraction: ChatControllerInteraction
private var storyStats: PeerStoryStats?
private let peerId: PeerId private let peerId: PeerId
private let messageReference: MessageReference? private let messageReference: MessageReference?
@ -440,7 +444,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
private let adMessageId: EngineMessage.Id? private let adMessageId: EngineMessage.Id?
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
private let avatarNode: AvatarNode let avatarNode: AvatarNode
private var avatarVideoNode: AvatarVideoNode? private var avatarVideoNode: AvatarVideoNode?
private var cachedDataDisposable = MetaDisposable() 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.peerId = peerId
self.peer = peer self.peer = peer
self.messageReference = messageReference self.messageReference = messageReference
@ -467,6 +471,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
self.presentationData = presentationData self.presentationData = presentationData
self.context = context self.context = context
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction
self.storyStats = storyStats
self.containerNode = ContextControllerSourceNode() self.containerNode = ContextControllerSourceNode()
@ -482,6 +487,10 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
if let peer = peer { if let peer = peer {
self.setPeer(context: context, theme: presentationData.theme.theme, synchronousLoad: synchronousLoad, peer: peer, authorOfMessage: messageReference, emptyColor: .black) 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 self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let peer = strongSelf.peer else { guard let strongSelf = self, let peer = strongSelf.peer else {
@ -595,6 +604,18 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
self.hierarchyTrackingLayer = nil self.hierarchyTrackingLayer = nil
} }
} }
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() { override func didLoad() {
super.didLoad() super.didLoad()
@ -603,9 +624,10 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
} }
func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) { func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
self.presentationData = presentationData if self.presentationData !== presentationData {
self.presentationData = presentationData
self.setNeedsLayout() self.setNeedsLayout()
}
} }
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
@ -663,7 +685,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info { 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) self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, .default)
} else { } else {
self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, .groupParticipant) self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, .groupParticipant(storyStats: self.storyStats, avatarHeaderNode: self))
} }
} }
} }

View File

@ -406,7 +406,15 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
if hasAvatar { if hasAvatar {
if let effectiveAuthor = effectiveAuthor { 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)
} }
} }
} }