[WIP] Stories

This commit is contained in:
Ali 2023-06-09 15:52:17 +04:00
parent 3a2f75ab82
commit 9b7e421107
9 changed files with 615 additions and 285 deletions

View File

@ -3493,7 +3493,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2))
}
self.updateVisibleContentOffset()
if !self.useMainQueueTransactions {
self.updateVisibleContentOffset()
}
if self.debugInfo {
//let delta = CACurrentMediaTime() - timestamp
@ -3501,6 +3503,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
completion()
if self.useMainQueueTransactions {
self.updateVisibleContentOffset()
}
}
}

View File

@ -4351,10 +4351,6 @@ func replayFinalState(
}
}
var filteredSubscriptionsOpaqueState: String?
if let state = transaction.getSubscriptionsStoriesState(key: .filtered)?.get(Stories.SubscriptionsState.self) {
filteredSubscriptionsOpaqueState = state.opaqueState
}
var appliedMaxReadId: Int32?
if let currentState = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) {
if let appliedMaxReadIdValue = appliedMaxReadId {
@ -4366,7 +4362,6 @@ func replayFinalState(
transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries)
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
subscriptionsOpaqueState: filteredSubscriptionsOpaqueState,
maxReadId: appliedMaxReadId ?? 0
)))
@ -4381,12 +4376,7 @@ func replayFinalState(
appliedMaxReadId = max(appliedMaxReadId, currentState.maxReadId)
}
var filteredSubscriptionsOpaqueState: String?
if let state = transaction.getSubscriptionsStoriesState(key: .filtered)?.get(Stories.SubscriptionsState.self) {
filteredSubscriptionsOpaqueState = state.opaqueState
}
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
subscriptionsOpaqueState: filteredSubscriptionsOpaqueState,
maxReadId: appliedMaxReadId
)))

View File

