Swiftgram/submodules/TelegramUI/Sources/ManagedDiceAnimationNode.swift
2020-10-30 17:24:56 +04:00

208 lines
9.1 KiB
Swift

import Foundation
import Display
import AsyncDisplayKit
import Postbox
import SyncCore
import TelegramCore
import SwiftSignalKit
import AccountContext
import StickerResources
import ManagedAnimationNode
enum ManagedDiceAnimationState: Equatable {
case rolling
case value(Int32, Bool)
}
private func animationItem(account: Account, emojis: Signal<[TelegramMediaFile], NoError>, configuration: Signal<InteractiveEmojiConfiguration?, NoError> = .single(nil), emoji: String, value: Int32?, immediate: Bool = false, roll: Bool = false, loop: Bool = false, successCallback: (() -> Void)? = nil) -> Signal<ManagedAnimationItem?, NoError> {
return combineLatest(emojis, configuration)
|> mapToSignal { diceEmojis, configuration -> Signal<ManagedAnimationItem?, NoError> in
if let value = value, value >= diceEmojis.count {
return .complete()
}
let file = diceEmojis[Int(value ?? 0)]
var callbacks: [(Int, () -> Void)] = []
if !loop, let successCallback = successCallback, let value = value, let configuration = configuration, let success = configuration.successParameters[emoji], value == success.value {
callbacks.append((success.frame, successCallback))
}
if let _ = account.postbox.mediaBox.completedResourcePath(file.resource) {
if immediate {
return .single(ManagedAnimationItem(source: .resource(account, file.resource), frames: .still(.end), duration: 0))
} else {
return .single(ManagedAnimationItem(source: .resource(account, file.resource), loop: loop, callbacks: callbacks))
}
} else {
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedSize = dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0))
let fetched = freeMediaFileInteractiveFetched(account: account, fileReference: .standalone(media: file))
let animationItem = Signal<ManagedAnimationItem?, NoError> { subscriber in
let fetchedDisposable = fetched.start()
let resourceDisposable = (chatMessageAnimationData(postbox: account.postbox, resource: file.resource, fitzModifier: nil, width: Int(fittedSize.width), height: Int(fittedSize.height), synchronousLoad: false)
|> filter { data in
return data.complete
}).start(next: { next in
subscriber.putNext(ManagedAnimationItem(source: .resource(account, file.resource), loop: loop, callbacks: callbacks))
subscriber.putCompletion()
})
return ActionDisposable {
fetchedDisposable.dispose()
resourceDisposable.dispose()
}
}
if roll {
return rollingAnimationItem(account: account, emojis: emojis, emoji: emoji) |> then(animationItem)
} else {
return animationItem
}
}
}
}
private func rollingAnimationItem(account: Account, emojis: Signal<[TelegramMediaFile], NoError>, emoji: String) -> Signal<ManagedAnimationItem?, NoError> {
switch emoji {
case "🎲":
return .single(ManagedAnimationItem(source: .local("Dice_Rolling"), loop: true))
case "🎯":
return .single(ManagedAnimationItem(source: .local("Darts_Aiming"), loop: true))
case "🏀":
return .single(ManagedAnimationItem(source: .local("Basketball_Bouncing"), loop: true))
case "":
return .single(ManagedAnimationItem(source: .local("Football_Bouncing"), loop: true))
default:
return animationItem(account: account, emojis: emojis, emoji: emoji, value: nil, loop: true)
}
}
private struct InteractiveEmojiSuccessParameters {
let value: Int
let frame: Int
}
public struct InteractiveEmojiConfiguration {
static var defaultValue: InteractiveEmojiConfiguration {
return InteractiveEmojiConfiguration(emojis: [], successParameters: [:])
}
public let emojis: [String]
fileprivate let successParameters: [String: InteractiveEmojiSuccessParameters]
fileprivate init(emojis: [String], successParameters: [String: InteractiveEmojiSuccessParameters]) {
self.emojis = emojis
self.successParameters = successParameters
}
static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration {
if let data = appConfiguration.data, let emojis = data["emojies_send_dice"] as? [String] {
var successParameters: [String: InteractiveEmojiSuccessParameters] = [:]
if let success = data["emojies_send_dice_success"] as? [String: [String: Double]] {
for (key, dict) in success {
if let successValue = dict["value"], let successFrame = dict["frame_start"] {
successParameters[key] = InteractiveEmojiSuccessParameters(value: Int(successValue), frame: Int(successFrame))
}
}
}
return InteractiveEmojiConfiguration(emojis: emojis, successParameters: successParameters)
} else {
return .defaultValue
}
}
}
final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStickerNode {
private let context: AccountContext
private let emoji: String
private var diceState: ManagedDiceAnimationState? = nil
private let disposable = MetaDisposable()
private let configuration = Promise<InteractiveEmojiConfiguration?>()
private let emojis = Promise<[TelegramMediaFile]>()
var success: (() -> Void)?
init(context: AccountContext, emoji: String) {
self.context = context
self.emoji = emoji
self.configuration.set(self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { preferencesView -> InteractiveEmojiConfiguration? in
let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
return InteractiveEmojiConfiguration.with(appConfiguration: appConfiguration)
})
self.emojis.set(loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .dice(emoji), forceActualized: false)
|> mapToSignal { stickerPack -> Signal<[TelegramMediaFile], NoError> in
switch stickerPack {
case let .result(_, items, _):
var emojiStickers: [TelegramMediaFile] = []
for case let item as StickerPackItem in items {
emojiStickers.append(item.file)
}
return .single(emojiStickers)
default:
return .complete()
}
})
super.init(size: CGSize(width: 184.0, height: 184.0))
}
deinit {
self.disposable.dispose()
}
func setState(_ diceState: ManagedDiceAnimationState) {
let previousState = self.diceState
self.diceState = diceState
var item: Signal<ManagedAnimationItem?, NoError>? = nil
let context = self.context
if let previousState = previousState {
switch previousState {
case .rolling:
switch diceState {
case let .value(value, _):
item = animationItem(account: context.account, emojis: self.emojis.get(), configuration: self.configuration.get(), emoji: self.emoji, value: value, successCallback: { [weak self] in
self?.success?()
})
case .rolling:
break
}
case .value:
switch diceState {
case .rolling:
item = rollingAnimationItem(account: context.account, emojis: self.emojis.get(), emoji: self.emoji)
case .value:
break
}
}
} else {
switch diceState {
case let .value(value, immediate):
item = animationItem(account: context.account, emojis: self.emojis.get(), configuration: self.configuration.get(), emoji: self.emoji, value: value, immediate: immediate, roll: true, successCallback: { [weak self] in
self?.success?()
})
case .rolling:
item = rollingAnimationItem(account: context.account, emojis: self.emojis.get(), emoji: self.emoji)
}
}
if let item = item {
self.disposable.set((item |> deliverOnMainQueue).start(next: { [weak self] item in
if let strongSelf = self, let item = item {
strongSelf.trackTo(item: item)
}
}))
}
}
func setOverlayColor(_ color: UIColor?, animated: Bool) {
}
}