Improve sticker-related components

This commit is contained in:
Ali 2019-12-27 01:32:42 +04:00
parent b476c87130
commit 2e60947b9e
13 changed files with 2438 additions and 2241 deletions

View File

@ -5238,4 +5238,5 @@ Any member of this group will be able to see messages in the channel.";
"StickerPackActionInfo.AddedTitle" = "Stickers Added"; "StickerPackActionInfo.AddedTitle" = "Stickers Added";
"StickerPackActionInfo.AddedText" = "%@ has been added to your stickers."; "StickerPackActionInfo.AddedText" = "%@ has been added to your stickers.";
"StickerPackActionInfo.RemovedTitle" = "Stickers Removed"; "StickerPackActionInfo.RemovedTitle" = "Stickers Removed";
"StickerPackActionInfo.ArchivedTitle" = "Stickers Archived";
"StickerPackActionInfo.RemovedText" = "%@ is no longer in your stickers."; "StickerPackActionInfo.RemovedText" = "%@ is no longer in your stickers.";

View File

@ -13,6 +13,7 @@ import OverlayStatusController
import AccountContext import AccountContext
import StickerPackPreviewUI import StickerPackPreviewUI
import ItemListStickerPackItem import ItemListStickerPackItem
import UndoUI
public enum ArchivedStickerPacksControllerMode { public enum ArchivedStickerPacksControllerMode {
case stickers case stickers
@ -248,6 +249,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
} }
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var navigationControllerImpl: (() -> NavigationController?)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
@ -300,24 +302,42 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
return return
} }
let _ = (loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false) let _ = (loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|> mapToSignal { result -> Signal<Void, NoError> in |> mapToSignal { result -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem]), NoError> in
switch result { switch result {
case let .result(info, items, installed): case let .result(info, items, installed):
if installed { if installed {
return .complete() return .complete()
} else { } else {
return addStickerPackInteractively(postbox: context.account.postbox, info: info, items: items) return addStickerPackInteractively(postbox: context.account.postbox, info: info, items: items)
} |> ignoreValues
case .fetching: |> mapToSignal { _ -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem]), NoError> in
break return .complete()
}
|> then(.single((info, items)))
}
case .fetching:
break
case .none: case .none:
break break
} }
return .complete() return .complete()
} }
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(next: { info, items in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .success), nil)
var animateInAsReplacement = false
if let navigationController = navigationControllerImpl?() {
for controller in navigationController.overlayControllers {
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitActionAndReplacementAnimation()
animateInAsReplacement = true
}
}
}
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: items.first, account: context.account), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in
return true
}), nil)
let applyPacks: Signal<Void, NoError> = stickerPacks.get() let applyPacks: Signal<Void, NoError> = stickerPacks.get()
|> filter { $0 != nil } |> filter { $0 != nil }
@ -428,7 +448,9 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
controller.present(c, in: .window(.root), with: p) controller.present(c, in: .window(.root), with: p)
} }
} }
navigationControllerImpl = { [weak controller] in
return controller?.navigationController as? NavigationController
}
presentStickerPackController = { [weak controller] info in presentStickerPackController = { [weak controller] info in
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil) presentControllerImpl?(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller?.navigationController as? NavigationController), nil)

View File