@ -106,6 +106,7 @@ public struct Namespaces {
public static let featuredStickersConfiguration: Int8 = 24
public static let emojiSearchCategories: Int8 = 25
public static let cachedEmojiQueryResults: Int8 = 26
public static let cachedPeerStoryListHeads: Int8 = 27
}
public struct UnorderedItemList {

View File

@ -352,39 +352,30 @@ public enum Stories {
public final class PeerState: Equatable, Codable {
private enum CodingKeys: CodingKey {
case subscriptionsOpaqueState
case maxReadId
}
public let subscriptionsOpaqueState: String?
public let maxReadId: Int32
public init(
subscriptionsOpaqueState: String?,
maxReadId: Int32
){
self.subscriptionsOpaqueState = subscriptionsOpaqueState
self.maxReadId = maxReadId
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.subscriptionsOpaqueState = try container.decodeIfPresent(String.self, forKey: .subscriptionsOpaqueState)
self.maxReadId = try container.decode(Int32.self, forKey: .maxReadId)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.subscriptionsOpaqueState, forKey: .subscriptionsOpaqueState)
try container.encode(self.maxReadId, forKey: .maxReadId)
}
public static func ==(lhs: PeerState, rhs: PeerState) -> Bool {
if lhs.subscriptionsOpaqueState != rhs.subscriptionsOpaqueState {
return false
}
if lhs.maxReadId != rhs.maxReadId {
return false
}
@ -864,7 +855,6 @@ func _internal_markStoryAsSeen(account: Account, peerId: PeerId, id: Int32, asPi
return account.postbox.transaction { transaction -> Api.InputUser? in
if let peerStoryState = transaction.getPeerStoryState(peerId: peerId)?.get(Stories.PeerState.self) {
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
subscriptionsOpaqueState: peerStoryState.subscriptionsOpaqueState,
maxReadId: max(peerStoryState.maxReadId, id)
)))
}

View File

@ -318,15 +318,6 @@ public final class StorySubscriptionsContext {
var updatedPeerEntries: [StoryItemsTableEntry] = []
for story in stories {
if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) {
/*#if DEBUG
if "".isEmpty {
if let codedEntry = CodableEntry(Stories.StoredItem.placeholder(Stories.Placeholder(id: storedItem.id, timestamp: storedItem.timestamp))) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id))
}
continue
}
#endif*/
if case .placeholder = storedItem, let previousEntry = previousPeerEntries.first(where: { $0.id == storedItem.id }) {
updatedPeerEntries.append(previousEntry)
} else {
@ -341,7 +332,6 @@ public final class StorySubscriptionsContext {
transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries)
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
subscriptionsOpaqueState: state,
maxReadId: maxReadId ?? 0
)))
}
@ -415,122 +405,470 @@ public final class StorySubscriptionsContext {
}
}
private final class CachedPeerStoryListHead: Codable {
let items: [Stories.StoredItem]
let totalCount: Int32
init(items: [Stories.StoredItem], totalCount: Int32) {
self.items = items
self.totalCount = totalCount
}
}
public final class PeerStoryListContext {
private final class Impl {
private let queue: Queue
private let account: Account
private let peerId: EnginePeer.Id
private let isArchived: Bool
private let statePromise = Promise<State>()
private var stateValue: State {
didSet {
self.statePromise.set(.single(self.stateValue))
}
}
var state: Signal<State, NoError> {
return self.statePromise.get()
}
private var isLoadingMore: Bool = false
private var requestDisposable: Disposable?
private var updatesDisposable: Disposable?
init(queue: Queue, account: Account, peerId: EnginePeer.Id, isArchived: Bool) {
self.queue = queue
self.account = account
self.peerId = peerId
self.isArchived = isArchived
self.stateValue = State(peerReference: nil, items: [], totalCount: 0, loadMoreToken: 0, isCached: true)
let _ = (account.postbox.transaction { transaction -> (PeerReference?, [EngineStoryItem], Int) in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key))?.get(CachedPeerStoryListHead.self)
guard let cached = cached else {
return (nil, [], 0)
}
var items: [EngineStoryItem] = []
for storedItem in cached.items {
if case let .item(item) = storedItem, let media = item.media {
let mappedItem = EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return transaction.getPeer(id).flatMap(EnginePeer.init)
}
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic
)
items.append(mappedItem)
}
}
let peerReference = transaction.getPeer(peerId).flatMap(PeerReference.init)
return (peerReference, items, Int(cached.totalCount))
}
|> deliverOn(self.queue)).start(next: { [weak self] peerReference, items, totalCount in
guard let self else {
return
}
self.stateValue = State(peerReference: peerReference, items: items, totalCount: totalCount, loadMoreToken: 0, isCached: true)
self.loadMore()
})
}
deinit {
self.requestDisposable?.dispose()
}
func loadMore() {
if self.isLoadingMore {
return
}
guard let loadMoreToken = self.stateValue.loadMoreToken else {
return
}
self.isLoadingMore = true
let peerId = self.peerId
let account = self.account
let isArchived = self.isArchived
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputUser -> Signal<([EngineStoryItem], Int, PeerReference?), NoError> in
guard let inputUser = inputUser else {
return .single(([], 0, nil))
}
let signal: Signal<Api.stories.Stories, MTRpcError>
if isArchived {
signal = account.network.request(Api.functions.stories.getStoriesArchive(offsetId: Int32(loadMoreToken), limit: 100))
} else {
signal = account.network.request(Api.functions.stories.getPinnedStories(userId: inputUser, offsetId: Int32(loadMoreToken), limit: 100))
}
return signal
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.stories.Stories?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<([EngineStoryItem], Int, PeerReference?), NoError> in
guard let result = result else {
return .single(([], 0, nil))
}
return account.postbox.transaction { transaction -> ([EngineStoryItem], Int, PeerReference?) in
var storyItems: [EngineStoryItem] = []
var totalCount: Int = 0
switch result {
case let .stories(count, stories, users):
totalCount = Int(count)
var peers: [Peer] = []
var peerPresences: [PeerId: Api.User] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
peerPresences[telegramUser.id] = user
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
for story in stories {
if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) {
if case let .item(item) = storedItem, let media = item.media {
let mappedItem = EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return transaction.getPeer(id).flatMap(EnginePeer.init)
}
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic
)
storyItems.append(mappedItem)
}
}
}
if loadMoreToken == 0 {
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
if let entry = CodableEntry(CachedPeerStoryListHead(items: storyItems.prefix(100).map { .item($0.asStoryItem()) }, totalCount: count)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}
}
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init))
}
}
}
|> deliverOn(self.queue)).start(next: { [weak self] storyItems, totalCount, peerReference in
guard let `self` = self else {
return
}
self.isLoadingMore = false
var updatedState = self.stateValue
if updatedState.isCached {
updatedState.items.removeAll()
updatedState.isCached = false
}
var existingIds = Set(updatedState.items.map { $0.id })
for item in storyItems {
if existingIds.contains(item.id) {
continue
}
existingIds.insert(item.id)
updatedState.items.append(item)
}
if updatedState.peerReference == nil {
updatedState.peerReference = peerReference
}
updatedState.loadMoreToken = (storyItems.last?.id).flatMap(Int.init)
if updatedState.loadMoreToken != nil {
updatedState.totalCount = max(totalCount, updatedState.items.count)
} else {
updatedState.totalCount = updatedState.items.count
}
self.stateValue = updatedState
if self.updatesDisposable == nil {
self.updatesDisposable = (self.account.stateManager.storyUpdates
|> deliverOn(self.queue)).start(next: { [weak self] updates in
guard let `self` = self else {
return
}
let selfPeerId = self.peerId
let _ = (self.account.postbox.transaction { transaction -> [PeerId: Peer] in
var peers: [PeerId: Peer] = [:]
for update in updates {
switch update {
case let .added(peerId, item):
if selfPeerId == peerId {
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
}
}
}
}
}
default:
break
}
}
return peers
}
|> deliverOn(self.queue)).start(next: { [weak self] peers in
guard let `self` = self else {
return
}
var finalUpdatedState: State?
for update in updates {
switch update {
case let .deleted(peerId, id):
if self.peerId == peerId {
if let index = self.stateValue.items.firstIndex(where: { $0.id == id }) {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items.remove(at: index)
updatedState.totalCount = max(0, updatedState.totalCount - 1)
finalUpdatedState = updatedState
}
}
case let .added(peerId, item):
if self.peerId == peerId {
if let index = self.stateValue.items.firstIndex(where: { $0.id == item.id }) {
if !self.isArchived {
if case let .item(item) = item {
if item.isPinned {
if let media = item.media {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items[index] = EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return peers[id].flatMap(EnginePeer.init)
}
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic
)
finalUpdatedState = updatedState
}
} else {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items.remove(at: index)
updatedState.totalCount = max(0, updatedState.totalCount - 1)
finalUpdatedState = updatedState
}
}
}
} else {
if !self.isArchived {
if case let .item(item) = item {
if item.isPinned {
if let media = item.media {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items.append(EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return peers[id].flatMap(EnginePeer.init)
}
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic
))
updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp
})
finalUpdatedState = updatedState
}
}
}
}
}
}
case .read:
break
}
}
if let finalUpdatedState = finalUpdatedState {
self.stateValue = finalUpdatedState
let items = finalUpdatedState.items
let totalCount = finalUpdatedState.totalCount
let _ = (self.account.postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8 + 1)
key.setInt64(0, value: peerId.toInt64())
key.setInt8(8, value: isArchived ? 1 : 0)
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.asStoryItem()) }, totalCount: Int32(totalCount))) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}).start()
}
})
})
}
})
}
}
public struct State: Equatable {
public var peerReference: PeerReference?
public var items: [EngineStoryItem]
public var totalCount: Int
public var loadMoreToken: Int?
public var isCached: Bool
init(
peerReference: PeerReference?,
items: [EngineStoryItem],
totalCount: Int,
loadMoreToken: Int?
loadMoreToken: Int?,
isCached: Bool
) {
self.peerReference = peerReference
self.items = items
self.totalCount = totalCount
self.loadMoreToken = loadMoreToken
self.isCached = isCached
}
}
private let account: Account
private let peerId: EnginePeer.Id
private let isArchived: Bool
private let statePromise = Promise<State>()
private var stateValue: State {
didSet {
self.statePromise.set(.single(self.stateValue))
}
}
public var state: Signal<State, NoError> {
return self.statePromise.get()
return impl.signalWith { impl, subscriber in
return impl.state.start(next: subscriber.putNext)
}
}
private var isLoadingMore: Bool = false
private var requestDisposable: Disposable?
private var updatesDisposable: Disposable?
private let queue: Queue
private let impl: QueueLocalObject<Impl>
public init(account: Account, peerId: EnginePeer.Id, isArchived: Bool) {
self.account = account
self.peerId = peerId
self.isArchived = isArchived
self.stateValue = State(peerReference: nil, items: [], totalCount: 0, loadMoreToken: 0)
self.statePromise.set(.single(self.stateValue))
self.loadMore()
}
deinit {
self.requestDisposable?.dispose()
let queue = Queue.mainQueue()
self.queue = queue
self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, account: account, peerId: peerId, isArchived: isArchived)
})
}
public func loadMore() {
if self.isLoadingMore {
return
}
guard let loadMoreToken = self.stateValue.loadMoreToken else {
return
self.impl.with { impl in
impl.loadMore()
}
}
}
public final class PeerExpiringStoryListContext {
private final class Impl {
private let queue: Queue
private let account: Account
private let peerId: EnginePeer.Id
self.isLoadingMore = true
private var listDisposable: Disposable?
private var pollDisposable: Disposable?
let peerId = self.peerId
let account = self.account
let isArchived = self.isArchived
self.requestDisposable = (self.account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputUser -> Signal<([EngineStoryItem], Int, PeerReference?), NoError> in
guard let inputUser = inputUser else {
return .single(([], 0, nil))
}
private let statePromise = Promise<State>()
init(queue: Queue, account: Account, peerId: EnginePeer.Id) {
self.queue = queue
self.account = account
self.peerId = peerId
let signal: Signal<Api.stories.Stories, MTRpcError>
if isArchived {
signal = account.network.request(Api.functions.stories.getStoriesArchive(offsetId: Int32(loadMoreToken), limit: 100))
} else {
signal = account.network.request(Api.functions.stories.getPinnedStories(userId: inputUser, offsetId: Int32(loadMoreToken), limit: 100))
}
return signal
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.stories.Stories?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<([EngineStoryItem], Int, PeerReference?), NoError> in
guard let result = result else {
return .single(([], 0, nil))
self.listDisposable = (account.postbox.combinedView(keys: [
PostboxViewKey.storiesState(key: .peer(peerId)),
PostboxViewKey.storyItems(peerId: peerId)
])
|> deliverOn(self.queue)).start(next: { [weak self] views in
guard let self else {
return
}
guard let stateView = views.views[PostboxViewKey.storiesState(key: .peer(peerId))] as? StoryStatesView else {
return
}
guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else {
return
}
return account.postbox.transaction { transaction -> ([EngineStoryItem], Int, PeerReference?) in
var storyItems: [EngineStoryItem] = []
var totalCount: Int = 0
let _ = (self.account.postbox.transaction { transaction -> State? in
let state = stateView.value?.get(Stories.PeerState.self)
switch result {
case let .stories(count, stories, users):
totalCount = Int(count)
var peers: [Peer] = []
var peerPresences: [PeerId: Api.User] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
peerPresences[telegramUser.id] = user
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
for story in stories {
if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) {
if case let .item(item) = storedItem, let media = item.media {
var items: [Item] = []
for item in itemsView.items {
if let item = item.value.get(Stories.StoredItem.self) {
switch item {
case let .item(item):
if let media = item.media {
let mappedItem = EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
@ -551,180 +889,156 @@ public final class PeerStoryListContext {
isExpired: item.isExpired,
isPublic: item.isPublic
)
storyItems.append(mappedItem)
items.append(.item(mappedItem))
}
case let .placeholder(placeholder):
items.append(.placeholder(id: placeholder.id, timestamp: placeholder.timestamp, expirationTimestamp: placeholder.expirationTimestamp))
}
}
}
return (storyItems, totalCount, transaction.getPeer(peerId).flatMap(PeerReference.init))
return State(
items: items,
isCached: false,
maxReadId: state?.maxReadId ?? 0
)
}
|> deliverOn(self.queue)).start(next: { [weak self] state in
guard let self else {
return
}
guard let state else {
return
}
self.statePromise.set(.single(state))
})
})
self.poll()
}
deinit {
self.listDisposable?.dispose()
self.pollDisposable?.dispose()
}
private func poll() {
self.pollDisposable?.dispose()
let account = self.account
let peerId = self.peerId
self.pollDisposable = (self.account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
}).start(next: { [weak self] storyItems, totalCount, peerReference in
guard let `self` = self else {
return
}
self.isLoadingMore = false
var updatedState = self.stateValue
var existingIds = Set(updatedState.items.map { $0.id })
for item in storyItems {
if existingIds.contains(item.id) {
continue
|> mapToSignal { inputUser -> Signal<Never, NoError> in
guard let inputUser else {
return .complete()
}
existingIds.insert(item.id)
updatedState.items.append(item)
}
if updatedState.peerReference == nil {
updatedState.peerReference = peerReference
}
updatedState.loadMoreToken = (storyItems.last?.id).flatMap(Int.init)
if updatedState.loadMoreToken != nil {
updatedState.totalCount = max(totalCount, updatedState.items.count)
} else {
updatedState.totalCount = updatedState.items.count
}
self.stateValue = updatedState
if self.updatesDisposable == nil {
self.updatesDisposable = (self.account.stateManager.storyUpdates
|> deliverOnMainQueue).start(next: { [weak self] updates in
return account.network.request(Api.functions.stories.getUserStories(userId: inputUser))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.stories.UserStories?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> Void in
var updatedPeerEntries: [StoryItemsTableEntry] = []
updatedPeerEntries.removeAll()
if let result = result, case let .userStories(stories, users) = result {
var peers: [Peer] = []
var peerPresences: [PeerId: Api.User] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
peerPresences[telegramUser.id] = user
}
switch stories {
case let .userStories(_, userId, maxReadId, stories):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
let previousPeerEntries: [StoryItemsTableEntry] = transaction.getStoryItems(peerId: peerId)
for story in stories {
if let storedItem = Stories.StoredItem(apiStoryItem: story, peerId: peerId, transaction: transaction) {
if case .placeholder = storedItem, let previousEntry = previousPeerEntries.first(where: { $0.id == storedItem.id }) {
updatedPeerEntries.append(previousEntry)
} else {
if let codedEntry = CodableEntry(storedItem) {
updatedPeerEntries.append(StoryItemsTableEntry(value: codedEntry, id: storedItem.id))
}
}
}
}
transaction.setPeerStoryState(peerId: peerId, state: CodableEntry(Stories.PeerState(
maxReadId: maxReadId ?? 0
)))
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
}
transaction.setStoryItems(peerId: peerId, items: updatedPeerEntries)
}
|> ignoreValues
}
}).start(completed: { [weak self] in
guard let `self` = self else {
return
}
self.pollDisposable = (Signal<Never, NoError>.complete() |> suspendAwareDelay(60.0, queue: self.queue) |> deliverOn(self.queue)).start(completed: { [weak self] in
guard let `self` = self else {
return
}
let selfPeerId = self.peerId
let _ = (self.account.postbox.transaction { transaction -> [PeerId: Peer] in
var peers: [PeerId: Peer] = [:]
for update in updates {
switch update {
case let .added(peerId, item):
if selfPeerId == peerId {
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
}
}
}
}
}
default:
break
}
}
return peers
}
|> deliverOnMainQueue).start(next: { [weak self] peers in
guard let `self` = self else {
return
}
var finalUpdatedState: State?
for update in updates {
switch update {
case let .deleted(peerId, id):
if self.peerId == peerId {
if let index = self.stateValue.items.firstIndex(where: { $0.id == id }) {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items.remove(at: index)
updatedState.totalCount = max(0, updatedState.totalCount - 1)
finalUpdatedState = updatedState
}
}
case let .added(peerId, item):
if self.peerId == peerId {
if let index = self.stateValue.items.firstIndex(where: { $0.id == item.id }) {
if !self.isArchived {
if case let .item(item) = item {
if item.isPinned {
if let media = item.media {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items[index] = EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return peers[id].flatMap(EnginePeer.init)
}
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic
)
finalUpdatedState = updatedState
}
} else {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items.remove(at: index)
updatedState.totalCount = max(0, updatedState.totalCount - 1)
finalUpdatedState = updatedState
}
}
}
} else {
if !self.isArchived {
if case let .item(item) = item {
if item.isPinned {
if let media = item.media {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.items.append(EngineStoryItem(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: EngineMedia(media),
text: item.text,
entities: item.entities,
views: item.views.flatMap { views in
return EngineStoryItem.Views(
seenCount: views.seenCount,
seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in
return peers[id].flatMap(EnginePeer.init)
}
)
},
privacy: item.privacy.flatMap(EngineStoryPrivacy.init),
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic
))
updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp
})
finalUpdatedState = updatedState
}
}
}
}
}
}
case .read:
break
}
}
if let finalUpdatedState = finalUpdatedState {
self.stateValue = finalUpdatedState
}
})
self.poll()
})
})
}
}
public enum Item: Equatable {
case item(EngineStoryItem)
case placeholder(id: Int32, timestamp: Int32, expirationTimestamp: Int32)
}
public final class State: Equatable {
public let items: [Item]
public let isCached: Bool
public let maxReadId: Int32
public init(items: [Item], isCached: Bool, maxReadId: Int32) {
self.items = items
self.isCached = isCached
self.maxReadId = maxReadId
}
public static func ==(lhs: State, rhs: State) -> Bool {
if lhs === rhs {
return true
}
if lhs.items != rhs.items {
return false
}
if lhs.maxReadId != rhs.maxReadId {
return false
}
return true
}
}
private let queue: Queue
private let impl: QueueLocalObject<Impl>
public init(account: Account, peerId: EnginePeer.Id) {
let queue = Queue.mainQueue()
self.queue = queue
self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, account: account, peerId: peerId)
})
}
}

