Story search improvements

This commit is contained in:
Isaac 2024-06-17 17:29:16 +04:00
parent 71a69dacc5
commit d5d80eb894
4 changed files with 423 additions and 201 deletions

View File

@ -2474,11 +2474,14 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
return (updatedItemValue, inputPeer)
}
|> mapToSignal { storyItem, inputPeer -> Signal<Never, NoError> in
guard let storyItem = storyItem, let inputPeer = inputPeer else {
guard let inputPeer = inputPeer else {
return .complete()
}
if let storyItem {
account.stateManager.injectStoryUpdates(updates: [InternalStoryUpdate.added(peerId: peerId, item: storyItem)])
}
account.stateManager.injectStoryUpdates(updates: [InternalStoryUpdate.updateMyReaction(peerId: peerId, id: id, reaction: reaction)])
return account.network.request(Api.functions.stories.sendReaction(flags: 0, peer: inputPeer, storyId: id, reaction: reaction?.apiReaction ?? .reactionEmpty))
|> map(Optional.init)

View File

@ -9,6 +9,7 @@ enum InternalStoryUpdate {
case added(peerId: PeerId, item: Stories.StoredItem)
case read(peerId: PeerId, maxId: Int32)
case updatePinnedToTopList(peerId: PeerId, ids: [Int32])
case updateMyReaction(peerId: PeerId, id: Int32, reaction: MessageReaction.Reaction?)
}
public final class EngineStoryItem: Equatable {
@ -1198,6 +1199,8 @@ public final class PeerStoryListContext: StoryListContext {
}
case .read:
break
case .updateMyReaction:
break
case let .updatePinnedToTopList(peerId, ids):
if self.peerId == peerId && !self.isArchived {
let previousIds = (finalUpdatedState ?? self.stateValue).pinnedIds
@ -1471,6 +1474,182 @@ public final class SearchStoryListContext: StoryListContext {
f()
}
}
if self.updatesDisposable == nil {
self.updatesDisposable = (self.account.stateManager.storyUpdates
|> deliverOn(self.queue)).start(next: { [weak self] updates in
guard let self else {
return
}
let _ = (self.account.postbox.transaction { transaction -> [PeerId: Peer] in
var peers: [PeerId: Peer] = [:]
for update in updates {
switch update {
case let .added(_, item):
if case let .item(item) = item {
if let views = item.views {
for id in views.seenPeerIds {
if let peer = transaction.getPeer(id) {
peers[peer.id] = peer
}
}
}
if let forwardInfo = item.forwardInfo, case let .known(peerId, _, _) = forwardInfo {
if let peer = transaction.getPeer(peerId) {
peers[peer.id] = peer
}
}
if let peerId = item.authorId {
if let peer = transaction.getPeer(peerId) {
peers[peer.id] = peer
}
}
}
case let .updateMyReaction(_, _, reaction):
if reaction != nil {
if let peer = transaction.getPeer(accountPeerId) {
peers[peer.id] = peer
}
}
default:
break
}
}
return peers
}
|> deliverOn(self.queue)).start(next: { [weak self] peers in
guard let self else {
return
}
var finalUpdatedState: State?
for update in updates {
switch update {
case .deleted:
break
case let .added(peerId, item):
if let index = (finalUpdatedState ?? self.stateValue).items.firstIndex(where: { $0.id == StoryId(peerId: peerId, id: item.id) }) {
let currentItem = (finalUpdatedState ?? self.stateValue).items[index]
if case let .item(item) = item, let media = item.media {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items[index] = State.Item(
id: StoryId(peerId: peerId, id: item.id),
storyItem: EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
alternativeMedia: item.alternativeMedia.flatMap(EngineMedia.init),
mediaAreas: item.mediaAreas,
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
reactedCount: views.reactedCount,
forwardCount: views.forwardCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return peers[id].flatMap(EnginePeer.init)
},
reactions: views.reactions,
hasList: views.hasList
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends,
isContacts: item.isContacts,
isSelectedContacts: item.isSelectedContacts,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited,
isMy: item.isMy,
myReaction: item.myReaction,
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
),
peer: currentItem.peer
)
finalUpdatedState = updatedState
}
}
case let .updateMyReaction(peerId, id, reaction):
if let index = (finalUpdatedState ?? self.stateValue).items.firstIndex(where: { $0.id == StoryId(peerId: peerId, id: id) }) {
let item = (finalUpdatedState ?? self.stateValue).items[index]
var updatedState = finalUpdatedState ?? self.stateValue
let previousViews: Stories.Item.Views? = item.storyItem.views.flatMap { views in
return Stories.Item.Views(
seenCount: views.seenCount,
reactedCount: views.reactedCount,
forwardCount: views.forwardCount,
seenPeerIds: views.seenPeers.map(\.id),
reactions: views.reactions,
hasList: views.hasList
)
}
let updatedViews = _internal_updateStoryViewsForMyReaction(isChannel: peerId.namespace == Namespaces.Peer.CloudChannel, views: previousViews, previousReaction: item.storyItem.myReaction, reaction: reaction)
let mappedViews = updatedViews.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
reactedCount: views.reactedCount,
forwardCount: views.forwardCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return peers[id].flatMap(EnginePeer.init)
},
reactions: views.reactions,
hasList: views.hasList
)
}
updatedState.items[index] = State.Item(
id: item.id,
storyItem: EngineStoryItem(
id: item.storyItem.id,
timestamp: item.storyItem.timestamp,
expirationTimestamp: item.storyItem.expirationTimestamp,
media: item.storyItem.media,
alternativeMedia: item.storyItem.alternativeMedia,
mediaAreas: item.storyItem.mediaAreas,
text: item.storyItem.text,
entities: item.storyItem.entities,
views: mappedViews,
privacy: item.storyItem.privacy,
isPinned: item.storyItem.isPinned,
isExpired: item.storyItem.isExpired,
isPublic: item.storyItem.isPublic,
isPending: item.storyItem.isPending,
isCloseFriends: item.storyItem.isCloseFriends,
isContacts: item.storyItem.isContacts,
isSelectedContacts: item.storyItem.isSelectedContacts,
isForwardingDisabled: item.storyItem.isForwardingDisabled,
isEdited: item.storyItem.isEdited,
isMy: item.storyItem.isMy,
myReaction: reaction,
forwardInfo: item.storyItem.forwardInfo,
author: item.storyItem.author
),
peer: item.peer
)
finalUpdatedState = updatedState
}
case .read:
break
case .updatePinnedToTopList:
break
}
}
if let finalUpdatedState {
self.stateValue = finalUpdatedState
}
})
})
}
})
}
}

