Bot preview

This commit is contained in:
Isaac 2024-07-19 22:47:05 +08:00
parent 9b49738353
commit 7ac40d5b78
4 changed files with 118 additions and 46 deletions

View File

@ -190,6 +190,10 @@ public final class SparseItemGrid: ASDisplayNode {
open var holeAnchor: HoleAnchor {
preconditionFailure()
}
open var isReorderable: Bool {
return false
}
public init() {
}
@ -1148,7 +1152,7 @@ public final class SparseItemGrid: ASDisplayNode {
var itemScale: CGFloat
let itemCornerRadius: CGFloat
if self.isReordering {
if self.isReordering && item.isReorderable {
itemScale = (itemFrame.height - 6.0 * 2.0) / itemFrame.height
itemCornerRadius = 10.0
} else {
@ -1240,7 +1244,7 @@ public final class SparseItemGrid: ASDisplayNode {
if let layer = item.layer {
layer.update(size: layer.bounds.size, insets: layout.containerLayout.insets, displayItem: item, binding: items.itemBinding, item: contentItem)
if self.isReordering {
if self.isReordering, let contentItem, contentItem.isReorderable {
if layer.animation(forKey: "shaking_position") == nil {
startShaking(layer: layer)
}

View File

@ -565,7 +565,7 @@ public struct StoryListContextState: Equatable {
public var peerReference: PeerReference?
public var items: [Item]
public var pinnedIds: Set<Int32>
public var pinnedIds: [Int32]
public var totalCount: Int
public var loadMoreToken: AnyHashable?
public var isCached: Bool
@ -575,7 +575,7 @@ public struct StoryListContextState: Equatable {
public init(
peerReference: PeerReference?,
items: [Item],
pinnedIds: Set<Int32>,
pinnedIds: [Int32],
totalCount: Int,
loadMoreToken: AnyHashable?,
isCached: Bool,
@ -633,7 +633,7 @@ public final class PeerStoryListContext: StoryListContext {
self.peerId = peerId
self.isArchived = isArchived
self.stateValue = State(peerReference: nil, items: [], pinnedIds: Set(), totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
self.stateValue = State(peerReference: nil, items: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
let _ = (account.postbox.transaction { transaction -> (PeerReference?, [State.Item], [Int32], Int, [MediaId: TelegramMediaFile], Bool) in
let key = ValueBoxKey(length: 8 + 1)
@ -723,17 +723,19 @@ public final class PeerStoryListContext: StoryListContext {
return
}
var updatedState = State(peerReference: peerReference, items: items, pinnedIds: Set(pinnedIds), totalCount: totalCount, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: hasCache, allEntityFiles: allEntityFiles, isLoading: false)
var updatedState = State(peerReference: peerReference, items: items, pinnedIds: pinnedIds, totalCount: totalCount, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: hasCache, allEntityFiles: allEntityFiles, isLoading: false)
updatedState.items.sort(by: { lhs, rhs in
let lhsPinned = updatedState.pinnedIds.contains(lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.contains(rhs.storyItem.id)
if lhsPinned != rhsPinned {
if lhsPinned {
return true
} else {
return false
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.firstIndex(of: rhs.storyItem.id)
if let lhsPinned, let rhsPinned {
if lhsPinned != rhsPinned {
return lhsPinned < rhsPinned
}
} else if (lhsPinned == nil) != (rhsPinned == nil) {
return lhsPinned != nil
}
return lhs.storyItem.timestamp > rhs.storyItem.timestamp
})
self.stateValue = updatedState
@ -862,7 +864,7 @@ public final class PeerStoryListContext: StoryListContext {
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.storyItem.asStoryItem()) }, pinnedIds: Array(pinnedIds), totalCount: count)) {
if let entry = CodableEntry(CachedPeerStoryListHead(items: storyItems.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: count)) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}
@ -1124,14 +1126,15 @@ public final class PeerStoryListContext: StoryListContext {
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
), peer: nil))
updatedState.items.sort(by: { lhs, rhs in
let lhsPinned = updatedState.pinnedIds.contains(lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.contains(rhs.storyItem.id)
if lhsPinned != rhsPinned {
if lhsPinned {
return true
} else {
return false
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.firstIndex(of: rhs.storyItem.id)
if let lhsPinned, let rhsPinned {
if lhsPinned != rhsPinned {
return lhsPinned < rhsPinned
}
} else if (lhsPinned == nil) != (rhsPinned == nil) {
return lhsPinned != nil
}
return lhs.storyItem.timestamp > rhs.storyItem.timestamp
})
@ -1180,14 +1183,15 @@ public final class PeerStoryListContext: StoryListContext {
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) }
), peer: nil))
updatedState.items.sort(by: { lhs, rhs in
let lhsPinned = updatedState.pinnedIds.contains(lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.contains(rhs.storyItem.id)
if lhsPinned != rhsPinned {
if lhsPinned {
return true
} else {
return false
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.firstIndex(of: rhs.storyItem.id)
if let lhsPinned, let rhsPinned {
if lhsPinned != rhsPinned {
return lhsPinned < rhsPinned
}
} else if (lhsPinned == nil) != (rhsPinned == nil) {
return lhsPinned != nil
}
return lhs.storyItem.timestamp > rhs.storyItem.timestamp
})
@ -1204,18 +1208,19 @@ public final class PeerStoryListContext: StoryListContext {
case let .updatePinnedToTopList(peerId, ids):
if self.peerId == peerId && !self.isArchived {
let previousIds = (finalUpdatedState ?? self.stateValue).pinnedIds
if previousIds != Set(ids) {
if previousIds != ids {
var updatedState = finalUpdatedState ?? self.stateValue
updatedState.pinnedIds = Set(ids)
updatedState.pinnedIds = ids
updatedState.items.sort(by: { lhs, rhs in
let lhsPinned = updatedState.pinnedIds.contains(lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.contains(rhs.storyItem.id)
if lhsPinned != rhsPinned {
if lhsPinned {
return true
} else {
return false
let lhsPinned = updatedState.pinnedIds.firstIndex(of: lhs.storyItem.id)
let rhsPinned = updatedState.pinnedIds.firstIndex(of: rhs.storyItem.id)
if let lhsPinned, let rhsPinned {
if lhsPinned != rhsPinned {
return lhsPinned < rhsPinned
}
} else if (lhsPinned == nil) != (rhsPinned == nil) {
return lhsPinned != nil
}
return lhs.storyItem.timestamp > rhs.storyItem.timestamp
})
@ -1235,7 +1240,7 @@ public final class PeerStoryListContext: StoryListContext {
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.storyItem.asStoryItem()) }, pinnedIds: Array(pinnedIds), totalCount: Int32(totalCount))) {
if let entry = CodableEntry(CachedPeerStoryListHead(items: items.prefix(100).map { .item($0.storyItem.asStoryItem()) }, pinnedIds: pinnedIds, totalCount: Int32(totalCount))) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedPeerStoryListHeads, key: key), entry: entry)
}
}).start()
@ -1308,7 +1313,7 @@ public final class SearchStoryListContext: StoryListContext {
self.account = account
self.source = source
self.stateValue = State(peerReference: nil, items: [], pinnedIds: Set(), totalCount: 0, loadMoreToken: AnyHashable(""), isCached: false, hasCache: false, allEntityFiles: [:], isLoading: false)
self.stateValue = State(peerReference: nil, items: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(""), isCached: false, hasCache: false, allEntityFiles: [:], isLoading: false)
self.statePromise.set(.single(self.stateValue))
self.loadMore(completion: nil)
@ -2107,7 +2112,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
self.isArchived = isArchived
self.stateValue = State(peerReference: nil, items: [], pinnedIds: Set(), totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
self.stateValue = State(peerReference: nil, items: [], pinnedIds: [], totalCount: 0, loadMoreToken: AnyHashable(0 as Int), isCached: true, hasCache: false, allEntityFiles: [:], isLoading: false)
let localStateKey: PostboxViewKey = .storiesState(key: .local)
@ -2222,7 +2227,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
self.stateValue = State(
peerReference: (peer?._asPeer()).flatMap(PeerReference.init),
items: items,
pinnedIds: Set(),
pinnedIds: [],
totalCount: items.count,
loadMoreToken: nil,
isCached: botPreview != nil,

View File

@ -1305,7 +1305,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
}
}
if let user = peerView.peers[peerView.peerId] as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
if let user = peerView.peers[peerView.peerId] as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), botInfo.flags.contains(.canEdit) {
availablePanes?.insert(.botPreview, at: 0)
} else if let cachedData = peerView.cachedData as? CachedUserData, let botPreview = cachedData.botPreview, !botPreview.media.isEmpty {
availablePanes?.insert(.botPreview, at: 0)

View File

@ -92,12 +92,16 @@ private final class VisualMediaItem: SparseItemGrid.Item {
override var index: Int {
return self.indexValue
}
override var isReorderable: Bool {
return self.isReorderableValue
}
let localMonthTimestamp: Int32
let peer: PeerReference
let storyId: StoryId
let story: EngineStoryItem
let authorPeer: EnginePeer?
let isPinned: Bool
let isReorderableValue: Bool
override var id: AnyHashable {
return AnyHashable(self.storyId)
@ -111,7 +115,7 @@ private final class VisualMediaItem: SparseItemGrid.Item {
return VisualMediaHoleAnchor(index: self.index, storyId: self.storyId, localMonthTimestamp: self.localMonthTimestamp)
}
init(index: Int, peer: PeerReference, storyId: StoryId, story: EngineStoryItem, authorPeer: EnginePeer?, isPinned: Bool, localMonthTimestamp: Int32) {
init(index: Int, peer: PeerReference, storyId: StoryId, story: EngineStoryItem, authorPeer: EnginePeer?, isPinned: Bool, localMonthTimestamp: Int32, isReorderable: Bool) {
self.indexValue = index
self.peer = peer
self.storyId = storyId
@ -119,6 +123,7 @@ private final class VisualMediaItem: SparseItemGrid.Item {
self.authorPeer = authorPeer
self.isPinned = isPinned
self.localMonthTimestamp = localMonthTimestamp
self.isReorderableValue = isReorderable
}
}
@ -2453,6 +2458,18 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: isPinned ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})))
if isPinned {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c?.dismiss(completion: {
guard let self else {
return
}
self.beginReordering()
})
})))
}
}
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Edit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
@ -2769,6 +2786,18 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
continue
}
var isReorderable = false
switch self.scope {
case .botPreview:
isReorderable = !item.storyItem.isPending
case let .peer(id, _, _):
if id == self.context.account.peerId {
isReorderable = state.pinnedIds.contains(item.storyItem.id)
}
default:
break
}
mappedItems.append(VisualMediaItem(
index: mappedItems.count,
peer: peerReference,
@ -2776,7 +2805,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
story: item.storyItem,
authorPeer: item.peer,
isPinned: state.pinnedIds.contains(item.storyItem.id),
localMonthTimestamp: Month(localTimestamp: item.storyItem.timestamp + timezoneOffset).packedValue
localMonthTimestamp: Month(localTimestamp: item.storyItem.timestamp + timezoneOffset).packedValue,
isReorderable: isReorderable
))
}
if mappedItems.count < state.totalCount, let lastItem = state.items.last, let _ = state.loadMoreToken {
@ -2809,7 +2839,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
let currentSynchronous = synchronous && firstTime
let currentReloadAtTop = reloadAtTop && firstTime
self.updateHistory(items: items, pinnedIds: state.pinnedIds, synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop, animated: animated)
self.updateHistory(items: items, pinnedIds: Set(state.pinnedIds), synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop, animated: animated)
}
private func updateHistory(items: SparseItemGrid.Items, pinnedIds: Set<Int32>, synchronous: Bool, reloadAtTop: Bool, animated: Bool) {
@ -2845,13 +2875,31 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
private func reorderIfPossible(item: SparseItemGrid.Item, toIndex: Int) {
if case .botPreview = self.scope, let items = self.items, let item = item as? VisualMediaItem {
if let items = self.items, let item = item as? VisualMediaItem {
var toIndex = toIndex
if case .botPreview = self.scope {
} else if case let .peer(id, _, _) = self.scope {
if id == self.context.account.peerId {
let maxPinnedIndex = items.items.lastIndex(where: { ($0 as? VisualMediaItem)?.isPinned == true })
if let maxPinnedIndex {
toIndex = min(toIndex, maxPinnedIndex)
} else {
return
}
}
} else {
return
}
guard let toItem = items.items.first(where: { $0.index == toIndex }) as? VisualMediaItem else {
return
}
if item.story.isPending || toItem.story.isPending {
return
}
if !item.isReorderable {
return
}
var ids = items.items.compactMap { item -> StoryId? in
return (item as? VisualMediaItem)?.storyId
@ -3980,6 +4028,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.reorderedIds = nil
if case .botPreview = self.scope, let listSource = self.listSource as? BotPreviewStoryListContext {
listSource.reorderItems(ids: reorderedIds)
} else if case let .peer(id, _, _) = self.scope, id == self.context.account.peerId, let items = self.items {
var updatedPinnedIds: [Int32] = []
for id in reorderedIds {
inner: for item in items.items {
if let item = item as? VisualMediaItem {
if item.storyId == id {
if item.isPinned {
updatedPinnedIds.append(id.id)
break inner
}
}
}
}
}
let _ = self.context.engine.messages.updatePinnedToTopStories(peerId: id, ids: updatedPinnedIds).startStandalone()
}
}
}