Swiftgram/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift
2019-12-27 01:32:42 +04:00

344 lines
16 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit
import TelegramUIPreferences
import AccountContext
import ShareController
import StickerResources
import AlertUI
import PresentationDataUtils
public enum StickerPackPreviewControllerMode {
case `default`
case settings
}
public final class StickerPackPreviewController: ViewController, StandalonePresentableController {
private var controllerNode: StickerPackPreviewControllerNode {
return self.displayNode as! StickerPackPreviewControllerNode
}
private var animatedIn = false
private var dismissed = false
private let context: AccountContext
private let mode: StickerPackPreviewControllerMode
private weak var parentNavigationController: NavigationController?
private let stickerPack: StickerPackReference
private var stickerPackContentsValue: LoadedStickerPack?
private let stickerPackDisposable = MetaDisposable()
private let stickerPackContents = Promise<LoadedStickerPack>()
private let stickerPackInstalledDisposable = MetaDisposable()
private let stickerPackInstalled = Promise<Bool>()
private let openMentionDisposable = MetaDisposable()
private var presentationDataDisposable: Disposable?
public var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? {
didSet {
if self.isNodeLoaded {
if let sendSticker = self.sendSticker {
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
if sendSticker(file, sourceNode, sourceRect) {
self?.dismiss()
return true
} else {
return false
}
}
} else {
self.controllerNode.sendSticker = nil
}
}
}
}
private let actionPerformed: ((StickerPackCollectionInfo, [ItemCollectionItem], StickerPackScreenPerformedAction) -> Void)?
public init(context: AccountContext, stickerPack: StickerPackReference, mode: StickerPackPreviewControllerMode = .default, parentNavigationController: NavigationController?, actionPerformed: ((StickerPackCollectionInfo, [ItemCollectionItem], StickerPackScreenPerformedAction) -> Void)? = nil) {
self.context = context
self.mode = mode
self.parentNavigationController = parentNavigationController
self.actionPerformed = actionPerformed
self.stickerPack = stickerPack
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
self.stickerPackContents.set(loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: stickerPack, forceActualized: true))
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self, strongSelf.isNodeLoaded {
strongSelf.controllerNode.updatePresentationData(presentationData)
}
})
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.stickerPackDisposable.dispose()
self.stickerPackInstalledDisposable.dispose()
self.openMentionDisposable.dispose()
self.presentationDataDisposable?.dispose()
}
override public func loadDisplayNode() {
var openShareImpl: (() -> Void)?
if self.mode == .settings {
openShareImpl = { [weak self] in
guard let strongSelf = self else {
return
}
if let stickerPackContentsValue = strongSelf.stickerPackContentsValue, case let .result(info, _, _) = stickerPackContentsValue, !info.shortName.isEmpty {
strongSelf.present(ShareController(context: strongSelf.context, subject: .url("https://t.me/addstickers/\(info.shortName)"), externalShare: true), in: .window(.root))
strongSelf.dismiss()
}
}
}
self.displayNode = StickerPackPreviewControllerNode(context: self.context, openShare: openShareImpl, openMention: { [weak self] mention in
guard let strongSelf = self else {
return
}
let account = strongSelf.context.account
strongSelf.openMentionDisposable.set((resolvePeerByName(account: strongSelf.context.account, name: mention)
|> mapToSignal { peerId -> Signal<Peer?, NoError> in
if let peerId = peerId {
return account.postbox.loadedPeerWithId(peerId)
|> map(Optional.init)
} else {
return .single(nil)
}
}
|> deliverOnMainQueue).start(next: { peer in
guard let strongSelf = self else {
return
}
if let peer = peer, let parentNavigationController = strongSelf.parentNavigationController {
strongSelf.dismiss()
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(peer.id), animated: true))
}
}))
}, actionPerformed: self.actionPerformed)
self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.controllerNode.cancel = { [weak self] in
self?.dismiss()
}
self.controllerNode.presentInGlobalOverlay = { [weak self] controller, arguments in
self?.presentInGlobalOverlay(controller, with: arguments)
}
if let sendSticker = self.sendSticker {
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
if sendSticker(file, sourceNode, sourceRect) {
self?.dismiss()
return true
} else {
return false
}
}
}
let account = self.context.account
self.displayNodeDidLoad()
self.stickerPackDisposable.set((combineLatest(self.stickerPackContents.get(), self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> take(1))
|> mapToSignal { next, sharedData -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
stickerSettings = value
}
switch next {
case let .result(info, items, _):
var preloadSignals: [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 data = 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()
data.dispose()
}
}
preloadSignals.append(signal)
}
let topItems = items.prefix(16)
for item in topItems {
if let item = item as? StickerPackItem, 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()
}
}
preloadSignals.append(signal)
}
}
return combineLatest(preloadSignals)
|> map { values -> Bool in
return !values.contains(false)
}
|> distinctUntilChanged
|> mapToSignal { loaded -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
if !loaded {
return .single((.fetching, stickerSettings))
} else {
return .single((next, stickerSettings))
}
}
default:
return .single((next, stickerSettings))
}
}
|> deliverOnMainQueue).start(next: { [weak self] next in
if let strongSelf = self {
if case .none = next.0 {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.dismiss()
} else {
strongSelf.controllerNode.updateStickerPack(next.0, stickerSettings: next.1)
strongSelf.stickerPackContentsValue = next.0
}
}
}))
self.ready.set(self.controllerNode.ready.get())
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn()
}
}
override public func dismiss(completion: (() -> Void)? = nil) {
if !self.dismissed {
self.dismissed = true
} else {
return
}
self.controllerNode.animateOut(completion: completion)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, 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)
}