View File

@ -1726,18 +1726,18 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
//TODO:localize
let listPeerId: EnginePeer.Id
var splitIndexIntoDays = true
switch self.scope {
case let .peer(id, _, _):
listPeerId = id
case .peer:
break
default:
listPeerId = self.context.account.peerId
splitIndexIntoDays = false
}
let listContext = PeerStoryListContentContextImpl(
context: self.context,
peerId: listPeerId,
listContext: self.listSource,
initialId: item.story.id
initialId: item.story.id,
splitIndexIntoDays: splitIndexIntoDays
)
self.pendingOpenListContext = listContext
self.itemGrid.isUserInteractionEnabled = false

View File

@ -1409,6 +1409,36 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
}
public final class PeerStoryListContentContextImpl: StoryContentContext {
private struct DayIndex: Hashable {
var year: Int32
var day: Int32
init(timestamp: Int32) {
var time: time_t = time_t(timestamp)
var timeinfo: tm = tm()
localtime_r(&time, &timeinfo)
self.year = timeinfo.tm_year
self.day = timeinfo.tm_yday
}
}
private struct PeerData {
let data: (TelegramEngine.EngineData.Item.Peer.Peer.Result,
TelegramEngine.EngineData.Item.Peer.Presence.Result,
TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable.Result,
TelegramEngine.EngineData.Item.Peer.CanViewStats.Result,
TelegramEngine.EngineData.Item.Peer.NotificationSettings.Result,
TelegramEngine.EngineData.Item.NotificationSettings.Global.Result,
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result,
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result,
TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result)
init(data: (TelegramEngine.EngineData.Item.Peer.Peer.Result, TelegramEngine.EngineData.Item.Peer.Presence.Result, TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable.Result, TelegramEngine.EngineData.Item.Peer.CanViewStats.Result, TelegramEngine.EngineData.Item.Peer.NotificationSettings.Result, TelegramEngine.EngineData.Item.NotificationSettings.Global.Result, TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result, TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result, TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result)) {
self.data = data
}
}
private let context: AccountContext
public private(set) var stateValue: StoryContentContextState?
@ -1423,6 +1453,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
}
private var storyDisposable: Disposable?
private var storyDataDisposable = MetaDisposable()
private var requestedStoryKeys = Set<StoryKey>()
private var requestStoryDisposables = DisposableSet()
@ -1435,10 +1466,10 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
private var preloadStoryResourceDisposables: [EngineMedia.Id: Disposable] = [:]
private var pollStoryMetadataDisposables = DisposableSet()
public init(context: AccountContext, peerId: EnginePeer.Id, listContext: StoryListContext, initialId: Int32?) {
self.context = context
private var currentPeerData: (EnginePeer.Id, Promise<PeerData>)?
context.engine.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: [peerId])
public init(context: AccountContext, listContext: StoryListContext, initialId: Int32?, splitIndexIntoDays: Bool) {
self.context = context
let preferHighQualityStories: Signal<Bool, NoError> = combineLatest(
context.sharedContext.automaticMediaDownloadSettings
@ -1457,47 +1488,15 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|> distinctUntilChanged
self.storyDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
TelegramEngine.EngineData.Item.Peer.Presence(id: peerId),
TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable(id: peerId),
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global(),
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId),
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: peerId),
TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: peerId)
),
listContext.state,
self.focusedIdUpdated.get(),
preferHighQualityStories
)
|> deliverOnMainQueue).startStrict(next: { [weak self] data, state, _, preferHighQualityStories in
|> deliverOnMainQueue).startStrict(next: { [weak self] state, _, preferHighQualityStories in
guard let self else {
return
}
let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data
guard let peer else {
return
}
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: [])
let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable,
presence: presence,
canViewStats: canViewStats,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts
)
self.listState = state
let focusedIndex: Int?
if let current = self.focusedId {
if let index = state.items.firstIndex(where: { $0.id == current }) {
@ -1523,29 +1522,63 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
}
}
struct DayIndex: Hashable {
var year: Int32
var day: Int32
let peerData: Signal<PeerData?, NoError>
if let focusedIndex {
let peerId = state.items[focusedIndex].id.peerId
if let currentPeerData = self.currentPeerData, currentPeerData.0 == peerId {
peerData = currentPeerData.1.get() |> map(Optional.init)
} else {
context.engine.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: [peerId])
init(timestamp: Int32) {
var time: time_t = time_t(timestamp)
var timeinfo: tm = tm()
localtime_r(&time, &timeinfo)
let currentPeerData: (EnginePeer.Id, Promise<PeerData>) = (peerId, Promise())
currentPeerData.1.set(context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
TelegramEngine.EngineData.Item.Peer.Presence(id: peerId),
TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable(id: peerId),
TelegramEngine.EngineData.Item.Peer.CanViewStats(id: peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global(),
TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId),
TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: peerId),
TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: peerId)
) |> map { PeerData(data: $0) })
self.currentPeerData = currentPeerData
self.year = timeinfo.tm_year
self.day = timeinfo.tm_yday
peerData = currentPeerData.1.get() |> map(Optional.init)
}
} else {
peerData = .single(nil)
}
self.storyDataDisposable.set((peerData
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let self else {
return
}
self.listState = state
let stateValue: StoryContentContextState
if let focusedIndex = focusedIndex {
if let focusedIndex, let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data?.data, let peer {
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: [])
let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable,
presence: presence,
canViewStats: canViewStats,
isPremiumRequiredForMessaging: isPremiumRequiredForMessaging,
preferHighQualityStories: preferHighQualityStories,
boostsToUnrestrict: boostsToUnrestrict,
appliedBoosts: appliedBoosts
)
let item = state.items[focusedIndex]
self.focusedId = item.id
var allItems: [StoryContentItem] = []
var dayCounts: [DayIndex: Int] = [:]
var itemDayIndices: [Int32: (Int, DayIndex)] = [:]
var itemDayIndices: [StoryId: (Int, DayIndex)] = [:]
for i in 0 ..< state.items.count {
let stateItem = state.items[i]
@ -1553,13 +1586,18 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
id: stateItem.id,
position: i,
dayCounters: nil,
peerId: peer.id,
peerId: stateItem.id.peerId,
storyItem: stateItem.storyItem,
entityFiles: extractItemEntityFiles(item: stateItem.storyItem, allEntityFiles: state.allEntityFiles),
itemPeer: stateItem.peer
))
let day = DayIndex(timestamp: stateItem.storyItem.timestamp)
let day: DayIndex
if splitIndexIntoDays {
day = DayIndex(timestamp: stateItem.storyItem.timestamp)
} else {
day = DayIndex(timestamp: 0)
}
let dayCount: Int
if let current = dayCounts[day] {
dayCount = current + 1
@ -1568,11 +1606,11 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
dayCount = 1
dayCounts[day] = dayCount
}
itemDayIndices[stateItem.storyItem.id] = (dayCount - 1, day)
itemDayIndices[stateItem.id] = (dayCount - 1, day)
}
var dayCounters: StoryContentItem.DayCounters?
if let (offset, day) = itemDayIndices[item.storyItem.id], let dayCount = dayCounts[day] {
if let (offset, day) = itemDayIndices[item.id], let dayCount = dayCounts[day] {
dayCounters = StoryContentItem.DayCounters(
position: offset,
totalCount: dayCount
@ -1587,7 +1625,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
id: item.id,
position: focusedIndex,
dayCounters: dayCounters,
peerId: peer.id,
peerId: item.id.peerId,
storyItem: item.storyItem,
entityFiles: extractItemEntityFiles(item: item.storyItem, allEntityFiles: state.allEntityFiles),
itemPeer: item.peer
@ -1621,8 +1659,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
if let focusedIndex, let slice = stateValue.slice {
var possibleItems: [(EnginePeer, StoryListContext.State.Item)] = []
if peer.id == self.context.account.peerId {
pollItems.append(StoryKey(peerId: peer.id, id: slice.item.storyItem.id))
if slice.item.id.peerId == self.context.account.peerId {
pollItems.append(StoryKey(peerId: slice.item.id.peerId, id: slice.item.id.id))
}
for i in focusedIndex ..< min(focusedIndex + 4, state.items.count) {
@ -1701,11 +1739,13 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
self.pollStoryMetadataDisposables.add(self.context.engine.messages.refreshStoryViews(peerId: peerId, ids: ids).startStrict())
}
}
}))
})
}
deinit {
self.storyDisposable?.dispose()
self.storyDataDisposable.dispose()
self.requestStoryDisposables.dispose()
for (_, disposable) in self.preloadStoryResourceDisposables {