@ -530,7 +530,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
} }
} }
navigationControllerImpl?()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(archivedItem.info.title).0, undo: true, info: archivedItem.info, topItem: archivedItem.topItems.first, account: context.account), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in navigationControllerImpl?()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_ArchivedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(archivedItem.info.title).0, undo: true, info: archivedItem.info, topItem: archivedItem.topItems.first, account: context.account), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { action in
if case .undo = action { if case .undo = action {
let _ = addStickerPackInteractively(postbox: context.account.postbox, info: archivedItem.info, items: items, positionInList: positionInList).start() let _ = addStickerPackInteractively(postbox: context.account.postbox, info: archivedItem.info, items: items, positionInList: positionInList).start()
} }
@ -840,11 +840,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
} }
switch action { switch action {
case .add: case .add:
navigationControllerImpl?()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: items.first, account: context.account), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in navigationControllerImpl?()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: items.first, account: context.account), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in
return true return true
})) }))
case let .remove(positionInList): case let .remove(positionInList):
navigationControllerImpl?()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).0, undo: true, info: info, topItem: items.first, account: context.account), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in navigationControllerImpl?()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).0, undo: true, info: info, topItem: items.first, account: context.account), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { action in
if case .undo = action { if case .undo = action {
let _ = addStickerPackInteractively(postbox: context.account.postbox, info: info, items: items, positionInList: positionInList).start() let _ = addStickerPackInteractively(postbox: context.account.postbox, info: info, items: items, positionInList: positionInList).start()
} }

View File

@ -266,3 +266,78 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
} }
} }
public func preloadedStickerPackThumbnail(account: Account, info: StickerPackCollectionInfo, items: [ItemCollectionItem]) -> Signal<Bool, NoError> {
if let thumbnail = info.thumbnail {
let signal = Signal<Bool, NoError> { subscriber in
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start()
let dataDisposable: Disposable
if info.flags.contains(.isAnimated) {
dataDisposable = chatMessageAnimationData(postbox: account.postbox, resource: thumbnail.resource, width: 80, height: 80, synchronousLoad: false).start(next: { data in
if data.complete {
subscriber.putNext(true)
subscriber.putCompletion()
} else {
subscriber.putNext(false)
}
})
} else {
dataDisposable = account.postbox.mediaBox.resourceData(thumbnail.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in
if data.complete {
subscriber.putNext(true)
subscriber.putCompletion()
} else {
subscriber.putNext(false)
}
})
}
return ActionDisposable {
fetched.dispose()
dataDisposable.dispose()
}
}
return signal
}
if let item = items.first as? StickerPackItem {
if item.file.isAnimatedSticker {
let signal = Signal<Bool, NoError> { subscriber in
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: FileMediaReference.standalone(media: item.file).resourceReference(item.file.resource)).start()
let data = account.postbox.mediaBox.resourceData(item.file.resource).start()
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fetchedRepresentation = chatMessageAnimatedStickerDatas(postbox: account.postbox, file: item.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)), fetched: true, onlyFullSize: false, synchronousLoad: false).start(next: { next in
let hasContent = next._0 != nil || next._1 != nil
subscriber.putNext(hasContent)
if hasContent {
subscriber.putCompletion()
}
})
return ActionDisposable {
fetched.dispose()
data.dispose()
fetchedRepresentation.dispose()
}
}
return signal
} else {
let signal = Signal<Bool, NoError> { subscriber in
let data = account.postbox.mediaBox.resourceData(item.file.resource).start()
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fetchedRepresentation = chatMessageAnimatedStickerDatas(postbox: account.postbox, file: item.file, small: true, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)), fetched: true, onlyFullSize: false, synchronousLoad: false).start(next: { next in
let hasContent = next._0 != nil || next._1 != nil
subscriber.putNext(hasContent)
if hasContent {
subscriber.putCompletion()
}
})
return ActionDisposable {
data.dispose()
fetchedRepresentation.dispose()
}
}
return signal
}
}
return .single(true)
}

View File

