Sticker pack improvements

This commit is contained in:
isaac 2026-04-28 16:56:02 +04:00
parent bf68a7db3d
commit 511a58e51a
9 changed files with 161 additions and 79 deletions

View file

@ -863,6 +863,8 @@ public final class FeaturedStickersScreen: ViewController {
fileprivate var searchNavigationNode: SearchNavigationContentNode?
private var eventsDisposable: Disposable?
public init(context: AccountContext, highlightedPackId: ItemCollectionId?, forceTheme: PresentationTheme? = nil, stickerActionTitle: String? = nil, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)? = nil) {
self.context = context
self.highlightedPackId = highlightedPackId
@ -906,6 +908,18 @@ public final class FeaturedStickersScreen: ViewController {
}
}
})
self.eventsDisposable = (context.account.stateManager.installedStickerPacksArchivedEvents
|> deliverOnMainQueue).startStrict(next: { [weak self] count in
guard let self else {
return
}
if count == 0 {
return
}
let presentationData = self.presentationData
self.push(textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.ArchivedPacksAlert_Title, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
})
}
required init(coder aDecoder: NSCoder) {
@ -914,6 +928,7 @@ public final class FeaturedStickersScreen: ViewController {
deinit {
self.presentationDataDisposable?.dispose()
self.eventsDisposable?.dispose()
}
private func updatePresentationData() {

View file

@ -172,7 +172,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
presentStickerPackController?(info)
}, addPack: { info in
let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|> mapToSignal { result -> Signal<Void, NoError> in
|> mapToSignal { result -> Signal<AddStickerPackResult, NoError> in
switch result {
case let .result(info, items, installed):
if installed {
@ -186,9 +186,18 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
break
}
return .complete()
} |> deliverOnMainQueue).start()
} |> deliverOnMainQueue).startStandalone()
})
actionsDisposable.add((context.account.stateManager.installedStickerPacksArchivedEvents
|> deliverOnMainQueue).startStandalone(next: { count in
if count == 0 {
return
}
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: nil, title: nil, text: presentationData.strings.ArchivedPacksAlert_Title, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
let stickerPacks = Promise<CombinedView>()
stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]))

View file

@ -864,7 +864,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break

View file

@ -1517,13 +1517,20 @@ private final class StickerPackContainer: ASDisplayNode {
if let (info, items, installed) = self.currentStickerPack {
var dismissed = false
switch self.decideNextAction(self, installed ? .remove : .add) {
case .dismiss:
self.requestDismiss()
dismissed = true
case .navigatedNext, .ignored:
self.updateStickerPackContents([.result(info: StickerPackCollectionInfo.Accessor(info), items: items, installed: !installed)], hasPremium: false)
case .dismiss:
self.requestDismiss()
dismissed = true
case .navigatedNext, .ignored:
self.updateStickerPackContents([.result(info: StickerPackCollectionInfo.Accessor(info), items: items, installed: !installed)], hasPremium: false)
}
guard let controller = self.controller else {
return
}
let navigationController = controller.parentNavigationController ?? (controller.navigationController as? NavigationController)
let context = self.context
let strings = self.presentationData.strings
let actionPerformed = self.controller?.actionPerformed
if installed {
let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
@ -1536,7 +1543,19 @@ private final class StickerPackContainer: ASDisplayNode {
}
})
} else {
let _ = self.context.engine.stickers.addStickerPackInteractively(info: info, items: items).start()
let _ = self.context.engine.stickers.addStickerPackInteractively(info: info, items: items, noDelay: true).startStandalone()
let _ = (self.context.account.stateManager.installedStickerPacksArchivedEvents
|> deliverOnMainQueue
|> take(1)
|> timeout(3.0, queue: .mainQueue(), alternate: .single(0))).startStandalone(next: { [weak navigationController] count in
if count == 0 {
return
}
guard let navigationController else {
return
}
navigationController.pushViewController(textAlertController(context: context, updatedPresentationData: controller.updatedPresentationData, title: nil, text: strings.ArchivedPacksAlert_Title, actions: [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]))
})
if dismissed {
actionPerformed?([(info, items, .add)])
}

View file

@ -360,6 +360,11 @@ public final class AccountStateManager {
return self.starRefBotConnectionEventsPipe.signal()
}
fileprivate let installedStickerPacksArchivedEventsPipe = ValuePipe<Int>()
var installedStickerPacksArchivedEvents: Signal<Int, NoError> {
return self.installedStickerPacksArchivedEventsPipe.signal()
}
private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext()
@ -2036,6 +2041,18 @@ public final class AccountStateManager {
}
}
public var installedStickerPacksArchivedEvents: Signal<Int, NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.installedStickerPacksArchivedEvents.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
func installedStickerPacksArchived(count: Int) {
self.impl.with { impl in
impl.installedStickerPacksArchivedEventsPipe.putNext(count)
}
}
var botPreviewUpdates: Signal<[InternalBotPreviewUpdate], NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.botPreviewUpdates.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)

View file