View File

@ -386,7 +386,8 @@ final class PeerInfoStoryGridScreenComponent: Component {
return nil
}
return self.environment?.controller()?.navigationController as? NavigationController
}
},
listContext: nil
)
self.paneNode = paneNode
self.addSubview(paneNode.view)

View File

@ -890,7 +890,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?) {
public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) {
self.context = context
self.peerId = peerId
self.chatLocation = chatLocation
@ -913,7 +913,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
captureProtected: captureProtected
)
self.listSource = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: self.isArchive)
self.listSource = listContext ?? PeerStoryListContext(account: context.account, peerId: peerId, isArchived: self.isArchive)
self.calendarSource = nil
super.init()

View File

@ -192,6 +192,7 @@ final class PeerInfoScreenData {
let groupsInCommon: GroupsInCommonContext?
let linkedDiscussionPeer: Peer?
let members: PeerInfoMembersData?
let storyListContext: PeerStoryListContext?
let encryptionKeyFingerprint: SecretChatKeyFingerprint?
let globalSettings: TelegramGlobalSettings?
let invitations: PeerExportedInvitationsState?
@ -214,6 +215,7 @@ final class PeerInfoScreenData {
groupsInCommon: GroupsInCommonContext?,
linkedDiscussionPeer: Peer?,
members: PeerInfoMembersData?,
storyListContext: PeerStoryListContext?,
encryptionKeyFingerprint: SecretChatKeyFingerprint?,
globalSettings: TelegramGlobalSettings?,
invitations: PeerExportedInvitationsState?,
@ -235,6 +237,7 @@ final class PeerInfoScreenData {
self.groupsInCommon = groupsInCommon
self.linkedDiscussionPeer = linkedDiscussionPeer
self.members = members
self.storyListContext = storyListContext
self.encryptionKeyFingerprint = encryptionKeyFingerprint
self.globalSettings = globalSettings
self.invitations = invitations
@ -390,10 +393,22 @@ func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, chatLoca
case .none, .settings:
return .complete()
case .user, .channel, .group:
return combineLatest(
context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder) |> ignoreValues),
context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> ignoreValues
)
var signals: [Signal<Never, NoError>] = []
signals.append(context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder) |> ignoreValues) |> ignoreValues)
signals.append(context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> ignoreValues)
if case .user = inputData {
signals.append(Signal { _ in
let listContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
return ActionDisposable {
let _ = listContext
}
})
}
return combineLatest(signals)
|> ignoreValues
}
}
@ -529,6 +544,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
groupsInCommon: nil,
linkedDiscussionPeer: nil,
members: nil,
storyListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: globalSettings,
invitations: nil,
@ -561,6 +577,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
groupsInCommon: nil,
linkedDiscussionPeer: nil,
members: nil,
storyListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: nil,
invitations: nil,
@ -667,17 +684,25 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
secretChatKeyFingerprint = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.SecretChatKeyFingerprint(id: secretChatId))
}
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false)
let hasStories: Signal<Bool, NoError> = storyListContext.state
|> map { state -> Bool in
return !state.items.isEmpty
}
|> distinctUntilChanged
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
secretChatKeyFingerprint,
status
status,
hasStories
)
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status -> PeerInfoScreenData in
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories -> PeerInfoScreenData in
var availablePanes = availablePanes
if peerView.peers[peerView.peerId] is TelegramUser {
if hasStories, peerView.peers[peerView.peerId] is TelegramUser {
availablePanes?.insert(.stories, at: 0)
}
@ -700,6 +725,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
groupsInCommon: groupsInCommon,
linkedDiscussionPeer: nil,
members: nil,
storyListContext: storyListContext,
encryptionKeyFingerprint: encryptionKeyFingerprint,
globalSettings: nil,
invitations: nil,
@ -779,6 +805,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
groupsInCommon: nil,
linkedDiscussionPeer: discussionPeer,
members: nil,
storyListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: nil,
invitations: invitations,
@ -982,6 +1009,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
groupsInCommon: nil,
linkedDiscussionPeer: discussionPeer,
members: membersData,
storyListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: nil,
invitations: invitations,

View File

@ -368,7 +368,7 @@ private final class PeerInfoPendingPane {
let paneNode: PeerInfoPaneNode
switch key {
case .stories:
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: false, navigationController: chatControllerInteraction.navigationController)
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, chatLocation: chatLocation, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: false, navigationController: chatControllerInteraction.navigationController, listContext: data.storyListContext)
paneNode = visualPaneNode
visualPaneNode.openCurrentDate = {
openMediaCalendar()