@ -183,7 +183,7 @@ private let threadPool: ThreadPool = {
}() }()
@available(iOS 9.0, *) @available(iOS 9.0, *)
public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<Data, NoError> { public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
return Signal({ subscriber in return Signal({ subscriber in
let cancelled = Atomic<Bool>(value: false) let cancelled = Atomic<Bool>(value: false)
@ -212,19 +212,36 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
var currentFrame: Int32 = 0 var currentFrame: Int32 = 0
let writeBuffer = WriteBuffer() //let writeBuffer = WriteBuffer()
let tempFile = TempBox.shared.tempFile(fileName: "result.asticker")
guard let file = ManagedFile(queue: nil, path: tempFile.path, mode: .readwrite) else {
return
}
func writeData(_ data: UnsafeRawPointer, length: Int) {
file.write(data, count: length)
}
func commitData() {
}
func completeWithCurrentResult() {
subscriber.putNext(.tempFile(tempFile))
subscriber.putCompletion()
}
var numberOfFramesCommitted = 0 var numberOfFramesCommitted = 0
var fps: Int32 = player.frameRate var fps: Int32 = player.frameRate
var frameCount: Int32 = player.frameCount var frameCount: Int32 = player.frameCount
writeBuffer.write(&fps, length: 4) writeData(&fps, length: 4)
writeBuffer.write(&frameCount, length: 4) writeData(&frameCount, length: 4)
var widthValue: Int32 = Int32(size.width) var widthValue: Int32 = Int32(size.width)
var heightValue: Int32 = Int32(size.height) var heightValue: Int32 = Int32(size.height)
var bytesPerRowValue: Int32 = Int32(bytesPerRow) var bytesPerRowValue: Int32 = Int32(bytesPerRow)
writeBuffer.write(&widthValue, length: 4) writeData(&widthValue, length: 4)
writeBuffer.write(&heightValue, length: 4) writeData(&heightValue, length: 4)
writeBuffer.write(&bytesPerRowValue, length: 4) writeData(&bytesPerRowValue, length: 4)
let frameLength = bytesPerRow * Int(size.height) let frameLength = bytesPerRow * Int(size.height)
assert(frameLength % 16 == 0) assert(frameLength % 16 == 0)
@ -293,8 +310,8 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE) let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
var frameLengthValue: Int32 = Int32(length) var frameLengthValue: Int32 = Int32(length)
writeBuffer.write(&frameLengthValue, length: 4) writeData(&frameLengthValue, length: 4)
writeBuffer.write(bytes, length: length) writeData(bytes, length: length)
} }
let tmp = previousYuvaFrameData let tmp = previousYuvaFrameData
@ -310,20 +327,14 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C
if numberOfFramesCommitted >= 5 { if numberOfFramesCommitted >= 5 {
numberOfFramesCommitted = 0 numberOfFramesCommitted = 0
subscriber.putNext(writeBuffer.makeData()) commitData()
writeBuffer.reset()
/*#if DEBUG
usleep(500000)
#endif*/
} }
} }
if writeBuffer.length != 0 { commitData()
subscriber.putNext(writeBuffer.makeData())
}
completeWithCurrentResult()
subscriber.putCompletion() subscriber.putCompletion()
/*print("animation render time \(CACurrentMediaTime() - startTime)") /*print("animation render time \(CACurrentMediaTime() - startTime)")
print("of which drawing time \(drawingTime)") print("of which drawing time \(drawingTime)")

View File

@ -222,7 +222,7 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
} }
public struct FoundStickerSets { public struct FoundStickerSets {
public let infos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)] public var infos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)]
public let entries: [ItemCollectionViewEntry] public let entries: [ItemCollectionViewEntry]
public init(infos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)] = [], entries: [ItemCollectionViewEntry] = []) { public init(infos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)] = [], entries: [ItemCollectionViewEntry] = []) {
self.infos = infos self.infos = infos

View File

@ -798,7 +798,7 @@ final class ChatMediaInputNode: ChatInputNode {
var index = 0 var index = 0
for item in trendingPacks { for item in trendingPacks {
if !installedPacks.contains(item.info.id) { if !installedPacks.contains(item.info.id) {
gridEntries.append(.trending(TrendingPaneEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread))) gridEntries.append(.trending(TrendingPaneEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: true)))
index += 1 index += 1
} }
} }
@ -1159,14 +1159,14 @@ final class ChatMediaInputNode: ChatInputNode {
if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex { if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex {
let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
let previousTrendingPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending //let previousTrendingPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index) self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index)
let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs
let updatedTrendingPanelIsActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending //let updatedTrendingPanelIsActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending
if updatedTrendingPanelIsActive != previousTrendingPanelWasActive { /*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive {
transition = .immediate transition = .immediate
} }*/
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
@ -1187,7 +1187,7 @@ final class ChatMediaInputNode: ChatInputNode {
case .trending: case .trending:
self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)) self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0))
} }
if updatedTrendingPanelIsActive != previousTrendingPanelWasActive { /*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive {
self.controllerInteraction.updateInputMode { current in self.controllerInteraction.updateInputMode { current in
switch current { switch current {
case let .media(mode, _): case let .media(mode, _):
@ -1200,7 +1200,7 @@ final class ChatMediaInputNode: ChatInputNode {
return current return current
} }
} }
} }*/
} else { } else {
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)