@ -228,49 +228,60 @@ private func resolveStickerPacks(network: Network, remoteInfos: [ItemCollectionI
}
}
private func installRemoteStickerPacks(network: Network, infos: [StickerPackCollectionInfo]) -> Signal<Set<ItemCollectionId>, NoError> {
var signals: [Signal<Set<ItemCollectionId>, NoError>] = []
private struct InstallRemoteStickerPacksResult {
var ids: Set<ItemCollectionId>
var archivedCount: Int
init(ids: Set<ItemCollectionId>, archivedCount: Int) {
self.ids = ids
self.archivedCount = archivedCount
}
}
private func installRemoteStickerPacks(network: Network, infos: [StickerPackCollectionInfo]) -> Signal<InstallRemoteStickerPacksResult, NoError> {
var signals: [Signal<InstallRemoteStickerPacksResult, NoError>] = []
for info in infos {
let install = network.request(Api.functions.messages.installStickerSet(stickerset: .inputStickerSetID(.init(id: info.id.id, accessHash: info.accessHash)), archived: .boolFalse))
|> map { result -> Set<ItemCollectionId> in
switch result {
case .stickerSetInstallResultSuccess:
return Set()
case let .stickerSetInstallResultArchive(stickerSetInstallResultArchiveData):
let archivedSets = stickerSetInstallResultArchiveData.sets
var archivedIds = Set<ItemCollectionId>()
for archivedSet in archivedSets {
switch archivedSet {
case let .stickerSetCovered(stickerSetCoveredData):
let set = stickerSetCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
case let .stickerSetMultiCovered(stickerSetMultiCoveredData):
let set = stickerSetMultiCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
case let .stickerSetFullCovered(stickerSetFullCoveredData):
let set = stickerSetFullCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
case let .stickerSetNoCovered(stickerSetNoCoveredData):
let set = stickerSetNoCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
}
}
return archivedIds
|> map { result -> InstallRemoteStickerPacksResult in
switch result {
case .stickerSetInstallResultSuccess:
return InstallRemoteStickerPacksResult(ids: Set(), archivedCount: 0)
case let .stickerSetInstallResultArchive(stickerSetInstallResultArchiveData):
let archivedSets = stickerSetInstallResultArchiveData.sets
var archivedIds = Set<ItemCollectionId>()
for archivedSet in archivedSets {
switch archivedSet {
case let .stickerSetCovered(stickerSetCoveredData):
let set = stickerSetCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
case let .stickerSetMultiCovered(stickerSetMultiCoveredData):
let set = stickerSetMultiCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
case let .stickerSetFullCovered(stickerSetFullCoveredData):
let set = stickerSetFullCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
case let .stickerSetNoCovered(stickerSetNoCoveredData):
let set = stickerSetNoCoveredData.set
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
}
}
return InstallRemoteStickerPacksResult(ids: archivedIds, archivedCount: archivedIds.count)
}
|> `catch` { _ -> Signal<Set<ItemCollectionId>, NoError> in
return .single(Set())
}
}
|> `catch` { _ -> Signal<InstallRemoteStickerPacksResult, NoError> in
return .single(InstallRemoteStickerPacksResult(ids: Set(), archivedCount: 0))
}
signals.append(install)
}
return combineLatest(signals)
|> map { idsSets -> Set<ItemCollectionId> in
var result = Set<ItemCollectionId>()
for ids in idsSets {
result.formUnion(ids)
}
return result
|> map { results -> InstallRemoteStickerPacksResult in
var result = InstallRemoteStickerPacksResult(ids: Set(), archivedCount: 0)
for resultValue in results {
result.ids.formUnion(resultValue.ids)
result.archivedCount += resultValue.archivedCount
}
return result
}
}
private func removeRemoteStickerPacks(network: Network, infos: [StickerPackCollectionInfo]) -> Signal<Void, NoError> {
@ -341,12 +352,12 @@ private func reorderRemoteStickerPacks(network: Network, namespace: SynchronizeI
private func synchronizeInstalledStickerPacks(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, namespace: SynchronizeInstalledStickerPacksOperationNamespace, operation: SynchronizeInstalledStickerPacksOperation) -> Signal<Void, NoError> {
let collectionNamespace: ItemCollectionId.Namespace
switch namespace {
case .stickers:
collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks
case .masks:
collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks
case .emoji:
collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks
case .stickers:
collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks
case .masks:
collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks
case .emoji:
collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks
}
let localCollectionInfos = transaction.getItemCollectionsInfos(namespace: collectionNamespace).map { $0.1 as! StickerPackCollectionInfo }
@ -466,12 +477,12 @@ func debugFetchAllStickers(account: Account) -> Signal<Never, NoError> {
private func continueSynchronizeInstalledStickerPacks(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, namespace: SynchronizeInstalledStickerPacksOperationNamespace, operation: SynchronizeInstalledStickerPacksOperation) -> Signal<Void, NoError> {
let collectionNamespace: ItemCollectionId.Namespace
switch namespace {
case .stickers:
collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks
case .masks:
collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks
case .emoji:
collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks
case .stickers:
collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks
case .masks:
collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks
case .emoji:
collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks
}
let localCollectionInfos = transaction.getItemCollectionsInfos(namespace: collectionNamespace).map { $0.1 as! StickerPackCollectionInfo }
@ -479,12 +490,12 @@ private func continueSynchronizeInstalledStickerPacks(transaction: Transaction,
let request: Signal<Api.messages.AllStickers, MTRpcError>
switch namespace {
case .stickers:
request = network.request(Api.functions.messages.getAllStickers(hash: initialLocalHash))
case .masks:
request = network.request(Api.functions.messages.getMaskStickers(hash: initialLocalHash))
case .emoji:
request = network.request(Api.functions.messages.getEmojiStickers(hash: initialLocalHash))
case .stickers:
request = network.request(Api.functions.messages.getAllStickers(hash: initialLocalHash))
case .masks:
request = network.request(Api.functions.messages.getMaskStickers(hash: initialLocalHash))
case .emoji:
request = network.request(Api.functions.messages.getEmojiStickers(hash: initialLocalHash))
}
let sequence = request
@ -661,7 +672,12 @@ private func continueSynchronizeInstalledStickerPacks(transaction: Transaction,
|> then(Signal<Void, NoError>.single(Void())))
|> mapToSignal { _ -> Signal<Set<ItemCollectionId>, NoError> in
return installRemoteStickerPacks(network: network, infos: addRemoteCollectionInfos)
|> mapToSignal { ids -> Signal<Set<ItemCollectionId>, NoError> in
|> mapToSignal { result -> Signal<Set<ItemCollectionId>, NoError> in
let ids = result.ids
if result.archivedCount != 0 || "".isEmpty {
stateManager.installedStickerPacksArchived(count: result.archivedCount + 1)
}
return (reorderRemoteStickerPacks(network: network, namespace: namespace, ids: resultingCollectionInfos.map({ $0.0.id }).filter({ !ids.contains($0) }))
|> then(Signal<Void, NoError>.single(Void())))
|> map { _ -> Set<ItemCollectionId> in

View file

@ -2,26 +2,28 @@ import Foundation
import Postbox
import SwiftSignalKit
public struct AddStickerPackResult {
}
func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil, noDelay: Bool) -> Signal<AddStickerPackResult, NoError> {
return postbox.transaction { transaction -> AddStickerPackResult in
let namespace: SynchronizeInstalledStickerPacksOperationNamespace?
switch info.id.namespace {
case Namespaces.ItemCollection.CloudStickerPacks:
namespace = .stickers
case Namespaces.ItemCollection.CloudMaskPacks:
namespace = .masks
case Namespaces.ItemCollection.CloudEmojiPacks:
namespace = .emoji
default:
namespace = nil
case Namespaces.ItemCollection.CloudStickerPacks:
namespace = .stickers
case Namespaces.ItemCollection.CloudMaskPacks:
namespace = .masks
case Namespaces.ItemCollection.CloudEmojiPacks:
namespace = .emoji
default:
namespace = nil
}
if let namespace = namespace {
if let namespace {
var mappedInfo = info
if items.isEmpty {
mappedInfo = StickerPackCollectionInfo(id: info.id, flags: info.flags, accessHash: info.accessHash, title: info.title, shortName: info.shortName, thumbnail: info.thumbnail, thumbnailFileId: info.thumbnailFileId, immediateThumbnailData: info.immediateThumbnailData, hash: Int32(bitPattern: arc4random()), count: info.count)
}
addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .add([mappedInfo.id]), noDelay: items.isEmpty)
addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .add([mappedInfo.id]), noDelay: noDelay)
var updatedInfos = transaction.getItemCollectionsInfos(namespace: mappedInfo.id.namespace).map { $0.1 as! StickerPackCollectionInfo }
if let index = updatedInfos.firstIndex(where: { $0.id == mappedInfo.id }) {
let currentInfo = updatedInfos[index]
@ -42,6 +44,10 @@ func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCo
transaction.replaceItemCollectionItems(collectionId: mappedInfo.id, items: indexedItems)
}
transaction.replaceItemCollectionInfos(namespace: mappedInfo.id.namespace, itemCollectionInfos: updatedInfos.map { ($0.id, $0) })
return AddStickerPackResult()
} else {
return AddStickerPackResult()
}
}
}

View file

@ -62,8 +62,8 @@ public extension TelegramEngine {
return _internal_searchGifs(account: self.account, query: query, nextOffset: nextOffset)
}
public func addStickerPackInteractively(info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal<Void, NoError> {
return _internal_addStickerPackInteractively(postbox: self.account.postbox, info: info, items: items, positionInList: positionInList)
public func addStickerPackInteractively(info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil, noDelay: Bool = false) -> Signal<AddStickerPackResult, NoError> {
return _internal_addStickerPackInteractively(postbox: self.account.postbox, info: info, items: items, positionInList: positionInList, noDelay: noDelay)
}
public func removeStickerPackInteractively(id: ItemCollectionId, option: RemoveStickerPackOption) -> Signal<(Int, [ItemCollectionItem])?, NoError> {

View file

@ -960,7 +960,7 @@ public class StickerPickerScreen: ViewController {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break
@ -1384,7 +1384,7 @@ public class StickerPickerScreen: ViewController {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break