View File

@ -12,6 +12,7 @@ import OverlayStatusController
import AccountContext import AccountContext
import StickerPackPreviewUI import StickerPackPreviewUI
import PresentationDataUtils import PresentationDataUtils
import UndoUI
final class TrendingPaneInteraction { final class TrendingPaneInteraction {
let installPack: (ItemCollectionInfo) -> Void let installPack: (ItemCollectionInfo) -> Void
@ -33,8 +34,9 @@ final class TrendingPaneEntry: Identifiable, Comparable {
let topItems: [StickerPackItem] let topItems: [StickerPackItem]
let installed: Bool let installed: Bool
let unread: Bool let unread: Bool
let topSeparator: Bool
init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool) { init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool) {
self.index = index self.index = index
self.info = info self.info = info
self.theme = theme self.theme = theme
@ -42,6 +44,7 @@ final class TrendingPaneEntry: Identifiable, Comparable {
self.topItems = topItems self.topItems = topItems
self.installed = installed self.installed = installed
self.unread = unread self.unread = unread
self.topSeparator = topSeparator
} }
var stableId: ItemCollectionId { var stableId: ItemCollectionId {
@ -70,6 +73,9 @@ final class TrendingPaneEntry: Identifiable, Comparable {
if lhs.unread != rhs.unread { if lhs.unread != rhs.unread {
return false return false
} }
if lhs.topSeparator != rhs.topSeparator {
return false
}
return true return true
} }
@ -79,7 +85,7 @@ final class TrendingPaneEntry: Identifiable, Comparable {
func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem { func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem {
let info = self.info let info = self.info
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, info: self.info, topItems: self.topItems, grid: grid, installed: self.installed, unread: self.unread, open: { return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: {
interaction.openPack(info) interaction.openPack(info)
}, install: { }, install: {
interaction.installPack(info) interaction.installPack(info)
@ -100,8 +106,8 @@ private func preparedTransition(from fromEntries: [TrendingPaneEntry], to toEntr
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices let deletions = deleteIndices
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction, grid: true), previousIndex: $0.2) } let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction, grid: false), previousIndex: $0.2) }
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction, grid: true)) } let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction, grid: false)) }
return TrendingPaneTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial) return TrendingPaneTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial)
} }
@ -111,7 +117,7 @@ func trendingPaneEntries(trendingEntries: [FeaturedStickerPackItem], installedPa
var index = 0 var index = 0
for item in trendingEntries { for item in trendingEntries {
if !installedPacks.contains(item.info.id) { if !installedPacks.contains(item.info.id) {
result.append(TrendingPaneEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread)) result.append(TrendingPaneEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: index != 0))
index += 1 index += 1
} }
} }
@ -167,26 +173,52 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane {
let interaction = TrendingPaneInteraction(installPack: { [weak self] info in let interaction = TrendingPaneInteraction(installPack: { [weak self] info in
if let strongSelf = self, let info = info as? StickerPackCollectionInfo { if let strongSelf = self, let info = info as? StickerPackCollectionInfo {
let account = strongSelf.context.account
let _ = (loadedStickerPack(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false) let _ = (loadedStickerPack(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false)
|> mapToSignal { result -> Signal<Void, NoError> in |> mapToSignal { result -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem]), NoError> in
switch result { switch result {
case let .result(info, items, installed): case let .result(info, items, installed):
if installed { if installed {
return .complete()
} else {
return preloadedStickerPackThumbnail(account: account, info: info, items: items)
|> filter { $0 }
|> ignoreValues
|> then(
addStickerPackInteractively(postbox: strongSelf.context.account.postbox, info: info, items: items)
|> ignoreValues
)
|> mapToSignal { _ -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem]), NoError> in
return .complete() return .complete()
} else {
return addStickerPackInteractively(postbox: strongSelf.context.account.postbox, info: info, items: items)
} }
case .fetching: |> then(.single((info, items)))
break }
case .none: case .fetching:
break break
case .none:
break
} }
return .complete() return .complete()
} |> deliverOnMainQueue).start(completed: { }
if let strongSelf = self { |> deliverOnMainQueue).start(next: { info, items in
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } guard let strongSelf = self else {
strongSelf.controllerInteraction.presentController(OverlayStatusController(theme: presentationData.theme, type: .success), nil) return
} }
var animateInAsReplacement = false
if let navigationController = strongSelf.controllerInteraction.navigationController() {
for controller in navigationController.overlayControllers {
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitActionAndReplacementAnimation()
animateInAsReplacement = true
}
}
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.controllerInteraction.navigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: items.first, account: strongSelf.context.account), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in
return true
}))
}) })
} }
}, openPack: { [weak self] info in }, openPack: { [weak self] info in

View File

@ -855,11 +855,9 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi
return Signal({ subscriber in return Signal({ subscriber in
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
if #available(iOS 9.0, *) { if #available(iOS 9.0, *) {
subscriber.putNext(.reset) return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { value in
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { data in subscriber.putNext(value)
subscriber.putNext(.data(data))
}, completed: { }, completed: {
subscriber.putNext(.done)
subscriber.putCompletion() subscriber.putCompletion()
}) })
} else { } else {

View File

@ -35,13 +35,13 @@ private enum StickerSearchEntryId: Equatable, Hashable {
private enum StickerSearchEntry: Identifiable, Comparable { private enum StickerSearchEntry: Identifiable, Comparable {
case sticker(index: Int, code: String?, stickerItem: FoundStickerItem, theme: PresentationTheme) case sticker(index: Int, code: String?, stickerItem: FoundStickerItem, theme: PresentationTheme)
case global(index: Int, info: StickerPackCollectionInfo, topItems: [StickerPackItem], installed: Bool) case global(index: Int, info: StickerPackCollectionInfo, topItems: [StickerPackItem], installed: Bool, topSeparator: Bool)
var stableId: StickerSearchEntryId { var stableId: StickerSearchEntryId {
switch self { switch self {
case let .sticker(_, code, stickerItem, _): case let .sticker(_, code, stickerItem, _):
return .sticker(code, stickerItem.file.fileId.id) return .sticker(code, stickerItem.file.fileId.id)
case let .global(_, info, _, _): case let .global(_, info, _, _, _):
return .global(info.id) return .global(info.id)
} }
} }
@ -66,8 +66,8 @@ private enum StickerSearchEntry: Identifiable, Comparable {
} else { } else {
return false return false
} }
case let .global(index, info, topItems, installed): case let .global(index, info, topItems, installed, topSeparator):
if case .global(index, info, topItems, installed) = rhs { if case .global(index, info, topItems, installed, topSeparator) = rhs {
return true return true
} else { } else {
return false return false
@ -84,11 +84,11 @@ private enum StickerSearchEntry: Identifiable, Comparable {
default: default:
return true return true
} }
case let .global(lhsIndex, _, _, _): case let .global(lhsIndex, _, _, _, _):
switch rhs { switch rhs {
case .sticker: case .sticker:
return false return false
case let .global(rhsIndex, _, _, _): case let .global(rhsIndex, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
} }
} }
@ -100,8 +100,8 @@ private enum StickerSearchEntry: Identifiable, Comparable {
return StickerPaneSearchStickerItem(account: account, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { node, rect in return StickerPaneSearchStickerItem(account: account, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { node, rect in
interaction.sendSticker(.standalone(media: stickerItem.file), node, rect) interaction.sendSticker(.standalone(media: stickerItem.file), node, rect)
}) })
case let .global(_, info, topItems, installed): case let .global(_, info, topItems, installed, topSeparator):
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, grid: false, installed: installed, unread: false, open: { return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: {
interaction.open(info) interaction.open(info)
}, install: { }, install: {
interaction.install(info) interaction.install(info)
@ -322,17 +322,57 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let local = searchStickerSets(postbox: context.account.postbox, query: text) let local = searchStickerSets(postbox: context.account.postbox, query: text)
let remote = searchStickerSetsRemotely(network: context.account.network, query: text) let remote = searchStickerSetsRemotely(network: context.account.network, query: text)
|> delay(0.2, queue: Queue.mainQueue()) |> delay(0.2, queue: Queue.mainQueue())
let packs = local let rawPacks = local
|> mapToSignal { result -> Signal<(FoundStickerSets, Bool, FoundStickerSets?), NoError> in |> mapToSignal { result -> Signal<(FoundStickerSets, Bool, FoundStickerSets?), NoError> in
var localResult = result var localResult = result
if let currentRemote = self.currentRemotePacks.with ({ $0 }) { if let currentRemote = self.currentRemotePacks.with ({ $0 }) {
localResult = localResult.merge(with: currentRemote) localResult = localResult.merge(with: currentRemote)
} }
return .single((localResult, false, nil)) return .single((localResult, false, nil))
|> then(remote |> map { remote -> (FoundStickerSets, Bool, FoundStickerSets?) in |> then(
return (result.merge(with: remote), true, remote) remote
}) |> map { remote -> (FoundStickerSets, Bool, FoundStickerSets?) in
return (result.merge(with: remote), true, remote)
}
)
} }
let installedPackIds = context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])
|> map { view -> Set<ItemCollectionId> in
var installedPacks = Set<ItemCollectionId>()
if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView {
if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] {
for entry in packsEntries {
installedPacks.insert(entry.id)
}
}
}
return installedPacks
}
|> distinctUntilChanged
let packs = combineLatest(rawPacks, installedPackIds)
|> map { packs, installedPackIds -> (FoundStickerSets, Bool, FoundStickerSets?) in
var (localPacks, completed, remotePacks) = packs
for i in 0 ..< localPacks.infos.count {
let installed = installedPackIds.contains(localPacks.infos[i].0)
if installed != localPacks.infos[i].3 {
localPacks.infos[i].3 = installed
}
}
if remotePacks != nil {
for i in 0 ..< remotePacks!.infos.count {
let installed = installedPackIds.contains(remotePacks!.infos[i].0)
if installed != remotePacks!.infos[i].3 {
remotePacks!.infos[i].3 = installed
}
}
}
return (localPacks, completed, remotePacks)
}
signal = combineLatest(stickers, packs) signal = combineLatest(stickers, packs)
|> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in
return (stickers, packs.0, packs.1, packs.2) return (stickers, packs.0, packs.1, packs.2)
@ -374,6 +414,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
existingStickerIds.insert(id) existingStickerIds.insert(id)
} }
} }
var isFirstGlobal = true
for (collectionId, info, _, installed) in packs.infos { for (collectionId, info, _, installed) in packs.infos {
if let info = info as? StickerPackCollectionInfo { if let info = info as? StickerPackCollectionInfo {
var topItems: [StickerPackItem] = [] var topItems: [StickerPackItem] = []
@ -384,7 +425,8 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
} }
} }
} }
entries.append(.global(index: index, info: info, topItems: topItems, installed: installed)) entries.append(.global(index: index, info: info, topItems: topItems, installed: installed, topSeparator: !isFirstGlobal))
isFirstGlobal = false
index += 1 index += 1
} }
} }

View File

@ -39,6 +39,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
let info: StickerPackCollectionInfo let info: StickerPackCollectionInfo
let topItems: [StickerPackItem] let topItems: [StickerPackItem]
let grid: Bool let grid: Bool
let topSeparator: Bool
let installed: Bool let installed: Bool
let unread: Bool let unread: Bool
let open: () -> Void let open: () -> Void
@ -47,16 +48,17 @@ final class StickerPaneSearchGlobalItem: GridItem {
let section: GridSection? = StickerPaneSearchGlobalSection() let section: GridSection? = StickerPaneSearchGlobalSection()
var fillsRowWithHeight: CGFloat? { var fillsRowWithHeight: CGFloat? {
return self.grid ? nil : 128.0 return self.grid ? nil : (128.0 + (self.topSeparator ? 12.0 : 0.0))
} }
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, installed: Bool, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { init(account: Account, theme: PresentationTheme, strings: PresentationStrings, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, topSeparator: Bool, installed: Bool, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) {
self.account = account self.account = account
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.info = info self.info = info
self.topItems = topItems self.topItems = topItems
self.grid = grid self.grid = grid
self.topSeparator = topSeparator
self.installed = installed self.installed = installed
self.unread = unread self.unread = unread
self.open = open self.open = open
@ -91,6 +93,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
private let installBackgroundNode: ASImageNode private let installBackgroundNode: ASImageNode
private let installButtonNode: HighlightTrackingButtonNode private let installButtonNode: HighlightTrackingButtonNode
private var itemNodes: [TrendingTopItemNode] private var itemNodes: [TrendingTopItemNode]
private let topSeparatorNode: ASDisplayNode
private var item: StickerPaneSearchGlobalItem? private var item: StickerPaneSearchGlobalItem?
private var appliedItem: StickerPaneSearchGlobalItem? private var appliedItem: StickerPaneSearchGlobalItem?
@ -134,6 +137,9 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
self.installButtonNode = HighlightTrackingButtonNode() self.installButtonNode = HighlightTrackingButtonNode()
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.isLayerBacked = true
self.itemNodes = [] self.itemNodes = []
super.init() super.init()
@ -144,6 +150,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
self.addSubnode(self.installBackgroundNode) self.addSubnode(self.installBackgroundNode)
self.addSubnode(self.installTextNode) self.addSubnode(self.installTextNode)
self.addSubnode(self.installButtonNode) self.addSubnode(self.installButtonNode)
self.addSubnode(self.topSeparatorNode)
self.installButtonNode.highligthedChanged = { [weak self] highlighted in self.installButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self { if let strongSelf = self {
@ -193,6 +200,15 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
let params = ListViewItemLayoutParams(width: self.bounds.size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: self.bounds.height) let params = ListViewItemLayoutParams(width: self.bounds.size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: self.bounds.height)
var topOffset: CGFloat = 12.0
if item.topSeparator {
topOffset += 12.0
}
self.topSeparatorNode.isHidden = !item.topSeparator
self.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: CGSize(width: params.width - 16.0 * 2.0, height: UIScreenPixel))
self.topSeparatorNode.backgroundColor = item.theme.chat.inputMediaPanel.stickersSectionTextColor.withAlphaComponent(0.3)
let makeInstallLayout = TextNode.asyncLayout(self.installTextNode) let makeInstallLayout = TextNode.asyncLayout(self.installTextNode)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode) let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
@ -208,7 +224,6 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
let leftInset: CGFloat = 14.0 let leftInset: CGFloat = 14.0
let rightInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0
let topOffset: CGFloat = 12.0
let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_Install, font: buttonFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_Install, font: buttonFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))