mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Animated emoji improvements
This commit is contained in:
parent
2a5b45883d
commit
c141531c7b
@ -7849,3 +7849,5 @@ Sorry for the inconvenience.";
|
||||
|
||||
"WebApp.CloseConfirmation" = "Changes that you made may not be saved.";
|
||||
"WebApp.CloseAnyway" = "Close Anyway";
|
||||
|
||||
"Emoji.ClearRecent" = "Clear Recent Emoji";
|
||||
|
||||
@ -165,6 +165,7 @@ public protocol AnimatedStickerNode: ASDisplayNode {
|
||||
var autoplay: Bool { get set }
|
||||
|
||||
var visibility: Bool { get set }
|
||||
var overrideVisibility: Bool { get set }
|
||||
|
||||
var isPlayingChanged: (Bool) -> Void { get }
|
||||
|
||||
@ -222,6 +223,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
|
||||
}
|
||||
|
||||
public var autoplay = false
|
||||
public var overrideVisibility: Bool = false
|
||||
|
||||
public var visibility = false {
|
||||
didSet {
|
||||
@ -386,7 +388,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
|
||||
|
||||
private func updateIsPlaying() {
|
||||
if !self.autoplay {
|
||||
let isPlaying = self.visibility && self.isDisplaying
|
||||
let isPlaying = self.visibility && (self.isDisplaying || self.overrideVisibility)
|
||||
if self.isPlaying != isPlaying {
|
||||
self.isPlaying = isPlaying
|
||||
if isPlaying {
|
||||
|
||||
@ -56,6 +56,8 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode
|
||||
}
|
||||
}
|
||||
|
||||
public var overrideVisibility: Bool = false
|
||||
|
||||
public var isPlayingChanged: (Bool) -> Void = { _ in }
|
||||
|
||||
private var sourceDisposable: Disposable?
|
||||
|
||||
@ -898,9 +898,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
let animationComponent = LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: "anim_smiletosticker",
|
||||
colors: colors,
|
||||
mode: .animateTransitionFromPrevious
|
||||
),
|
||||
colors: colors,
|
||||
size: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
let inputNodeSize = self.inputModeView.update(
|
||||
|
||||
@ -661,6 +661,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return
|
||||
}
|
||||
let animation: LottieAnimationComponent.AnimationItem?
|
||||
let colors: [String: UIColor]
|
||||
let progressValue: Double?
|
||||
switch state {
|
||||
case let .downloading(progress):
|
||||
@ -668,13 +669,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
animation = LottieAnimationComponent.AnimationItem(
|
||||
name: "anim_search_downloading",
|
||||
colors: [
|
||||
"Oval.Ellipse 1.Stroke 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow1.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow2.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
],
|
||||
mode: .animating(loop: true)
|
||||
)
|
||||
colors = [
|
||||
"Oval.Ellipse 1.Stroke 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow1.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow2.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
]
|
||||
progressValue = progress
|
||||
|
||||
strongSelf.clearUnseenDownloadsTimer?.invalidate()
|
||||
@ -684,18 +685,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
animation = LottieAnimationComponent.AnimationItem(
|
||||
name: "anim_search_downloaded",
|
||||
colors: [
|
||||
"Fill 2.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Mask1.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Mask2.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow3.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Fill.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Oval.Ellipse 1.Stroke 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow1.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow2.Union.Fill 1": strongSelf.presentationData.theme.rootController.navigationSearchBar.inputFillColor.blitOver(strongSelf.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor, alpha: 1.0),
|
||||
],
|
||||
mode: .animating(loop: false)
|
||||
)
|
||||
colors = [
|
||||
"Fill 2.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Mask1.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Mask2.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow3.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Fill.Ellipse 1.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Oval.Ellipse 1.Stroke 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow1.Union.Fill 1": strongSelf.presentationData.theme.list.itemAccentColor,
|
||||
"Arrow2.Union.Fill 1": strongSelf.presentationData.theme.rootController.navigationSearchBar.inputFillColor.blitOver(strongSelf.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor, alpha: 1.0),
|
||||
]
|
||||
progressValue = 1.0
|
||||
|
||||
if strongSelf.clearUnseenDownloadsTimer == nil {
|
||||
@ -718,6 +719,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
strongSelf.hasDownloads = hasDownloadsValue
|
||||
|
||||
animation = nil
|
||||
colors = [:]
|
||||
progressValue = nil
|
||||
|
||||
strongSelf.clearUnseenDownloadsTimer?.invalidate()
|
||||
@ -728,6 +730,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
let contentComponent = AnyComponent(ZStack<Empty>([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieAnimationComponent(
|
||||
animation: animation,
|
||||
colors: colors,
|
||||
size: CGSize(width: 24.0, height: 24.0)
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(ProgressIndicatorComponent(
|
||||
|
||||
@ -458,6 +458,25 @@ public struct Transition {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func animateSublayerScale(view: UIView, from fromValue: CGFloat, to toValue: CGFloat, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
view.layer.animate(
|
||||
from: fromValue as NSNumber,
|
||||
to: toValue as NSNumber,
|
||||
keyPath: "sublayerTransform.scale",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
curve: curve,
|
||||
removeOnCompletion: true,
|
||||
additive: additive,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func animateAlpha(view: UIView, from fromValue: CGFloat, to toValue: CGFloat, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
|
||||
@ -20,21 +20,21 @@ public final class LottieAnimationComponent: Component {
|
||||
|
||||
public var name: String
|
||||
public var mode: Mode
|
||||
public var colors: [String: UIColor]
|
||||
|
||||
public init(name: String, colors: [String: UIColor], mode: Mode) {
|
||||
public init(name: String, mode: Mode) {
|
||||
self.name = name
|
||||
self.colors = colors
|
||||
self.mode = mode
|
||||
}
|
||||
}
|
||||
|
||||
public let animation: AnimationItem
|
||||
public let colors: [String: UIColor]
|
||||
public let tag: AnyObject?
|
||||
public let size: CGSize?
|
||||
|
||||
public init(animation: AnimationItem, tag: AnyObject? = nil, size: CGSize?) {
|
||||
public init(animation: AnimationItem, colors: [String: UIColor], tag: AnyObject? = nil, size: CGSize?) {
|
||||
self.animation = animation
|
||||
self.colors = colors
|
||||
self.tag = tag
|
||||
self.size = size
|
||||
}
|
||||
@ -42,6 +42,7 @@ public final class LottieAnimationComponent: Component {
|
||||
public func tagged(_ tag: AnyObject?) -> LottieAnimationComponent {
|
||||
return LottieAnimationComponent(
|
||||
animation: self.animation,
|
||||
colors: self.colors,
|
||||
tag: tag,
|
||||
size: self.size
|
||||
)
|
||||
@ -51,6 +52,9 @@ public final class LottieAnimationComponent: Component {
|
||||
if lhs.animation != rhs.animation {
|
||||
return false
|
||||
}
|
||||
if lhs.colors != rhs.colors {
|
||||
return false
|
||||
}
|
||||
if lhs.tag !== rhs.tag {
|
||||
return false
|
||||
}
|
||||
@ -117,6 +121,11 @@ public final class LottieAnimationComponent: Component {
|
||||
|
||||
func update(component: LottieAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
var updatePlayback = false
|
||||
var updateColors = false
|
||||
|
||||
if let currentComponent = self.component, currentComponent.colors != component.colors {
|
||||
updateColors = true
|
||||
}
|
||||
|
||||
if self.component?.animation != component.animation {
|
||||
if let animationView = self.animationView {
|
||||
@ -158,15 +167,7 @@ public final class LottieAnimationComponent: Component {
|
||||
view.backgroundColor = .clear
|
||||
view.isOpaque = false
|
||||
|
||||
if let value = component.animation.colors["__allcolors__"] {
|
||||
for keypath in view.allKeypaths(predicate: { $0.keys.last == "Color" }) {
|
||||
view.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: keypath))
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value) in component.animation.colors {
|
||||
view.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
|
||||
}
|
||||
updateColors = true
|
||||
|
||||
self.animationView = view
|
||||
self.addSubview(view)
|
||||
@ -176,6 +177,23 @@ public final class LottieAnimationComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
if updateColors, let animationView = self.animationView {
|
||||
if let value = component.colors["__allcolors__"] {
|
||||
for keypath in animationView.allKeypaths(predicate: { $0.keys.last == "Color" }) {
|
||||
animationView.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: keypath))
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value) in component.colors {
|
||||
if key == "__allcolors__" {
|
||||
continue
|
||||
}
|
||||
animationView.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: "\(key).Color"))
|
||||
}
|
||||
}
|
||||
|
||||
var animationSize = CGSize()
|
||||
if let animationView = self.animationView, let animation = animationView.animation {
|
||||
animationSize = animation.size
|
||||
@ -187,7 +205,36 @@ public final class LottieAnimationComponent: Component {
|
||||
let size = CGSize(width: min(animationSize.width, availableSize.width), height: min(animationSize.height, availableSize.height))
|
||||
|
||||
if let animationView = self.animationView {
|
||||
animationView.frame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.height - animationSize.height) / 2.0)), size: animationSize)
|
||||
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.height - animationSize.height) / 2.0)), size: animationSize)
|
||||
|
||||
if animationView.frame != animationFrame {
|
||||
if !transition.animation.isImmediate && !animationView.frame.isEmpty && animationView.frame.size != animationFrame.size {
|
||||
let previouosAnimationFrame = animationView.frame
|
||||
|
||||
if let snapshotView = animationView.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = previouosAnimationFrame
|
||||
|
||||
animationView.superview?.insertSubview(snapshotView, belowSubview: animationView)
|
||||
|
||||
transition.setPosition(view: snapshotView, position: CGPoint(x: animationFrame.midX, y: animationFrame.midY))
|
||||
snapshotView.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||
let scaleFactor = previouosAnimationFrame.width / animationFrame.width
|
||||
transition.animateScale(view: snapshotView, from: scaleFactor, to: 1.0)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
transition.setPosition(view: animationView, position: CGPoint(x: animationFrame.midX, y: animationFrame.midY))
|
||||
transition.setBounds(view: animationView, bounds: CGRect(origin: CGPoint(), size: animationFrame.size))
|
||||
transition.animateSublayerScale(view: animationView, from: previouosAnimationFrame.width / animationFrame.width, to: 1.0)
|
||||
animationView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
} else if animationView.frame.size == animationFrame.size {
|
||||
transition.setFrame(view: animationView, frame: animationFrame)
|
||||
} else {
|
||||
animationView.frame = animationFrame
|
||||
}
|
||||
}
|
||||
|
||||
if updatePlayback {
|
||||
if case .animating = component.animation.mode {
|
||||
|
||||
@ -261,6 +261,10 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
|
||||
private var isTopPanelExpanded: Bool = false
|
||||
|
||||
public var topPanelComponentView: UIView? {
|
||||
return self.topPanelView?.componentView
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -774,6 +778,14 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
|
||||
self.component?.isTopPanelExpandedUpdated(self.isTopPanelExpanded, transition)
|
||||
}
|
||||
|
||||
public func collapseTopPanel() {
|
||||
if !self.isTopPanelExpanded {
|
||||
return
|
||||
}
|
||||
|
||||
self.isTopPanelExpandedUpdated(isExpanded: false, transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
|
||||
@ -68,6 +68,11 @@ public final class PeekController: ViewController, ContextControllerProtocol {
|
||||
|
||||
private var animatedIn = false
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(presentationData: PresentationData, content: PeekControllerContent, sourceView: @escaping () -> (UIView, CGRect)?) {
|
||||
self.presentationData = presentationData
|
||||
self.content = content
|
||||
|
||||
@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
|
||||
public enum PeekControllerContentPresentation {
|
||||
case contained
|
||||
@ -26,6 +27,7 @@ public protocol PeekControllerContent {
|
||||
}
|
||||
|
||||
public protocol PeekControllerContentNode {
|
||||
func ready() -> Signal<Bool, NoError>
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
|
||||
}
|
||||
|
||||
|
||||
@ -126,6 +126,8 @@ final class PeekControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
self.hapticFeedback.prepareTap()
|
||||
|
||||
controller.ready.set(self.contentNode.ready())
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
@ -64,6 +64,8 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
|
||||
init(account: Account, item: ImportStickerPack.Sticker) {
|
||||
self.account = account
|
||||
self.item = item
|
||||
@ -104,6 +106,21 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController
|
||||
}
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.started = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf._ready.set(.single(true))
|
||||
}
|
||||
} else {
|
||||
self._ready.set(.single(true))
|
||||
}
|
||||
}
|
||||
|
||||
func ready() -> Signal<Bool, NoError> {
|
||||
return self._ready.get()
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
|
||||
@ -101,6 +101,8 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
|
||||
init(account: Account, item: StickerPreviewPeekItem) {
|
||||
self.account = account
|
||||
self.item = item
|
||||
@ -117,6 +119,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
||||
|
||||
if item.file.isAnimatedSticker || item.file.isVideoSticker {
|
||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
animationNode.overrideVisibility = true
|
||||
self.animationNode = animationNode
|
||||
|
||||
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
@ -166,12 +169,32 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
||||
if let additionalAnimationNode = self.additionalAnimationNode {
|
||||
self.addSubnode(additionalAnimationNode)
|
||||
}
|
||||
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.started = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf._ready.set(.single(true))
|
||||
}
|
||||
} else {
|
||||
self.imageNode.imageUpdated = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf._ready.set(.single(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.effectDisposable.dispose()
|
||||
}
|
||||
|
||||
public func ready() -> Signal<Bool, NoError> {
|
||||
return self._ready.get()
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let boundingSize: CGSize
|
||||
if let _ = self.additionalAnimationNode {
|
||||
|
||||
@ -544,6 +544,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[982592842] = { return Api.PasswordKdfAlgo.parse_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow($0) }
|
||||
dict[-732254058] = { return Api.PasswordKdfAlgo.parse_passwordKdfAlgoUnknown($0) }
|
||||
dict[-368917890] = { return Api.PaymentCharge.parse_paymentCharge($0) }
|
||||
dict[-1996951013] = { return Api.PaymentFormMethod.parse_paymentFormMethod($0) }
|
||||
dict[-1868808300] = { return Api.PaymentRequestedInfo.parse_paymentRequestedInfo($0) }
|
||||
dict[-842892769] = { return Api.PaymentSavedCredentials.parse_paymentSavedCredentialsCard($0) }
|
||||
dict[-1566230754] = { return Api.Peer.parse_peerChannel($0) }
|
||||
@ -710,6 +711,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[313694676] = { return Api.StickerPack.parse_stickerPack($0) }
|
||||
dict[768691932] = { return Api.StickerSet.parse_stickerSet($0) }
|
||||
dict[1678812626] = { return Api.StickerSetCovered.parse_stickerSetCovered($0) }
|
||||
dict[451763941] = { return Api.StickerSetCovered.parse_stickerSetFullCovered($0) }
|
||||
dict[872932635] = { return Api.StickerSetCovered.parse_stickerSetMultiCovered($0) }
|
||||
dict[-1609668650] = { return Api.Theme.parse_theme($0) }
|
||||
dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) }
|
||||
@ -805,6 +807,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1767677564] = { return Api.Update.parse_updateReadChannelDiscussionOutbox($0) }
|
||||
dict[-1842450928] = { return Api.Update.parse_updateReadChannelInbox($0) }
|
||||
dict[-1218471511] = { return Api.Update.parse_updateReadChannelOutbox($0) }
|
||||
dict[-78886548] = { return Api.Update.parse_updateReadFeaturedEmojiStickers($0) }
|
||||
dict[1461528386] = { return Api.Update.parse_updateReadFeaturedStickers($0) }
|
||||
dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) }
|
||||
dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) }
|
||||
@ -1003,7 +1006,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
|
||||
dict[-1362048039] = { return Api.payments.ExportedInvoice.parse_exportedInvoice($0) }
|
||||
dict[-1340916937] = { return Api.payments.PaymentForm.parse_paymentForm($0) }
|
||||
dict[1288001087] = { return Api.payments.PaymentForm.parse_paymentForm($0) }
|
||||
dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) }
|
||||
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
|
||||
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) }
|
||||
@ -1418,6 +1421,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentCharge:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentFormMethod:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentRequestedInfo:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentSavedCredentials:
|
||||
|
||||
@ -326,6 +326,46 @@ public extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum PaymentFormMethod: TypeConstructorDescription {
|
||||
case paymentFormMethod(url: String, title: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .paymentFormMethod(let url, let title):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1996951013)
|
||||
}
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .paymentFormMethod(let url, let title):
|
||||
return ("paymentFormMethod", [("url", String(describing: url)), ("title", String(describing: title))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_paymentFormMethod(_ reader: BufferReader) -> PaymentFormMethod? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.PaymentFormMethod.paymentFormMethod(url: _1!, title: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum PaymentRequestedInfo: TypeConstructorDescription {
|
||||
case paymentRequestedInfo(flags: Int32, name: String?, phone: String?, email: String?, shippingAddress: Api.PostAddress?)
|
||||
@ -1228,99 +1268,3 @@ public extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum Photo: TypeConstructorDescription {
|
||||
case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], videoSizes: [Api.VideoSize]?, dcId: Int32)
|
||||
case photoEmpty(id: Int64)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId):
|
||||
if boxed {
|
||||
buffer.appendInt32(-82216347)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeBytes(fileReference, buffer: buffer, boxed: false)
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sizes.count))
|
||||
for item in sizes {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(videoSizes!.count))
|
||||
for item in videoSizes! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
serializeInt32(dcId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .photoEmpty(let id):
|
||||
if boxed {
|
||||
buffer.appendInt32(590459437)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId):
|
||||
return ("photo", [("flags", String(describing: flags)), ("id", String(describing: id)), ("accessHash", String(describing: accessHash)), ("fileReference", String(describing: fileReference)), ("date", String(describing: date)), ("sizes", String(describing: sizes)), ("videoSizes", String(describing: videoSizes)), ("dcId", String(describing: dcId))])
|
||||
case .photoEmpty(let id):
|
||||
return ("photoEmpty", [("id", String(describing: id))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_photo(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Int64?
|
||||
_3 = reader.readInt64()
|
||||
var _4: Buffer?
|
||||
_4 = parseBytes(reader)
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: [Api.PhotoSize]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self)
|
||||
}
|
||||
var _7: [Api.VideoSize]?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.VideoSize.self)
|
||||
} }
|
||||
var _8: Int32?
|
||||
_8 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.Photo.photoEmpty(id: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,99 @@
|
||||
public extension Api {
|
||||
enum Photo: TypeConstructorDescription {
|
||||
case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], videoSizes: [Api.VideoSize]?, dcId: Int32)
|
||||
case photoEmpty(id: Int64)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId):
|
||||
if boxed {
|
||||
buffer.appendInt32(-82216347)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeBytes(fileReference, buffer: buffer, boxed: false)
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sizes.count))
|
||||
for item in sizes {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(videoSizes!.count))
|
||||
for item in videoSizes! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
serializeInt32(dcId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .photoEmpty(let id):
|
||||
if boxed {
|
||||
buffer.appendInt32(590459437)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let videoSizes, let dcId):
|
||||
return ("photo", [("flags", String(describing: flags)), ("id", String(describing: id)), ("accessHash", String(describing: accessHash)), ("fileReference", String(describing: fileReference)), ("date", String(describing: date)), ("sizes", String(describing: sizes)), ("videoSizes", String(describing: videoSizes)), ("dcId", String(describing: dcId))])
|
||||
case .photoEmpty(let id):
|
||||
return ("photoEmpty", [("id", String(describing: id))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_photo(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Int64?
|
||||
_3 = reader.readInt64()
|
||||
var _4: Buffer?
|
||||
_4 = parseBytes(reader)
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: [Api.PhotoSize]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self)
|
||||
}
|
||||
var _7: [Api.VideoSize]?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.VideoSize.self)
|
||||
} }
|
||||
var _8: Int32?
|
||||
_8 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.Photo.photoEmpty(id: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum PhotoSize: TypeConstructorDescription {
|
||||
case photoCachedSize(type: String, w: Int32, h: Int32, bytes: Buffer)
|
||||
@ -848,87 +944,3 @@ public extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum ReactionCount: TypeConstructorDescription {
|
||||
case reactionCount(flags: Int32, reaction: String, count: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .reactionCount(let flags, let reaction, let count):
|
||||
if boxed {
|
||||
buffer.appendInt32(1873957073)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(reaction, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .reactionCount(let flags, let reaction, let count):
|
||||
return ("reactionCount", [("flags", String(describing: flags)), ("reaction", String(describing: reaction)), ("count", String(describing: count))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.ReactionCount.reactionCount(flags: _1!, reaction: _2!, count: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum ReceivedNotifyMessage: TypeConstructorDescription {
|
||||
case receivedNotifyMessage(id: Int32, flags: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .receivedNotifyMessage(let id, let flags):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1551583367)
|
||||
}
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .receivedNotifyMessage(let id, let flags):
|
||||
return ("receivedNotifyMessage", [("id", String(describing: id)), ("flags", String(describing: flags))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_receivedNotifyMessage(_ reader: BufferReader) -> ReceivedNotifyMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.ReceivedNotifyMessage.receivedNotifyMessage(id: _1!, flags: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,87 @@
|
||||
public extension Api {
|
||||
enum ReactionCount: TypeConstructorDescription {
|
||||
case reactionCount(flags: Int32, reaction: String, count: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .reactionCount(let flags, let reaction, let count):
|
||||
if boxed {
|
||||
buffer.appendInt32(1873957073)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(reaction, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .reactionCount(let flags, let reaction, let count):
|
||||
return ("reactionCount", [("flags", String(describing: flags)), ("reaction", String(describing: reaction)), ("count", String(describing: count))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.ReactionCount.reactionCount(flags: _1!, reaction: _2!, count: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum ReceivedNotifyMessage: TypeConstructorDescription {
|
||||
case receivedNotifyMessage(id: Int32, flags: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .receivedNotifyMessage(let id, let flags):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1551583367)
|
||||
}
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .receivedNotifyMessage(let id, let flags):
|
||||
return ("receivedNotifyMessage", [("id", String(describing: id)), ("flags", String(describing: flags))])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_receivedNotifyMessage(_ reader: BufferReader) -> ReceivedNotifyMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.ReceivedNotifyMessage.receivedNotifyMessage(id: _1!, flags: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum RecentMeUrl: TypeConstructorDescription {
|
||||
case recentMeUrlChat(url: String, chatId: Int64)
|
||||
|
||||
@ -87,6 +87,7 @@ public extension Api {
|
||||
public extension Api {
|
||||
enum StickerSetCovered: TypeConstructorDescription {
|
||||
case stickerSetCovered(set: Api.StickerSet, cover: Api.Document)
|
||||
case stickerSetFullCovered(set: Api.StickerSet, packs: [Api.StickerPack], documents: [Api.Document])
|
||||
case stickerSetMultiCovered(set: Api.StickerSet, covers: [Api.Document])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
@ -98,6 +99,22 @@ public extension Api {
|
||||
set.serialize(buffer, true)
|
||||
cover.serialize(buffer, true)
|
||||
break
|
||||
case .stickerSetFullCovered(let set, let packs, let documents):
|
||||
if boxed {
|
||||
buffer.appendInt32(451763941)
|
||||
}
|
||||
set.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(packs.count))
|
||||
for item in packs {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(documents.count))
|
||||
for item in documents {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .stickerSetMultiCovered(let set, let covers):
|
||||
if boxed {
|
||||
buffer.appendInt32(872932635)
|
||||
@ -116,6 +133,8 @@ public extension Api {
|
||||
switch self {
|
||||
case .stickerSetCovered(let set, let cover):
|
||||
return ("stickerSetCovered", [("set", String(describing: set)), ("cover", String(describing: cover))])
|
||||
case .stickerSetFullCovered(let set, let packs, let documents):
|
||||
return ("stickerSetFullCovered", [("set", String(describing: set)), ("packs", String(describing: packs)), ("documents", String(describing: documents))])
|
||||
case .stickerSetMultiCovered(let set, let covers):
|
||||
return ("stickerSetMultiCovered", [("set", String(describing: set)), ("covers", String(describing: covers))])
|
||||
}
|
||||
@ -139,6 +158,29 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
var _2: [Api.StickerPack]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
|
||||
}
|
||||
var _3: [Api.Document]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, documents: _3!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -592,6 +634,7 @@ public extension Api {
|
||||
case updateReadChannelDiscussionOutbox(channelId: Int64, topMsgId: Int32, readMaxId: Int32)
|
||||
case updateReadChannelInbox(flags: Int32, folderId: Int32?, channelId: Int64, maxId: Int32, stillUnreadCount: Int32, pts: Int32)
|
||||
case updateReadChannelOutbox(channelId: Int64, maxId: Int32)
|
||||
case updateReadFeaturedEmojiStickers
|
||||
case updateReadFeaturedStickers
|
||||
case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32)
|
||||
case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32)
|
||||
@ -1332,6 +1375,12 @@ public extension Api {
|
||||
}
|
||||
serializeInt64(channelId, buffer: buffer, boxed: false)
|
||||
serializeInt32(maxId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .updateReadFeaturedEmojiStickers:
|
||||
if boxed {
|
||||
buffer.appendInt32(-78886548)
|
||||
}
|
||||
|
||||
break
|
||||
case .updateReadFeaturedStickers:
|
||||
if boxed {
|
||||
@ -1660,6 +1709,8 @@ public extension Api {
|
||||
return ("updateReadChannelInbox", [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("channelId", String(describing: channelId)), ("maxId", String(describing: maxId)), ("stillUnreadCount", String(describing: stillUnreadCount)), ("pts", String(describing: pts))])
|
||||
case .updateReadChannelOutbox(let channelId, let maxId):
|
||||
return ("updateReadChannelOutbox", [("channelId", String(describing: channelId)), ("maxId", String(describing: maxId))])
|
||||
case .updateReadFeaturedEmojiStickers:
|
||||
return ("updateReadFeaturedEmojiStickers", [])
|
||||
case .updateReadFeaturedStickers:
|
||||
return ("updateReadFeaturedStickers", [])
|
||||
case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount):
|
||||
@ -3196,6 +3247,9 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateReadFeaturedEmojiStickers(_ reader: BufferReader) -> Update? {
|
||||
return Api.Update.updateReadFeaturedEmojiStickers
|
||||
}
|
||||
public static func parse_updateReadFeaturedStickers(_ reader: BufferReader) -> Update? {
|
||||
return Api.Update.updateReadFeaturedStickers
|
||||
}
|
||||
|
||||
@ -728,13 +728,13 @@ public extension Api.payments {
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum PaymentForm: TypeConstructorDescription {
|
||||
case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: Api.PaymentSavedCredentials?, users: [Api.User])
|
||||
case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: Api.PaymentSavedCredentials?, users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let savedInfo, let savedCredentials, let users):
|
||||
case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1340916937)
|
||||
buffer.appendInt32(1288001087)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(formId, buffer: buffer, boxed: false)
|
||||
@ -747,6 +747,11 @@ public extension Api.payments {
|
||||
serializeString(url, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeString(nativeProvider!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {nativeParams!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 6) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(additionalMethods!.count))
|
||||
for item in additionalMethods! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 0) != 0 {savedInfo!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {savedCredentials!.serialize(buffer, true)}
|
||||
buffer.appendInt32(481674261)
|
||||
@ -760,8 +765,8 @@ public extension Api.payments {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let savedInfo, let savedCredentials, let users):
|
||||
return ("paymentForm", [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("botId", String(describing: botId)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("invoice", String(describing: invoice)), ("providerId", String(describing: providerId)), ("url", String(describing: url)), ("nativeProvider", String(describing: nativeProvider)), ("nativeParams", String(describing: nativeParams)), ("savedInfo", String(describing: savedInfo)), ("savedCredentials", String(describing: savedCredentials)), ("users", String(describing: users))])
|
||||
case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users):
|
||||
return ("paymentForm", [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("botId", String(describing: botId)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("invoice", String(describing: invoice)), ("providerId", String(describing: providerId)), ("url", String(describing: url)), ("nativeProvider", String(describing: nativeProvider)), ("nativeParams", String(describing: nativeParams)), ("additionalMethods", String(describing: additionalMethods)), ("savedInfo", String(describing: savedInfo)), ("savedCredentials", String(describing: savedCredentials)), ("users", String(describing: users))])
|
||||
}
|
||||
}
|
||||
|
||||
@ -794,17 +799,21 @@ public extension Api.payments {
|
||||
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
||||
_11 = Api.parse(reader, signature: signature) as? Api.DataJSON
|
||||
} }
|
||||
var _12: Api.PaymentRequestedInfo?
|
||||
var _12: [Api.PaymentFormMethod]?
|
||||
if Int(_1!) & Int(1 << 6) != 0 {if let _ = reader.readInt32() {
|
||||
_12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentFormMethod.self)
|
||||
} }
|
||||
var _13: Api.PaymentRequestedInfo?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_12 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
|
||||
_13 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
|
||||
} }
|
||||
var _13: Api.PaymentSavedCredentials?
|
||||
var _14: Api.PaymentSavedCredentials?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
|
||||
_13 = Api.parse(reader, signature: signature) as? Api.PaymentSavedCredentials
|
||||
_14 = Api.parse(reader, signature: signature) as? Api.PaymentSavedCredentials
|
||||
} }
|
||||
var _14: [Api.User]?
|
||||
var _15: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
@ -817,11 +826,12 @@ public extension Api.payments {
|
||||
let _c9 = _9 != nil
|
||||
let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil
|
||||
let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil
|
||||
let _c12 = (Int(_1!) & Int(1 << 0) == 0) || _12 != nil
|
||||
let _c13 = (Int(_1!) & Int(1 << 1) == 0) || _13 != nil
|
||||
let _c14 = _14 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
|
||||
return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, savedInfo: _12, savedCredentials: _13, users: _14!)
|
||||
let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil
|
||||
let _c13 = (Int(_1!) & Int(1 << 0) == 0) || _13 != nil
|
||||
let _c14 = (Int(_1!) & Int(1 << 1) == 0) || _14 != nil
|
||||
let _c15 = _15 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 {
|
||||
return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
||||
@ -4215,6 +4215,21 @@ public extension Api.functions.messages {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func getFeaturedEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.FeaturedStickers>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(248473398)
|
||||
serializeInt64(hash, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messages.getFeaturedEmojiStickers", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.FeaturedStickers?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func getFeaturedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.FeaturedStickers>) {
|
||||
let buffer = Buffer()
|
||||
@ -6295,11 +6310,11 @@ public extension Api.functions.payments {
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func canPurchasePremium() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
static func canPurchasePremium(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1435856696)
|
||||
|
||||
return (FunctionDescription(name: "payments.canPurchasePremium", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
buffer.appendInt32(-1614700874)
|
||||
purpose.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "payments.canPurchasePremium", parameters: [("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
|
||||
@ -800,13 +800,13 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
AnyComponentWithIdentity(id: "a", component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: "anim_profilemore",
|
||||
colors: [
|
||||
"Point 2.Group 1.Fill 1": whiteColor,
|
||||
"Point 3.Group 1.Fill 1": whiteColor,
|
||||
"Point 1.Group 1.Fill 1": whiteColor
|
||||
],
|
||||
mode: .still(position: .begin)
|
||||
),
|
||||
colors: [
|
||||
"Point 2.Group 1.Fill 1": whiteColor,
|
||||
"Point 3.Group 1.Fill 1": whiteColor,
|
||||
"Point 1.Group 1.Fill 1": whiteColor
|
||||
],
|
||||
size: CGSize(width: 22.0, height: 22.0)
|
||||
).tagged(moreAnimationTag))),
|
||||
])),
|
||||
|
||||
@ -351,6 +351,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
|
||||
var addedHashtags: [String] = []
|
||||
var emojiItems: [RecentEmojiItem] = []
|
||||
|
||||
var localGroupingKeyBySourceKey: [Int64: Int64] = [:]
|
||||
|
||||
@ -475,6 +476,13 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
let hashtag = nsText.substring(with: entityRange)
|
||||
addedHashtags.append(hashtag)
|
||||
}
|
||||
} else if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
if let file = inlineStickers[mediaId] as? TelegramMediaFile {
|
||||
emojiItems.append(RecentEmojiItem(.file(file)))
|
||||
} else if let file = transaction.getMedia(mediaId) as? TelegramMediaFile {
|
||||
emojiItems.append(RecentEmojiItem(.file(file)))
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
@ -786,6 +794,19 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
var messageIds: [MessageId?] = []
|
||||
if !storeMessages.isEmpty {
|
||||
for emojiItem in emojiItems {
|
||||
if let entry = CodableEntry(emojiItem) {
|
||||
let id: RecentEmojiItemId
|
||||
switch emojiItem.content {
|
||||
case let .file(file):
|
||||
id = RecentEmojiItemId(file.fileId)
|
||||
case let .text(text):
|
||||
id = RecentEmojiItemId(text)
|
||||
}
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.LocalRecentEmoji, item: OrderedItemListEntry(id: id.rawValue, contents: entry), removeTailIfCountExceeds: 20)
|
||||
}
|
||||
}
|
||||
|
||||
let globallyUniqueIdToMessageId = transaction.addMessages(storeMessages, location: .Random)
|
||||
for globallyUniqueId in globallyUniqueIds {
|
||||
messageIds.append(globallyUniqueIdToMessageId[globallyUniqueId])
|
||||
|
||||
@ -316,6 +316,7 @@ public final class AccountViewTracker {
|
||||
|
||||
private var channelPollingContexts: [PeerId: ChannelPollingContext] = [:]
|
||||
private var featuredStickerPacksContext: FeaturedStickerPacksContext?
|
||||
private var featuredEmojiPacksContext: FeaturedStickerPacksContext?
|
||||
|
||||
let chatHistoryPreloadManager: ChatHistoryPreloadManager
|
||||
|
||||
@ -1711,7 +1712,7 @@ public final class AccountViewTracker {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
if context.timestamp == nil || abs(context.timestamp! - timestamp) > 60.0 * 60.0 {
|
||||
context.timestamp = timestamp
|
||||
context.disposable.set(updatedFeaturedStickerPacks(network: account.network, postbox: account.postbox).start())
|
||||
context.disposable.set(updatedFeaturedStickerPacks(network: account.network, postbox: account.postbox, category: .stickerPacks).start())
|
||||
}
|
||||
|
||||
let index = context.subscribers.add(Void())
|
||||
@ -1736,6 +1737,56 @@ public final class AccountViewTracker {
|
||||
}
|
||||
}
|
||||
|
||||
public func featuredEmojiPacks() -> Signal<[FeaturedStickerPackItem], NoError> {
|
||||
return Signal { subscriber in
|
||||
if let account = self.account {
|
||||
let view = account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)]).start(next: { next in
|
||||
if let view = next.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)] as? OrderedItemListView {
|
||||
subscriber.putNext(view.items.map { $0.contents.get(FeaturedStickerPackItem.self)! })
|
||||
} else {
|
||||
subscriber.putNext([])
|
||||
}
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
let disposable = MetaDisposable()
|
||||
self.queue.async {
|
||||
let context: FeaturedStickerPacksContext
|
||||
if let current = self.featuredEmojiPacksContext {
|
||||
context = current
|
||||
} else {
|
||||
context = FeaturedStickerPacksContext()
|
||||
self.featuredEmojiPacksContext = context
|
||||
}
|
||||
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
if context.timestamp == nil || abs(context.timestamp! - timestamp) > 60.0 * 60.0 {
|
||||
context.timestamp = timestamp
|
||||
context.disposable.set(updatedFeaturedStickerPacks(network: account.network, postbox: account.postbox, category: .emojiPacks).start())
|
||||
}
|
||||
|
||||
let index = context.subscribers.add(Void())
|
||||
|
||||
disposable.set(ActionDisposable {
|
||||
self.queue.async {
|
||||
if let context = self.featuredEmojiPacksContext {
|
||||
context.subscribers.remove(index)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return ActionDisposable {
|
||||
view.dispose()
|
||||
disposable.dispose()
|
||||
}
|
||||
} else {
|
||||
subscriber.putNext([])
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func callListView(type: CallListViewType, index: MessageIndex, count: Int) -> Signal<CallListView, NoError> {
|
||||
if let account = self.account {
|
||||
let granularity: Int32 = 60 * 60 * 24
|
||||
|
||||
@ -220,10 +220,12 @@ private func installRemoteStickerPacks(network: Network, infos: [StickerPackColl
|
||||
var archivedIds = Set<ItemCollectionId>()
|
||||
for archivedSet in archivedSets {
|
||||
switch archivedSet {
|
||||
case let .stickerSetCovered(set, _):
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
case let .stickerSetMultiCovered(set, _):
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
case let .stickerSetCovered(set, _):
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
case let .stickerSetMultiCovered(set, _):
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
case let .stickerSetFullCovered(set, _, _):
|
||||
archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id)
|
||||
}
|
||||
}
|
||||
return archivedIds
|
||||
|
||||
@ -3,6 +3,30 @@ import TelegramApi
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
enum FeaturedStickerPacksCategory {
|
||||
case stickerPacks
|
||||
case emojiPacks
|
||||
}
|
||||
|
||||
extension FeaturedStickerPacksCategory {
|
||||
var itemListNamespace: Int32 {
|
||||
switch self {
|
||||
case .stickerPacks:
|
||||
return Namespaces.OrderedItemList.CloudFeaturedStickerPacks
|
||||
case .emojiPacks:
|
||||
return Namespaces.OrderedItemList.CloudFeaturedEmojiPacks
|
||||
}
|
||||
}
|
||||
|
||||
var collectionIdNamespace: Int32 {
|
||||
switch self {
|
||||
case .stickerPacks:
|
||||
return Namespaces.ItemCollection.CloudStickerPacks
|
||||
case .emojiPacks:
|
||||
return Namespaces.ItemCollection.CloudEmojiPacks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func hashForIdsReverse(_ ids: [Int64]) -> Int64 {
|
||||
var acc: UInt64 = 0
|
||||
@ -24,9 +48,9 @@ func manageStickerPacks(network: Network, postbox: Postbox) -> Signal<Void, NoEr
|
||||
} |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||
}
|
||||
|
||||
func updatedFeaturedStickerPacks(network: Network, postbox: Postbox) -> Signal<Void, NoError> {
|
||||
func updatedFeaturedStickerPacks(network: Network, postbox: Postbox, category: FeaturedStickerPacksCategory) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
let initialPacks = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
|
||||
let initialPacks = transaction.getOrderedListItems(collectionId: category.itemListNamespace)
|
||||
var initialPackMap: [Int64: FeaturedStickerPackItem] = [:]
|
||||
for entry in initialPacks {
|
||||
let item = entry.contents.get(FeaturedStickerPackItem.self)!
|
||||
@ -37,18 +61,29 @@ func updatedFeaturedStickerPacks(network: Network, postbox: Postbox) -> Signal<V
|
||||
return FeaturedStickerPackItemId($0.id).packId
|
||||
}
|
||||
let initialHash: Int64 = hashForIdsReverse(initialPackIds)
|
||||
return network.request(Api.functions.messages.getFeaturedStickers(hash: initialHash))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
|
||||
struct FeaturedListContent {
|
||||
var unreadIds: Set<Int64>
|
||||
var packs: [FeaturedStickerPackItem]
|
||||
var isPremium: Bool
|
||||
}
|
||||
enum FeaturedList {
|
||||
case notModified
|
||||
case content(FeaturedListContent)
|
||||
}
|
||||
let signal: Signal<FeaturedList, NoError>
|
||||
switch category {
|
||||
case .stickerPacks:
|
||||
signal = network.request(Api.functions.messages.getFeaturedStickers(hash: initialHash))
|
||||
|> map { result -> FeaturedList in
|
||||
switch result {
|
||||
case .featuredStickersNotModified:
|
||||
break
|
||||
return .notModified
|
||||
case let .featuredStickers(flags, _, _, sets, unread):
|
||||
let unreadIds = Set(unread)
|
||||
var updatedPacks: [FeaturedStickerPackItem] = []
|
||||
for set in sets {
|
||||
var (info, items) = parsePreviewStickerSet(set)
|
||||
var (info, items) = parsePreviewStickerSet(set, namespace: category.collectionIdNamespace)
|
||||
if let previousPack = initialPackMap[info.id.id] {
|
||||
if previousPack.info.hash == info.hash {
|
||||
items = previousPack.topItems
|
||||
@ -56,7 +91,56 @@ func updatedFeaturedStickerPacks(network: Network, postbox: Postbox) -> Signal<V
|
||||
}
|
||||
updatedPacks.append(FeaturedStickerPackItem(info: info, topItems: items, unread: unreadIds.contains(info.id.id)))
|
||||
}
|
||||
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedStickerPacks, items: updatedPacks.compactMap { item -> OrderedItemListEntry? in
|
||||
let isPremium = flags & (1 << 0) != 0
|
||||
return .content(FeaturedListContent(
|
||||
unreadIds: unreadIds,
|
||||
packs: updatedPacks,
|
||||
isPremium: isPremium
|
||||
))
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<FeaturedList, NoError> in
|
||||
return .single(.notModified)
|
||||
}
|
||||
case .emojiPacks:
|
||||
signal = network.request(Api.functions.messages.getFeaturedEmojiStickers(hash: initialHash))
|
||||
|> map { result -> FeaturedList in
|
||||
switch result {
|
||||
case .featuredStickersNotModified:
|
||||
return .notModified
|
||||
case let .featuredStickers(flags, _, _, sets, unread):
|
||||
let unreadIds = Set(unread)
|
||||
var updatedPacks: [FeaturedStickerPackItem] = []
|
||||
for set in sets {
|
||||
var (info, items) = parsePreviewStickerSet(set, namespace: category.collectionIdNamespace)
|
||||
if let previousPack = initialPackMap[info.id.id] {
|
||||
if previousPack.info.hash == info.hash {
|
||||
items = previousPack.topItems
|
||||
}
|
||||
}
|
||||
updatedPacks.append(FeaturedStickerPackItem(info: info, topItems: items, unread: unreadIds.contains(info.id.id)))
|
||||
}
|
||||
let isPremium = flags & (1 << 0) != 0
|
||||
return .content(FeaturedListContent(
|
||||
unreadIds: unreadIds,
|
||||
packs: updatedPacks,
|
||||
isPremium: isPremium
|
||||
))
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<FeaturedList, NoError> in
|
||||
return .single(.notModified)
|
||||
}
|
||||
}
|
||||
|
||||
return signal
|
||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
switch result {
|
||||
case .notModified:
|
||||
break
|
||||
case let .content(content):
|
||||
transaction.replaceOrderedItemListItems(collectionId: category.itemListNamespace, items: content.packs.compactMap { item -> OrderedItemListEntry? in
|
||||
if let entry = CodableEntry(item) {
|
||||
return OrderedItemListEntry(id: FeaturedStickerPackItemId(item.info.id.id).rawValue, contents: entry)
|
||||
} else {
|
||||
@ -64,14 +148,14 @@ func updatedFeaturedStickerPacks(network: Network, postbox: Postbox) -> Signal<V
|
||||
}
|
||||
})
|
||||
|
||||
let isPremium = flags & (1 << 0) != 0
|
||||
if let entry = CodableEntry(FeaturedStickersConfiguration(isPremium: isPremium)) {
|
||||
if let entry = CodableEntry(FeaturedStickersConfiguration(isPremium: content.isPremium)) {
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.featuredStickersConfiguration, key: ValueBoxKey(length: 0)), entry: entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
public func requestOldFeaturedStickerPacks(network: Network, postbox: Postbox, offset: Int, limit: Int) -> Signal<[FeaturedStickerPackItem], NoError> {
|
||||
@ -85,7 +169,7 @@ public func requestOldFeaturedStickerPacks(network: Network, postbox: Postbox, o
|
||||
let unreadIds = Set(unread)
|
||||
var updatedPacks: [FeaturedStickerPackItem] = []
|
||||
for set in sets {
|
||||
let (info, items) = parsePreviewStickerSet(set)
|
||||
let (info, items) = parsePreviewStickerSet(set, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
updatedPacks.append(FeaturedStickerPackItem(info: info, topItems: items, unread: unreadIds.contains(info.id.id)))
|
||||
}
|
||||
return updatedPacks
|
||||
@ -125,23 +209,55 @@ public func preloadedFeaturedStickerSet(network: Network, postbox: Postbox, id:
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollectionId.Namespace = Namespaces.ItemCollection.CloudStickerPacks) -> (StickerPackCollectionInfo, [StickerPackItem]) {
|
||||
func parsePreviewStickerSet(_ set: Api.StickerSetCovered, namespace: ItemCollectionId.Namespace) -> (StickerPackCollectionInfo, [StickerPackItem]) {
|
||||
switch set {
|
||||
case let .stickerSetCovered(set, cover):
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
|
||||
var items: [StickerPackItem] = []
|
||||
case let .stickerSetCovered(set, cover):
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
|
||||
var items: [StickerPackItem] = []
|
||||
if let file = telegramMediaFileFromApiDocument(cover), let id = file.id {
|
||||
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: []))
|
||||
}
|
||||
return (info, items)
|
||||
case let .stickerSetMultiCovered(set, covers):
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
|
||||
var items: [StickerPackItem] = []
|
||||
for cover in covers {
|
||||
if let file = telegramMediaFileFromApiDocument(cover), let id = file.id {
|
||||
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: []))
|
||||
}
|
||||
return (info, items)
|
||||
case let .stickerSetMultiCovered(set, covers):
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
|
||||
var items: [StickerPackItem] = []
|
||||
for cover in covers {
|
||||
if let file = telegramMediaFileFromApiDocument(cover), let id = file.id {
|
||||
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: []))
|
||||
}
|
||||
return (info, items)
|
||||
case let .stickerSetFullCovered(set, packs, documents):
|
||||
var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:]
|
||||
for pack in packs {
|
||||
switch pack {
|
||||
case let .stickerPack(text, fileIds):
|
||||
let key = ValueBoxKey(text).toMemoryBuffer()
|
||||
for fileId in fileIds {
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
if indexKeysByFile[mediaId] == nil {
|
||||
indexKeysByFile[mediaId] = [key]
|
||||
} else {
|
||||
indexKeysByFile[mediaId]!.append(key)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return (info, items)
|
||||
}
|
||||
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
|
||||
var items: [StickerPackItem] = []
|
||||
for document in documents {
|
||||
if let file = telegramMediaFileFromApiDocument(document), let id = file.id {
|
||||
let fileIndexKeys: [MemoryBuffer]
|
||||
if let indexKeys = indexKeysByFile[id] {
|
||||
fileIndexKeys = indexKeys
|
||||
} else {
|
||||
fileIndexKeys = []
|
||||
}
|
||||
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: fileIndexKeys))
|
||||
}
|
||||
}
|
||||
return (info, items)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,3 +45,6 @@ func _internal_clearRecentlyUsedStickers(transaction: Transaction) {
|
||||
addSynchronizeRecentlyUsedMediaOperation(transaction: transaction, category: .stickers, operation: .clear)
|
||||
}
|
||||
|
||||
func _internal_clearRecentlyUsedEmoji(transaction: Transaction) {
|
||||
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.LocalRecentEmoji, items: [])
|
||||
}
|
||||
|
||||
@ -64,6 +64,8 @@ public struct Namespaces {
|
||||
public static let RecentDownloads: Int32 = 11
|
||||
public static let PremiumStickers: Int32 = 12
|
||||
public static let CloudPremiumStickers: Int32 = 13
|
||||
public static let LocalRecentEmoji: Int32 = 14
|
||||
public static let CloudFeaturedEmojiPacks: Int32 = 15
|
||||
}
|
||||
|
||||
public struct CachedItemCollection {
|
||||
|
||||
@ -49,3 +49,106 @@ public final class RecentMediaItem: Codable, Equatable {
|
||||
return lhs.media.isEqual(to: rhs.media)
|
||||
}
|
||||
}
|
||||
|
||||
public struct RecentEmojiItemId {
|
||||
public enum Id {
|
||||
case media(MediaId)
|
||||
case text(String)
|
||||
}
|
||||
|
||||
public let rawValue: MemoryBuffer
|
||||
public let id: Id
|
||||
|
||||
public init(_ rawValue: MemoryBuffer) {
|
||||
self.rawValue = rawValue
|
||||
|
||||
assert(rawValue.length >= 1)
|
||||
var type: UInt8 = 0
|
||||
memcpy(&type, rawValue.memory.advanced(by: 0), 1)
|
||||
|
||||
if type == 0 {
|
||||
assert(rawValue.length == 1 + 4 + 8)
|
||||
var mediaIdNamespace: Int32 = 0
|
||||
var mediaIdId: Int64 = 0
|
||||
memcpy(&mediaIdNamespace, rawValue.memory.advanced(by: 1), 4)
|
||||
memcpy(&mediaIdId, rawValue.memory.advanced(by: 1 + 4), 8)
|
||||
self.id = .media(MediaId(namespace: mediaIdNamespace, id: mediaIdId))
|
||||
} else if type == 1 {
|
||||
var length: UInt16 = 0
|
||||
assert(rawValue.length >= 1 + 2)
|
||||
memcpy(&length, rawValue.memory.advanced(by: 1), 2)
|
||||
|
||||
assert(rawValue.length >= 1 + 2 + Int(length))
|
||||
|
||||
self.id = .text(String(data: Data(bytes: rawValue.memory.advanced(by: 1 + 2), count: Int(length)), encoding: .utf8) ?? ".")
|
||||
} else {
|
||||
assert(false)
|
||||
self.id = .text(".")
|
||||
}
|
||||
}
|
||||
|
||||
public init(_ mediaId: MediaId) {
|
||||
self.id = .media(mediaId)
|
||||
|
||||
var mediaIdNamespace: Int32 = mediaId.namespace
|
||||
var mediaIdId: Int64 = mediaId.id
|
||||
self.rawValue = MemoryBuffer(memory: malloc(1 + 4 + 8)!, capacity: 1 + 4 + 8, length: 1 + 4 + 8, freeWhenDone: true)
|
||||
var type: UInt8 = 0
|
||||
memcpy(self.rawValue.memory.advanced(by: 0), &type, 1)
|
||||
memcpy(self.rawValue.memory.advanced(by: 1), &mediaIdNamespace, 4)
|
||||
memcpy(self.rawValue.memory.advanced(by: 1 + 4), &mediaIdId, 8)
|
||||
}
|
||||
|
||||
public init(_ text: String) {
|
||||
self.id = .text(text)
|
||||
|
||||
let data = text.data(using: .utf8) ?? Data()
|
||||
var length: UInt16 = UInt16(data.count)
|
||||
|
||||
self.rawValue = MemoryBuffer(memory: malloc(1 + 2 + data.count)!, capacity: 1 + 2 + data.count, length: 1 + 2 + data.count, freeWhenDone: true)
|
||||
var type: UInt8 = 1
|
||||
memcpy(self.rawValue.memory.advanced(by: 0), &type, 1)
|
||||
memcpy(self.rawValue.memory.advanced(by: 1), &length, 2)
|
||||
data.withUnsafeBytes { bytes in
|
||||
let _ = memcpy(self.rawValue.memory.advanced(by: 1 + 2), bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), bytes.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class RecentEmojiItem: Codable, Equatable {
|
||||
public enum Content: Equatable {
|
||||
case file(TelegramMediaFile)
|
||||
case text(String)
|
||||
}
|
||||
|
||||
public let content: Content
|
||||
|
||||
public init(_ content: Content) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
if let mediaData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "m") {
|
||||
self.content = .file(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: mediaData.data))))
|
||||
} else {
|
||||
self.content = .text(try container.decode(String.self, forKey: "s"))
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
switch self.content {
|
||||
case let .file(file):
|
||||
try container.encode(PostboxEncoder().encodeObjectToRawData(file), forKey: "m")
|
||||
case let .text(string):
|
||||
try container.encode(string, forKey: "s")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: RecentEmojiItem, rhs: RecentEmojiItem) -> Bool {
|
||||
return lhs.content == rhs.content
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ public enum RestoreAppStoreReceiptError {
|
||||
}
|
||||
|
||||
func _internal_canPurchasePremium(account: Account) -> Signal<Bool, NoError> {
|
||||
return account.network.request(Api.functions.payments.canPurchasePremium())
|
||||
return account.network.request(Api.functions.payments.canPurchasePremium(purpose: .inputStorePaymentPremiumSubscription(flags: 0)))
|
||||
|> map { result -> Bool in
|
||||
switch result {
|
||||
case .boolTrue:
|
||||
|
||||
@ -213,7 +213,7 @@ func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source
|
||||
|> mapToSignal { result -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> in
|
||||
return postbox.transaction { transaction -> TelegramMediaInvoice in
|
||||
switch result {
|
||||
case let .paymentForm(_, _, _, title, description, photo, invoice, _, _, _, _, _, _, _):
|
||||
case let .paymentForm(_, _, _, title, description, photo, invoice, _, _, _, _, _, _, _, _):
|
||||
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
||||
|
||||
var parsedFlags = TelegramMediaInvoiceFlags()
|
||||
@ -266,10 +266,11 @@ func _internal_fetchBotPaymentForm(postbox: Postbox, network: Network, source: B
|
||||
|> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
|
||||
return postbox.transaction { transaction -> BotPaymentForm in
|
||||
switch result {
|
||||
case let .paymentForm(flags, id, botId, title, description, photo, invoice, providerId, url, nativeProvider, nativeParams, savedInfo, savedCredentials, apiUsers):
|
||||
case let .paymentForm(flags, id, botId, title, description, photo, invoice, providerId, url, nativeProvider, nativeParams, additionalMethods, savedInfo, savedCredentials, apiUsers):
|
||||
let _ = title
|
||||
let _ = description
|
||||
let _ = photo
|
||||
let _ = additionalMethods
|
||||
|
||||
var peers: [Peer] = []
|
||||
for user in apiUsers {
|
||||
|
||||
@ -306,7 +306,7 @@ func _internal_searchStickerSetsRemotely(network: Network, query: String) -> Sig
|
||||
case let .foundStickerSets(_, sets: sets):
|
||||
var result = FoundStickerSets()
|
||||
for set in sets {
|
||||
let parsed = parsePreviewStickerSet(set)
|
||||
let parsed = parsePreviewStickerSet(set, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
let values = parsed.1.map({ ItemCollectionViewEntry(index: ItemCollectionViewEntryIndex(collectionIndex: index, collectionId: parsed.0.id, itemIndex: $0.index), item: $0) })
|
||||
result = result.withUpdatedInfosAndEntries(infos: [(parsed.0.id, parsed.0, parsed.1.first, false)], entries: values)
|
||||
index += 1
|
||||
|
||||
@ -105,7 +105,7 @@ func _internal_stickerPacksAttachedToMedia(account: Account, media: AnyMediaRefe
|
||||
|> map { result -> [StickerPackReference] in
|
||||
return result.map { pack in
|
||||
switch pack {
|
||||
case let .stickerSetCovered(set, _), let .stickerSetMultiCovered(set, _):
|
||||
case let .stickerSetCovered(set, _), let .stickerSetMultiCovered(set, _), let .stickerSetFullCovered(set, _, _):
|
||||
let info = StickerPackCollectionInfo(apiSet: set, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
return .id(id: info.id.id, accessHash: info.accessHash)
|
||||
}
|
||||
|
||||
@ -140,64 +140,69 @@ public final class CoveredStickerSet : Equatable {
|
||||
func _internal_installStickerSetInteractively(account: Account, info: StickerPackCollectionInfo, items: [ItemCollectionItem]) -> Signal<InstallStickerSetResult, InstallStickerSetError> {
|
||||
return account.network.request(Api.functions.messages.installStickerSet(stickerset: .inputStickerSetID(id: info.id.id, accessHash: info.accessHash), archived: .boolFalse)) |> mapError { _ -> InstallStickerSetError in
|
||||
return .generic
|
||||
} |> mapToSignal { result -> Signal<InstallStickerSetResult, InstallStickerSetError> in
|
||||
let addResult:InstallStickerSetResult
|
||||
switch result {
|
||||
case .stickerSetInstallResultSuccess:
|
||||
addResult = .successful
|
||||
case let .stickerSetInstallResultArchive(sets: archived):
|
||||
var coveredSets:[CoveredStickerSet] = []
|
||||
for archived in archived {
|
||||
let apiDocuments:[Api.Document]
|
||||
let apiSet:Api.StickerSet
|
||||
switch archived {
|
||||
case let .stickerSetCovered(set: set, cover: cover):
|
||||
apiSet = set
|
||||
apiDocuments = [cover]
|
||||
case let .stickerSetMultiCovered(set: set, covers: covers):
|
||||
apiSet = set
|
||||
apiDocuments = covers
|
||||
}
|
||||
|> mapToSignal { result -> Signal<InstallStickerSetResult, InstallStickerSetError> in
|
||||
let addResult:InstallStickerSetResult
|
||||
switch result {
|
||||
case .stickerSetInstallResultSuccess:
|
||||
addResult = .successful
|
||||
case let .stickerSetInstallResultArchive(sets: archived):
|
||||
var coveredSets:[CoveredStickerSet] = []
|
||||
for archived in archived {
|
||||
let apiDocuments:[Api.Document]
|
||||
let apiSet:Api.StickerSet
|
||||
switch archived {
|
||||
case let .stickerSetCovered(set: set, cover: cover):
|
||||
apiSet = set
|
||||
apiDocuments = [cover]
|
||||
case let .stickerSetMultiCovered(set: set, covers: covers):
|
||||
apiSet = set
|
||||
apiDocuments = covers
|
||||
case let .stickerSetFullCovered(set, _, documents):
|
||||
apiSet = set
|
||||
apiDocuments = documents
|
||||
}
|
||||
|
||||
let info = StickerPackCollectionInfo(apiSet: apiSet, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
|
||||
var items:[StickerPackItem] = []
|
||||
for apiDocument in apiDocuments {
|
||||
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
|
||||
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(items.count), id: id.id), file: file, indexKeys: []))
|
||||
}
|
||||
|
||||
let info = StickerPackCollectionInfo(apiSet: apiSet, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
|
||||
var items:[StickerPackItem] = []
|
||||
for apiDocument in apiDocuments {
|
||||
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
|
||||
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(items.count), id: id.id), file: file, indexKeys: []))
|
||||
}
|
||||
coveredSets.append(CoveredStickerSet(info: info, items: items))
|
||||
}
|
||||
addResult = .archived(coveredSets)
|
||||
}
|
||||
|
||||
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
var collections = transaction.getCollectionsItems(namespace: info.id.namespace)
|
||||
|
||||
var removableIndexes:[Int] = []
|
||||
for i in 0 ..< collections.count {
|
||||
if collections[i].0 == info.id {
|
||||
removableIndexes.append(i)
|
||||
}
|
||||
if case let .archived(sets) = addResult {
|
||||
for set in sets {
|
||||
if collections[i].0 == set.info.id {
|
||||
removableIndexes.append(i)
|
||||
}
|
||||
}
|
||||
coveredSets.append(CoveredStickerSet(info: info, items: items))
|
||||
}
|
||||
addResult = .archived(coveredSets)
|
||||
}
|
||||
|
||||
for index in removableIndexes.reversed() {
|
||||
collections.remove(at: index)
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
var collections = transaction.getCollectionsItems(namespace: info.id.namespace)
|
||||
|
||||
var removableIndexes:[Int] = []
|
||||
for i in 0 ..< collections.count {
|
||||
if collections[i].0 == info.id {
|
||||
removableIndexes.append(i)
|
||||
}
|
||||
if case let .archived(sets) = addResult {
|
||||
for set in sets {
|
||||
if collections[i].0 == set.info.id {
|
||||
removableIndexes.append(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for index in removableIndexes.reversed() {
|
||||
collections.remove(at: index)
|
||||
}
|
||||
|
||||
collections.insert((info.id, info, items), at: 0)
|
||||
|
||||
transaction.replaceItemCollections(namespace: info.id.namespace, itemCollections: collections)
|
||||
} |> map { _ in return addResult} |> mapError { _ -> InstallStickerSetError in }
|
||||
collections.insert((info.id, info, items), at: 0)
|
||||
|
||||
transaction.replaceItemCollections(namespace: info.id.namespace, itemCollections: collections)
|
||||
}
|
||||
|> map { _ in return addResult} |> mapError { _ -> InstallStickerSetError in }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -133,6 +133,13 @@ public extension TelegramEngine {
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func clearRecentlyUsedEmoji() -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
_internal_clearRecentlyUsedEmoji(transaction: transaction)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func reorderStickerPacks(namespace: ItemCollectionId.Namespace, itemIds: [ItemCollectionId]) -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
let infos = transaction.getItemCollectionsInfos(namespace: namespace)
|
||||
|
||||
@ -529,7 +529,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
||||
reactionActiveBackground: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||
reactionActiveForeground: .clear
|
||||
)
|
||||
), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaControlInnerBackgroundColor: UIColor(rgb: 0x313131), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0x313131).mixedWith(UIColor(rgb: 0xffffff), alpha: 0.05), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xffffff), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff).withAlphaComponent(0.12), separator: UIColor(rgb: 0xffffff, alpha: 0.5), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0xffffff), barNegative: UIColor(rgb: 0xffffff)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)
|
||||
), primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), linkTextColor: UIColor(rgb: 0xffffff), linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.5), scamColor: UIColor(rgb: 0xeb5545), textHighlightColor: UIColor(rgb: 0xf5c038), accentTextColor: UIColor(rgb: 0xffffff), accentControlColor: UIColor(rgb: 0xffffff), accentControlDisabledColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaActiveControlColor: UIColor(rgb: 0xffffff), mediaInactiveControlColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaControlInnerBackgroundColor: UIColor(rgb: 0x313131), pendingActivityColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileTitleColor: UIColor(rgb: 0xffffff), fileDescriptionColor: UIColor(rgb: 0xffffff, alpha: 0.5), fileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), mediaPlaceholderColor: UIColor(rgb: 0xffffff, alpha: 0.2), polls: PresentationThemeChatBubblePolls(radioButton: UIColor(rgb: 0xffffff), radioProgress: UIColor(rgb: 0xffffff), highlight: UIColor(rgb: 0xffffff).withAlphaComponent(0.12), separator: UIColor(rgb: 0xffffff, alpha: 0.5), bar: UIColor(rgb: 0xffffff), barIconForeground: .clear, barPositive: UIColor(rgb: 0xffffff), barNegative: UIColor(rgb: 0xffffff)), actionButtonsFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), actionButtonsStrokeColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xb2b2b2, alpha: 0.18)), actionButtonsTextColor: PresentationThemeVariableColor(color: UIColor(rgb: 0xffffff)), textSelectionColor: UIColor(rgb: 0xffffff, alpha: 0.2), textSelectionKnobColor: UIColor(rgb: 0xffffff)
|
||||
),
|
||||
freeform: PresentationThemeBubbleColor(
|
||||
withWallpaper: PresentationThemeBubbleColorComponents(
|
||||
|
||||
@ -373,7 +373,7 @@ public struct PresentationResourcesChat {
|
||||
|
||||
public static func chatInputMediaPanelGridDismissImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputMediaPanelGridDismissImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/GridDismissIcon"), color: theme.chat.inputMediaPanel.panelIconColor.withAlphaComponent(0.65))
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/GridDismissIcon"), color: theme.chat.inputMediaPanel.stickersSectionTextColor)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -110,18 +110,18 @@ public final class AudioTranscriptionButtonComponent: Component {
|
||||
component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: animationName,
|
||||
colors: [
|
||||
"icon.Group 3.Stroke 1": foregroundColor,
|
||||
"icon.Group 1.Stroke 1": foregroundColor,
|
||||
"icon.Group 4.Stroke 1": foregroundColor,
|
||||
"icon.Group 2.Stroke 1": foregroundColor,
|
||||
"Artboard Copy 2 Outlines.Group 5.Stroke 1": foregroundColor,
|
||||
"Artboard Copy 2 Outlines.Group 1.Stroke 1": foregroundColor,
|
||||
"Artboard Copy 2 Outlines.Group 4.Stroke 1": foregroundColor,
|
||||
"Artboard Copy Outlines.Group 1.Stroke 1": foregroundColor,
|
||||
],
|
||||
mode: .animateTransitionFromPrevious
|
||||
),
|
||||
colors: [
|
||||
"icon.Group 3.Stroke 1": foregroundColor,
|
||||
"icon.Group 1.Stroke 1": foregroundColor,
|
||||
"icon.Group 4.Stroke 1": foregroundColor,
|
||||
"icon.Group 2.Stroke 1": foregroundColor,
|
||||
"Artboard Copy 2 Outlines.Group 5.Stroke 1": foregroundColor,
|
||||
"Artboard Copy 2 Outlines.Group 1.Stroke 1": foregroundColor,
|
||||
"Artboard Copy 2 Outlines.Group 4.Stroke 1": foregroundColor,
|
||||
"Artboard Copy Outlines.Group 1.Stroke 1": foregroundColor,
|
||||
],
|
||||
size: CGSize(width: 30.0, height: 30.0)
|
||||
)),
|
||||
environment: {},
|
||||
@ -142,11 +142,11 @@ public final class AudioTranscriptionButtonComponent: Component {
|
||||
component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: "voicets_progress",
|
||||
colors: [
|
||||
"Rectangle 60.Rectangle 60.Stroke 1": foregroundColor
|
||||
],
|
||||
mode: .animating(loop: true)
|
||||
),
|
||||
colors: [
|
||||
"Rectangle 60.Rectangle 60.Stroke 1": foregroundColor
|
||||
],
|
||||
size: progressFrame.size
|
||||
)),
|
||||
environment: {},
|
||||
|
||||
@ -142,13 +142,13 @@ public final class AudioTranscriptionPendingLottieIndicatorComponent: Component
|
||||
component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: "animated_text_dots",
|
||||
colors: [
|
||||
"Comp 1.Point 3.Group 1.Fill 1": component.color,
|
||||
"Comp 1.Point 2.Group 1.Fill 1": component.color,
|
||||
"Comp 1.Point 1.Group 1.Fill 1": component.color
|
||||
],
|
||||
mode: .animating(loop: true)
|
||||
),
|
||||
colors: [
|
||||
"Comp 1.Point 3.Group 1.Fill 1": component.color,
|
||||
"Comp 1.Point 2.Group 1.Fill 1": component.color,
|
||||
"Comp 1.Point 1.Group 1.Fill 1": component.color
|
||||
],
|
||||
size: animationSize
|
||||
)),
|
||||
environment: {},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -13,11 +13,11 @@ import SwiftSignalKit
|
||||
|
||||
public final class EntityKeyboardChildEnvironment: Equatable {
|
||||
public let theme: PresentationTheme
|
||||
public let getContentActiveItemUpdated: (AnyHashable) -> ActionSlot<(AnyHashable, Transition)>?
|
||||
public let getContentActiveItemUpdated: (AnyHashable) -> ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
getContentActiveItemUpdated: @escaping (AnyHashable) -> ActionSlot<(AnyHashable, Transition)>?
|
||||
getContentActiveItemUpdated: @escaping (AnyHashable) -> ActionSlot<(AnyHashable, AnyHashable?, Transition)>?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.getContentActiveItemUpdated = getContentActiveItemUpdated
|
||||
@ -200,8 +200,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
var contentAccessoryLeftButtons: [AnyComponentWithIdentity<Empty>] = []
|
||||
var contentAccessoryRightButtons: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
||||
let gifsContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
let stickersContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
let gifsContentItemIdUpdated = ActionSlot<(AnyHashable, AnyHashable?, Transition)>()
|
||||
let stickersContentItemIdUpdated = ActionSlot<(AnyHashable, AnyHashable?, Transition)>()
|
||||
|
||||
if transition.userData(MarkInputCollapsed.self) != nil {
|
||||
self.searchComponent = nil
|
||||
@ -242,6 +242,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.emojiContent.context,
|
||||
file: emoji.file,
|
||||
isFeatured: false,
|
||||
isPremiumLocked: false,
|
||||
animationCache: component.emojiContent.animationCache,
|
||||
animationRenderer: component.emojiContent.animationRenderer,
|
||||
theme: component.theme,
|
||||
@ -293,8 +295,9 @@ public final class EntityKeyboardComponent: Component {
|
||||
let iconMapping: [String: String] = [
|
||||
"saved": "Chat/Input/Media/SavedStickersTabIcon",
|
||||
"recent": "Chat/Input/Media/RecentTabIcon",
|
||||
"premium": "Chat/Input/Media/PremiumIcon"
|
||||
"premium": "Peer Info/PremiumIcon"
|
||||
]
|
||||
//TODO:localize
|
||||
let titleMapping: [String: String] = [
|
||||
"saved": "Saved",
|
||||
"recent": "Recent",
|
||||
@ -323,6 +326,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: stickerContent.context,
|
||||
file: file,
|
||||
isFeatured: itemGroup.isFeatured,
|
||||
isPremiumLocked: itemGroup.isPremiumLocked,
|
||||
animationCache: stickerContent.animationCache,
|
||||
animationRenderer: stickerContent.animationRenderer,
|
||||
theme: component.theme,
|
||||
@ -375,18 +380,42 @@ public final class EntityKeyboardComponent: Component {
|
||||
).minSize(CGSize(width: 38.0, height: 38.0)))))
|
||||
}
|
||||
|
||||
let emojiContentItemIdUpdated = ActionSlot<(AnyHashable, Transition)>()
|
||||
let emojiContentItemIdUpdated = ActionSlot<(AnyHashable, AnyHashable?, Transition)>()
|
||||
contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(component.emojiContent)))
|
||||
var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
for itemGroup in component.emojiContent.itemGroups {
|
||||
if !itemGroup.items.isEmpty {
|
||||
if let id = itemGroup.groupId.base as? String {
|
||||
if id == "static" {
|
||||
if id == "recent" {
|
||||
let iconMapping: [String: String] = [
|
||||
"recent": "Chat/Input/Media/RecentTabIcon",
|
||||
]
|
||||
//TODO:localize
|
||||
let titleMapping: [String: String] = [
|
||||
"recent": "Recent",
|
||||
]
|
||||
if let iconName = iconMapping[id], let title = titleMapping[id] {
|
||||
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: itemGroup.supergroupId,
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: iconName,
|
||||
theme: component.theme,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "emoji", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
}
|
||||
))
|
||||
))
|
||||
}
|
||||
} else if id == "static" {
|
||||
//TODO:localize
|
||||
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: itemGroup.supergroupId,
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardStaticStickersPanelComponent(
|
||||
theme: component.theme,
|
||||
title: "Emoji",
|
||||
pressed: { [weak self] subgroupId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -404,6 +433,8 @@ public final class EntityKeyboardComponent: Component {
|
||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||
context: component.emojiContent.context,
|
||||
file: file,
|
||||
isFeatured: itemGroup.isFeatured,
|
||||
isPremiumLocked: itemGroup.isPremiumLocked,
|
||||
animationCache: component.emojiContent.animationCache,
|
||||
animationRenderer: component.emojiContent.animationRenderer,
|
||||
theme: component.theme,
|
||||
@ -651,9 +682,17 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
|
||||
private func scrollToItemGroup(contentId: String, groupId: AnyHashable, subgroupId: Int32?) {
|
||||
if let pagerView = self.pagerView.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: contentId)) as? EmojiPagerContentComponent.View {
|
||||
pagerView.scrollToItemGroup(id: groupId, subgroupId: subgroupId)
|
||||
guard let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View else {
|
||||
return
|
||||
}
|
||||
guard let pagerContentView = self.pagerView.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: contentId)) as? EmojiPagerContentComponent.View else {
|
||||
return
|
||||
}
|
||||
if let topPanelView = pagerView.topPanelComponentView as? EntityKeyboardTopContainerPanelComponent.View {
|
||||
topPanelView.internalUpdatePanelsAreCollapsed()
|
||||
}
|
||||
pagerContentView.scrollToItemGroup(id: groupId, subgroupId: subgroupId)
|
||||
pagerView.collapseTopPanel()
|
||||
}
|
||||
|
||||
private func reorderPacks(category: ReorderCategory, items: [EntityKeyboardTopPanelComponent.Item]) {
|
||||
|
||||
@ -91,8 +91,6 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
let intrinsicHeight: CGFloat = 41.0
|
||||
let height = intrinsicHeight
|
||||
|
||||
let isExpanded = availableSize.height > 41.0
|
||||
|
||||
let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value
|
||||
|
||||
var transitionOffsetFraction: CGFloat = 0.0
|
||||
@ -142,10 +140,6 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
self.addSubview(panelView.view)
|
||||
}
|
||||
|
||||
if !isExpanded {
|
||||
panelView.isExpanded = false
|
||||
}
|
||||
|
||||
let panelId = panel.id
|
||||
let _ = panelView.view.update(
|
||||
transition: panelTransition,
|
||||
@ -261,6 +255,12 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
|
||||
self.panelEnvironment?.isExpandedUpdated(hasExpanded, transition)
|
||||
}
|
||||
|
||||
public func internalUpdatePanelsAreCollapsed() {
|
||||
for (_, panelView) in self.panelViews {
|
||||
panelView.isExpanded = false
|
||||
}
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.alpha.isZero {
|
||||
return nil
|
||||
|
||||
@ -18,6 +18,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
|
||||
let context: AccountContext
|
||||
let file: TelegramMediaFile
|
||||
let isFeatured: Bool
|
||||
let isPremiumLocked: Bool
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
let theme: PresentationTheme
|
||||
@ -27,6 +29,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
init(
|
||||
context: AccountContext,
|
||||
file: TelegramMediaFile,
|
||||
isFeatured: Bool,
|
||||
isPremiumLocked: Bool,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
theme: PresentationTheme,
|
||||
@ -35,6 +39,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
self.isFeatured = isFeatured
|
||||
self.isPremiumLocked = isPremiumLocked
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.theme = theme
|
||||
@ -49,6 +55,12 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
if lhs.file.fileId != rhs.file.fileId {
|
||||
return false
|
||||
}
|
||||
if lhs.isFeatured != rhs.isFeatured {
|
||||
return false
|
||||
}
|
||||
if lhs.isPremiumLocked != rhs.isPremiumLocked {
|
||||
return false
|
||||
}
|
||||
if lhs.animationCache !== rhs.animationCache {
|
||||
return false
|
||||
}
|
||||
@ -107,7 +119,6 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
renderer: component.animationRenderer,
|
||||
placeholderColor: .lightGray,
|
||||
blurredBadgeColor: .clear,
|
||||
displayPremiumBadgeIfAvailable: false,
|
||||
pointSize: CGSize(width: 44.0, height: 44.0),
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
|
||||
guard let strongSelf = self else {
|
||||
@ -130,6 +141,15 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
if let itemLayer = self.itemLayer {
|
||||
transition.setPosition(layer: itemLayer, position: CGPoint(x: iconFrame.midX, y: iconFrame.midY))
|
||||
transition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||
|
||||
var badge: EmojiPagerContentComponent.View.ItemLayer.Badge?
|
||||
if component.isPremiumLocked {
|
||||
badge = .locked
|
||||
} else if component.isFeatured {
|
||||
badge = .featured
|
||||
}
|
||||
itemLayer.update(transition: transition, size: iconFrame.size, badge: badge, blurredBadgeColor: UIColor(white: 0.0, alpha: 0.1), blurredBadgeBackgroundColor: component.theme.list.plainBackgroundColor)
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
}
|
||||
|
||||
@ -144,7 +164,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
let titleSize = titleView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor))
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor)),
|
||||
insets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 62.0, height: 100.0)
|
||||
@ -154,7 +175,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
||||
view.alpha = 0.0
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height), size: titleSize)
|
||||
view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height - 1.0), size: titleSize)
|
||||
transition.setAlpha(view: view, alpha: 1.0)
|
||||
}
|
||||
} else if let titleView = self.titleView {
|
||||
@ -274,12 +295,35 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
|
||||
if self.component?.imageName != component.imageName {
|
||||
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: component.imageName), color: component.theme.chat.inputMediaPanel.panelIconColor)
|
||||
|
||||
if component.imageName.hasSuffix("PremiumIcon") {
|
||||
self.iconView.image = generateImage(CGSize(width: 44.0, height: 42.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
if let image = UIImage(bundleImageName: "Peer Info/PremiumIcon") {
|
||||
if let cgImage = image.cgImage {
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||
}
|
||||
|
||||
let colorsArray: [CGColor] = [
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x6B93FF).cgColor,
|
||||
UIColor(rgb: 0x976FFF).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor,
|
||||
UIColor(rgb: 0xE46ACE).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
let nativeIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 28.0, height: 28.0)
|
||||
let boundingIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 38.0, height: 38.0) : CGSize(width: 24.0, height: 24.0)
|
||||
let boundingIconSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 38.0, height: 38.0) : CGSize(width: 28.0, height: 28.0)
|
||||
|
||||
let iconSize = (self.iconView.image?.size ?? nativeIconSize).aspectFitted(boundingIconSize)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) / 2.0), y: floor((nativeIconSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
@ -297,7 +341,8 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
let titleSize = titleView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor))
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor)),
|
||||
insets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 62.0, height: 100.0)
|
||||
@ -307,7 +352,7 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
||||
view.alpha = 0.0
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height), size: titleSize)
|
||||
view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height - 1.0), size: titleSize)
|
||||
transition.setAlpha(view: view, alpha: 1.0)
|
||||
}
|
||||
} else if let titleView = self.titleView {
|
||||
@ -336,13 +381,16 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
typealias EnvironmentType = EntityKeyboardTopPanelItemEnvironment
|
||||
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let pressed: (EmojiPagerContentComponent.StaticEmojiSegment) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
pressed: @escaping (EmojiPagerContentComponent.StaticEmojiSegment) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.pressed = pressed
|
||||
}
|
||||
|
||||
@ -350,19 +398,29 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let scrollViewContainer: UIView
|
||||
private let scrollView: UIScrollView
|
||||
private var visibleItemViews: [EmojiPagerContentComponent.StaticEmojiSegment: ComponentView<Empty>] = [:]
|
||||
|
||||
private var titleView: ComponentView<Empty>?
|
||||
|
||||
private var component: EntityKeyboardStaticStickersPanelComponent?
|
||||
private var itemEnvironment: EntityKeyboardTopPanelItemEnvironment?
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollViewContainer = UIView()
|
||||
self.scrollViewContainer.clipsToBounds = true
|
||||
|
||||
self.scrollView = UIScrollView()
|
||||
|
||||
super.init(frame: frame)
|
||||
@ -380,9 +438,9 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.scrollViewContainer.addSubview(self.scrollView)
|
||||
self.addSubview(self.scrollViewContainer)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
@ -411,31 +469,36 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
}
|
||||
|
||||
private func updateVisibleItems(transition: Transition, animateAppearingItems: Bool) {
|
||||
guard let component = self.component else {
|
||||
guard let component = self.component, let itemEnvironment = self.itemEnvironment else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = component
|
||||
|
||||
var validItemIds = Set<EmojiPagerContentComponent.StaticEmojiSegment>()
|
||||
let visibleBounds = self.scrollView.bounds
|
||||
|
||||
let componentHeight: CGFloat = 32.0
|
||||
let componentHeight: CGFloat = self.scrollView.contentSize.height
|
||||
|
||||
let isExpanded = componentHeight > 32.0
|
||||
|
||||
let items = EmojiPagerContentComponent.StaticEmojiSegment.allCases
|
||||
let itemSize: CGFloat = 28.0
|
||||
let itemSize: CGFloat = isExpanded ? 42.0 : 32.0
|
||||
let itemSpacing: CGFloat = 4.0
|
||||
let sideInset: CGFloat = 2.0
|
||||
let sideInset: CGFloat = isExpanded ? 5.0 : 2.0
|
||||
let itemOffset: CGFloat = isExpanded ? -8.0 : 0.0
|
||||
for i in 0 ..< items.count {
|
||||
let itemFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(i) * (itemSize + itemSpacing), y: floor(componentHeight - itemSize) / 2.0), size: CGSize(width: itemSize, height: itemSize))
|
||||
let itemFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(i) * (itemSize + itemSpacing), y: floor(componentHeight - itemSize) / 2.0 + itemOffset), size: CGSize(width: itemSize, height: itemSize))
|
||||
if visibleBounds.intersects(itemFrame) {
|
||||
let item = items[i]
|
||||
validItemIds.insert(item)
|
||||
|
||||
var animateItem = false
|
||||
var itemTransition = transition
|
||||
let itemView: ComponentView<Empty>
|
||||
if let current = self.visibleItemViews[item] {
|
||||
itemView = current
|
||||
} else {
|
||||
animateItem = animateAppearingItems
|
||||
itemTransition = .immediate
|
||||
itemView = ComponentView<Empty>()
|
||||
self.visibleItemViews[item] = itemView
|
||||
}
|
||||
@ -460,14 +523,21 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
animationName = "emojicat_flags"
|
||||
}
|
||||
|
||||
let color: UIColor
|
||||
if itemEnvironment.highlightedSubgroupId == AnyHashable(items[i].rawValue) {
|
||||
color = component.theme.chat.inputMediaPanel.panelIconColor.mixedWith(component.theme.chat.inputPanel.primaryTextColor, alpha: 0.35)
|
||||
} else {
|
||||
color = component.theme.chat.inputMediaPanel.panelIconColor
|
||||
}
|
||||
|
||||
let _ = itemView.update(
|
||||
transition: .immediate,
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: animationName,
|
||||
colors: ["__allcolors__": component.theme.chat.inputMediaPanel.panelIconColor],
|
||||
mode: animateAppearingItems ? .animating(loop: false) : .still(position: .end)
|
||||
mode: animateItem ? .animating(loop: false) : .still(position: .end)
|
||||
),
|
||||
colors: ["__allcolors__": color],
|
||||
size: CGSize(width: itemSize, height: itemSize)
|
||||
)),
|
||||
environment: {},
|
||||
@ -477,7 +547,7 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
if view.superview == nil {
|
||||
self.scrollView.addSubview(view)
|
||||
}
|
||||
view.frame = itemFrame
|
||||
itemTransition.setFrame(view: view, frame: itemFrame)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -495,26 +565,80 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
}
|
||||
|
||||
func update(component: EntityKeyboardStaticStickersPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.layer.cornerRadius = availableSize.height / 2.0
|
||||
transition.setFrame(view: self.scrollViewContainer, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
transition.setCornerRadius(layer: self.scrollViewContainer.layer, cornerRadius: min(availableSize.width / 2.0, availableSize.height / 2.0))
|
||||
|
||||
let itemEnvironment = environment[EntityKeyboardTopPanelItemEnvironment.self].value
|
||||
|
||||
self.component = component
|
||||
var scrollToItem: AnyHashable?
|
||||
if itemEnvironment.highlightedSubgroupId != self.itemEnvironment?.highlightedSubgroupId {
|
||||
scrollToItem = itemEnvironment.highlightedSubgroupId
|
||||
}
|
||||
|
||||
let itemSize: CGFloat = 28.0
|
||||
self.component = component
|
||||
self.itemEnvironment = itemEnvironment
|
||||
|
||||
let isExpanded = itemEnvironment.isExpanded
|
||||
let itemSize: CGFloat = isExpanded ? 42.0 : 32.0
|
||||
let itemSpacing: CGFloat = 4.0
|
||||
let sideInset: CGFloat = 2.0
|
||||
let sideInset: CGFloat = isExpanded ? 5.0 : 2.0
|
||||
let itemCount = EmojiPagerContentComponent.StaticEmojiSegment.allCases.count
|
||||
|
||||
self.ignoreScrolling = true
|
||||
self.scrollView.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(availableSize.width, 150.0), height: availableSize.height))
|
||||
self.scrollView.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(availableSize.width, 160.0), height: availableSize.height))
|
||||
self.scrollView.contentSize = CGSize(width: sideInset * 2.0 + itemSize * CGFloat(itemCount) + itemSpacing * CGFloat(itemCount - 1), height: availableSize.height)
|
||||
self.ignoreScrolling = false
|
||||
|
||||
self.updateVisibleItems(transition: .immediate, animateAppearingItems: false)
|
||||
self.updateVisibleItems(transition: transition, animateAppearingItems: false)
|
||||
|
||||
if !itemEnvironment.isHighlighted && self.scrollView.contentOffset.x != 0.0 {
|
||||
if (!itemEnvironment.isHighlighted || isExpanded) && self.scrollView.contentOffset.x != 0.0 {
|
||||
self.scrollView.setContentOffset(CGPoint(), animated: true)
|
||||
scrollToItem = nil
|
||||
}
|
||||
|
||||
if let scrollToItem = scrollToItem {
|
||||
let items = EmojiPagerContentComponent.StaticEmojiSegment.allCases
|
||||
for i in 0 ..< items.count {
|
||||
if AnyHashable(items[i].rawValue) == scrollToItem {
|
||||
let itemFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(i) * (itemSize + itemSpacing), y: 0.0), size: CGSize(width: itemSize, height: itemSize))
|
||||
self.scrollView.scrollRectToVisible(itemFrame.insetBy(dx: -sideInset, dy: 0.0), animated: true)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if itemEnvironment.isExpanded {
|
||||
let titleView: ComponentView<Empty>
|
||||
if let current = self.titleView {
|
||||
titleView = current
|
||||
} else {
|
||||
titleView = ComponentView<Empty>()
|
||||
self.titleView = titleView
|
||||
}
|
||||
let titleSize = titleView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor)),
|
||||
insets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 62.0, height: 100.0)
|
||||
)
|
||||
if let view = titleView.view {
|
||||
if view.superview == nil {
|
||||
view.alpha = 0.0
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height - 4.0), size: titleSize)
|
||||
transition.setAlpha(view: view, alpha: 1.0)
|
||||
}
|
||||
} else if let titleView = self.titleView {
|
||||
self.titleView = nil
|
||||
if let view = titleView.view {
|
||||
transition.setAlpha(view: view, alpha: 0.0, completion: { [weak view] _ in
|
||||
view?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
@ -533,10 +657,12 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
final class EntityKeyboardTopPanelItemEnvironment: Equatable {
|
||||
let isExpanded: Bool
|
||||
let isHighlighted: Bool
|
||||
let highlightedSubgroupId: AnyHashable?
|
||||
|
||||
init(isExpanded: Bool, isHighlighted: Bool) {
|
||||
init(isExpanded: Bool, isHighlighted: Bool, highlightedSubgroupId: AnyHashable?) {
|
||||
self.isExpanded = isExpanded
|
||||
self.isHighlighted = isHighlighted
|
||||
self.highlightedSubgroupId = highlightedSubgroupId
|
||||
}
|
||||
|
||||
static func ==(lhs: EntityKeyboardTopPanelItemEnvironment, rhs: EntityKeyboardTopPanelItemEnvironment) -> Bool {
|
||||
@ -546,6 +672,9 @@ final class EntityKeyboardTopPanelItemEnvironment: Equatable {
|
||||
if lhs.isHighlighted != rhs.isHighlighted {
|
||||
return false
|
||||
}
|
||||
if lhs.highlightedSubgroupId != rhs.highlightedSubgroupId {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -617,6 +746,8 @@ private final class ReorderGestureRecognizer: UIGestureRecognizer {
|
||||
self.stopLongTapTimer()
|
||||
self.stopLongPressTimer()
|
||||
self.initialLocation = nil
|
||||
|
||||
self.isActiveUpdated(false)
|
||||
}
|
||||
|
||||
private func longTapTimerFired() {
|
||||
@ -776,14 +907,14 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let items: [Item]
|
||||
let defaultActiveItemId: AnyHashable?
|
||||
let activeContentItemIdUpdated: ActionSlot<(AnyHashable, Transition)>
|
||||
let activeContentItemIdUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>
|
||||
let reorderItems: ([Item]) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
items: [Item],
|
||||
defaultActiveItemId: AnyHashable? = nil,
|
||||
activeContentItemIdUpdated: ActionSlot<(AnyHashable, Transition)>,
|
||||
activeContentItemIdUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>,
|
||||
reorderItems: @escaping ([Item]) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
@ -836,7 +967,7 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
self.isExpanded = isExpanded
|
||||
self.itemSize = self.isExpanded ? CGSize(width: 54.0, height: 68.0) : CGSize(width: 32.0, height: 32.0)
|
||||
self.staticItemSize = self.itemSize
|
||||
self.staticExpandedItemSize = self.isExpanded ? CGSize(width: 150.0, height: 68.0) : CGSize(width: 150.0, height: 32.0)
|
||||
self.staticExpandedItemSize = self.isExpanded ? self.staticItemSize : CGSize(width: 160.0, height: 32.0)
|
||||
self.innerItemSize = self.isExpanded ? CGSize(width: 50.0, height: 62.0) : CGSize(width: 28.0, height: 28.0)
|
||||
|
||||
var contentSize = CGSize(width: sideInset, height: height)
|
||||
@ -887,11 +1018,19 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
return self.items[index].frame
|
||||
}
|
||||
|
||||
func contentFrame(containerFrame: CGRect) -> CGRect {
|
||||
func contentFrame(index: Int, containerFrame: CGRect) -> CGRect {
|
||||
let outerFrame = self.items[index].frame
|
||||
let innerFrame = self.items[index].innerFrame
|
||||
|
||||
let sizeDifference = CGSize(width: outerFrame.width - innerFrame.width, height: outerFrame.height - innerFrame.height)
|
||||
let offsetDifference = CGPoint(x: outerFrame.minX - innerFrame.minX, y: outerFrame.minY - innerFrame.minY)
|
||||
|
||||
var frame = containerFrame
|
||||
frame.origin.x += floor((self.itemSize.width - self.innerItemSize.width)) / 2.0
|
||||
frame.origin.y += floor((self.itemSize.height - self.innerItemSize.height)) / 2.0
|
||||
frame.size = self.innerItemSize
|
||||
frame.origin.x -= offsetDifference.x
|
||||
frame.origin.y -= offsetDifference.y
|
||||
frame.size.width -= sizeDifference.width
|
||||
frame.size.height -= sizeDifference.height
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
@ -933,12 +1072,17 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
private var isReordering: Bool = false
|
||||
private var isDraggingOrReordering: Bool = false
|
||||
private var draggingStoppedTimer: SwiftSignalKit.Timer?
|
||||
private var draggingFocusItemIndex: Int?
|
||||
private var draggingEndOffset: CGFloat?
|
||||
|
||||
private var isExpanded: Bool = false
|
||||
|
||||
private var visibilityFraction: CGFloat = 1.0
|
||||
|
||||
private var activeContentItemId: AnyHashable?
|
||||
private var activeSubcontentItemId: AnyHashable?
|
||||
|
||||
private var reorderGestureRecognizer: ReorderGestureRecognizer?
|
||||
|
||||
private var component: EntityKeyboardTopPanelComponent?
|
||||
weak var state: EmptyComponentState?
|
||||
@ -980,7 +1124,7 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
return strongSelf.scrollView.contentOffset.x > 0.0
|
||||
}
|
||||
|
||||
self.addGestureRecognizer(ReorderGestureRecognizer(
|
||||
let reorderGestureRecognizer = ReorderGestureRecognizer(
|
||||
shouldBegin: { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
return (false, false, nil)
|
||||
@ -1022,7 +1166,9 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
strongSelf.updateIsReordering(isActive)
|
||||
}
|
||||
))
|
||||
)
|
||||
self.reorderGestureRecognizer = reorderGestureRecognizer
|
||||
self.addGestureRecognizer(reorderGestureRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -1038,10 +1184,60 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
self.draggingEndOffset = nil
|
||||
|
||||
if let component = self.component {
|
||||
var focusItemIndex: Int?
|
||||
|
||||
var location = self.scrollView.panGestureRecognizer.location(in: self.scrollView)
|
||||
let translation = self.scrollView.panGestureRecognizer.translation(in: self.scrollView)
|
||||
location.x -= translation.x
|
||||
location.y -= translation.y
|
||||
|
||||
for (id, itemView) in self.itemViews {
|
||||
if itemView.frame.insetBy(dx: -4.0, dy: -4.0).contains(location) {
|
||||
inner: for i in 0 ..< component.items.count {
|
||||
if id == component.items[i].id {
|
||||
focusItemIndex = i
|
||||
break inner
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.draggingFocusItemIndex = focusItemIndex
|
||||
}
|
||||
|
||||
self.updateIsDragging(true)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
self.draggingEndOffset = scrollView.contentOffset.x
|
||||
|
||||
if let component = self.component {
|
||||
var focusItemIndex: Int?
|
||||
|
||||
var location = self.scrollView.panGestureRecognizer.location(in: self.scrollView)
|
||||
let translation = self.scrollView.panGestureRecognizer.translation(in: self.scrollView)
|
||||
location.x -= translation.x
|
||||
location.y -= translation.y
|
||||
|
||||
for (id, itemView) in self.itemViews {
|
||||
if itemView.frame.insetBy(dx: -4.0, dy: -4.0).contains(location) {
|
||||
inner: for i in 0 ..< component.items.count {
|
||||
if id == component.items[i].id {
|
||||
focusItemIndex = i
|
||||
break inner
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.draggingFocusItemIndex = focusItemIndex
|
||||
}
|
||||
|
||||
if !decelerate {
|
||||
self.updateIsDragging(false)
|
||||
}
|
||||
@ -1187,7 +1383,8 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
|
||||
var visibleBounds = self.scrollView.bounds
|
||||
visibleBounds.size.width += 200.0
|
||||
visibleBounds.origin.x -= 200.0
|
||||
visibleBounds.size.width += 400.0
|
||||
|
||||
var validIds = Set<AnyHashable>()
|
||||
let visibleItemRange = itemLayout.visibleItemRange(for: visibleBounds)
|
||||
@ -1212,7 +1409,7 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
transition: itemTransition,
|
||||
component: item.content,
|
||||
environment: {
|
||||
EntityKeyboardTopPanelItemEnvironment(isExpanded: itemLayout.isExpanded, isHighlighted: self.activeContentItemId == item.id)
|
||||
EntityKeyboardTopPanelItemEnvironment(isExpanded: itemLayout.isExpanded, isHighlighted: self.activeContentItemId == item.id, highlightedSubgroupId: self.activeContentItemId == item.id ? self.activeSubcontentItemId : nil)
|
||||
},
|
||||
containerSize: itemOuterFrame.size
|
||||
)
|
||||
@ -1259,6 +1456,7 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
if self.isReordering {
|
||||
self.isReordering = false
|
||||
self.reorderGestureRecognizer?.state = .failed
|
||||
}
|
||||
if self.isDraggingOrReordering {
|
||||
self.isDraggingOrReordering = false
|
||||
@ -1302,35 +1500,124 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
|
||||
var updatedBounds: CGRect?
|
||||
if wasExpanded != isExpanded, let previousItemLayout = previousItemLayout {
|
||||
if !isExpanded {
|
||||
if let draggingEndOffset = self.draggingEndOffset {
|
||||
if abs(self.scrollView.contentOffset.x - draggingEndOffset) > 16.0 {
|
||||
self.draggingFocusItemIndex = nil
|
||||
}
|
||||
} else {
|
||||
self.draggingFocusItemIndex = nil
|
||||
}
|
||||
}
|
||||
|
||||
var visibleBounds = self.scrollView.bounds
|
||||
visibleBounds.size.width += 200.0
|
||||
visibleBounds.origin.x -= 200.0
|
||||
visibleBounds.size.width += 400.0
|
||||
|
||||
let previousVisibleRange = previousItemLayout.visibleItemRange(for: visibleBounds)
|
||||
if previousVisibleRange.minIndex <= previousVisibleRange.maxIndex {
|
||||
let previousItemFrame = previousItemLayout.containerFrame(at: previousVisibleRange.minIndex)
|
||||
let updatedItemFrame = itemLayout.containerFrame(at: previousVisibleRange.minIndex)
|
||||
var itemIndex = self.draggingFocusItemIndex ?? ((previousVisibleRange.minIndex + previousVisibleRange.maxIndex) / 2)
|
||||
if !isExpanded {
|
||||
if self.scrollView.bounds.maxX >= self.scrollView.contentSize.width {
|
||||
itemIndex = component.items.count - 1
|
||||
}
|
||||
if self.scrollView.bounds.minX <= 0.0 {
|
||||
itemIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
var previousItemFrame = previousItemLayout.containerFrame(at: itemIndex)
|
||||
var updatedItemFrame = itemLayout.containerFrame(at: itemIndex)
|
||||
|
||||
let previousDistanceToItem = (previousItemFrame.minX - self.scrollView.bounds.minX)
|
||||
let previousDistanceToItemRight = (previousItemFrame.maxX - self.scrollView.bounds.maxX)
|
||||
var newBounds = CGRect(origin: CGPoint(x: updatedItemFrame.minX - previousDistanceToItem, y: 0.0), size: availableSize)
|
||||
var useRightAnchor = false
|
||||
if newBounds.minX > itemLayout.contentSize.width - self.scrollView.bounds.width {
|
||||
newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width
|
||||
itemIndex = component.items.count - 1
|
||||
useRightAnchor = true
|
||||
}
|
||||
if newBounds.minX < 0.0 {
|
||||
newBounds.origin.x = 0.0
|
||||
itemIndex = 0
|
||||
useRightAnchor = false
|
||||
}
|
||||
|
||||
if useRightAnchor {
|
||||
var newBounds = CGRect(origin: CGPoint(x: updatedItemFrame.maxX - previousDistanceToItemRight, y: 0.0), size: availableSize)
|
||||
if newBounds.minX > itemLayout.contentSize.width - self.scrollView.bounds.width {
|
||||
newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width
|
||||
}
|
||||
if newBounds.minX < 0.0 {
|
||||
}
|
||||
}
|
||||
|
||||
previousItemFrame = previousItemLayout.containerFrame(at: itemIndex)
|
||||
updatedItemFrame = itemLayout.containerFrame(at: itemIndex)
|
||||
|
||||
self.draggingFocusItemIndex = itemIndex
|
||||
|
||||
let previousDistanceToItem = (previousItemFrame.minX - self.scrollView.bounds.minX)// / previousItemFrame.width
|
||||
let newBounds = CGRect(origin: CGPoint(x: updatedItemFrame.minX - previousDistanceToItem/* * updatedItemFrame.width)*/, y: 0.0), size: availableSize)
|
||||
updatedBounds = newBounds
|
||||
|
||||
var updatedVisibleBounds = newBounds
|
||||
updatedVisibleBounds.size.width += 200.0
|
||||
updatedVisibleBounds.origin.x -= 200.0
|
||||
updatedVisibleBounds.size.width += 400.0
|
||||
let updatedVisibleRange = itemLayout.visibleItemRange(for: updatedVisibleBounds)
|
||||
|
||||
let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.minX, y: previousItemFrame.minY), size: previousItemFrame.size)
|
||||
for index in updatedVisibleRange.minIndex ..< updatedVisibleRange.maxIndex {
|
||||
let indexDifference = index - previousVisibleRange.minIndex
|
||||
if let itemView = self.itemViews[self.items[index].id] {
|
||||
let itemContainerOriginX = baseFrame.minX + CGFloat(indexDifference) * (previousItemLayout.itemSize.width + previousItemLayout.itemSpacing)
|
||||
let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerOriginX, y: baseFrame.minY), size: baseFrame.size)
|
||||
let itemOuterFrame = previousItemLayout.contentFrame(containerFrame: itemContainerFrame)
|
||||
|
||||
let itemSize = itemView.bounds.size
|
||||
itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||
if useRightAnchor {
|
||||
let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.maxX - previousItemFrame.width, y: previousItemFrame.minY), size: previousItemFrame.size)
|
||||
for index in updatedVisibleRange.minIndex ... updatedVisibleRange.maxIndex {
|
||||
let indexDifference = index - itemIndex
|
||||
if let itemView = self.itemViews[self.items[index].id] {
|
||||
let itemContainerMaxX = baseFrame.maxX + CGFloat(indexDifference) * (previousItemLayout.itemSize.width + previousItemLayout.itemSpacing)
|
||||
let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerMaxX - baseFrame.width, y: baseFrame.minY), size: baseFrame.size)
|
||||
let itemOuterFrame = previousItemLayout.contentFrame(index: index, containerFrame: itemContainerFrame)
|
||||
|
||||
let itemSize = itemView.bounds.size
|
||||
itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||
|
||||
if let activeContentItemId = self.activeContentItemId, activeContentItemId == self.items[index].id {
|
||||
self.highlightedIconBackgroundView.frame = itemOuterFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.minX, y: previousItemFrame.minY), size: previousItemFrame.size)
|
||||
for index in updatedVisibleRange.minIndex ... updatedVisibleRange.maxIndex {
|
||||
let indexDifference = index - itemIndex
|
||||
if let itemView = self.itemViews[self.items[index].id] {
|
||||
var itemContainerOriginX = baseFrame.minX
|
||||
if indexDifference > 0 {
|
||||
for i in 0 ..< indexDifference {
|
||||
itemContainerOriginX += previousItemLayout.itemSpacing
|
||||
itemContainerOriginX += previousItemLayout.containerFrame(at: itemIndex + i).width
|
||||
}
|
||||
} else if indexDifference < 0 {
|
||||
for i in 0 ..< (-indexDifference) {
|
||||
itemContainerOriginX -= previousItemLayout.itemSpacing
|
||||
itemContainerOriginX -= previousItemLayout.containerFrame(at: itemIndex - i - 1).width
|
||||
}
|
||||
}
|
||||
|
||||
let previousContainerFrame = previousItemLayout.containerFrame(at: index)
|
||||
let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerOriginX, y: previousContainerFrame.minY), size: previousContainerFrame.size)
|
||||
let itemOuterFrame = previousItemLayout.contentFrame(index: index, containerFrame: itemContainerFrame)
|
||||
|
||||
let itemSize = itemView.bounds.size
|
||||
itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||
|
||||
if let activeContentItemId = self.activeContentItemId, activeContentItemId == self.items[index].id {
|
||||
self.highlightedIconBackgroundView.frame = itemOuterFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isExpanded {
|
||||
self.draggingFocusItemIndex = nil
|
||||
}
|
||||
}
|
||||
|
||||
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||
@ -1355,7 +1642,13 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
highlightTransition = .immediate
|
||||
}
|
||||
|
||||
highlightTransition.setCornerRadius(layer: self.highlightedIconBackgroundView.layer, cornerRadius: activeContentItemId.base is String ? min(itemFrame.width / 2.0, itemFrame.height / 2.0) : 10.0)
|
||||
let isRound: Bool
|
||||
if let string = activeContentItemId.base as? String, (string == "recent" || string == "static") {
|
||||
isRound = true
|
||||
} else {
|
||||
isRound = false
|
||||
}
|
||||
highlightTransition.setCornerRadius(layer: self.highlightedIconBackgroundView.layer, cornerRadius: isRound ? min(itemFrame.width / 2.0, itemFrame.height / 2.0) : 10.0)
|
||||
highlightTransition.setPosition(view: self.highlightedIconBackgroundView, position: CGPoint(x: itemFrame.midX, y: itemFrame.midY))
|
||||
highlightTransition.setBounds(view: self.highlightedIconBackgroundView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
} else {
|
||||
@ -1373,11 +1666,11 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
strongSelf.visibilityFractionUpdated(value: fraction, transition: transition)
|
||||
}
|
||||
|
||||
component.activeContentItemIdUpdated.connect { [weak self] (itemId, transition) in
|
||||
component.activeContentItemIdUpdated.connect { [weak self] (itemId, subcontentItemId, transition) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.activeContentItemIdUpdated(itemId: itemId, transition: transition)
|
||||
strongSelf.activeContentItemIdUpdated(itemId: itemId, subcontentItemId: subcontentItemId, transition: transition)
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
@ -1401,14 +1694,15 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func activeContentItemIdUpdated(itemId: AnyHashable, transition: Transition) {
|
||||
private func activeContentItemIdUpdated(itemId: AnyHashable, subcontentItemId: AnyHashable?, transition: Transition) {
|
||||
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
if self.activeContentItemId == itemId {
|
||||
if self.activeContentItemId == itemId && self.activeSubcontentItemId == subcontentItemId {
|
||||
return
|
||||
}
|
||||
self.activeContentItemId = itemId
|
||||
self.activeSubcontentItemId = subcontentItemId
|
||||
|
||||
let _ = component
|
||||
let _ = itemLayout
|
||||
|
||||
@ -42,12 +42,14 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer {
|
||||
|
||||
if self.shouldBeAnimating {
|
||||
self.playbackTimer?.invalidate()
|
||||
let startTimestamp = self.playbackTimestamp + CFAbsoluteTimeGetCurrent()
|
||||
self.playbackTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.frameManager?.tick(timestamp: strongSelf.playbackTimestamp)
|
||||
strongSelf.playbackTimestamp += 1.0 / 30.0
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() - startTimestamp
|
||||
strongSelf.frameManager?.tick(timestamp: timestamp)
|
||||
strongSelf.playbackTimestamp = timestamp
|
||||
}, queue: .mainQueue())
|
||||
self.playbackTimer?.start()
|
||||
} else {
|
||||
@ -560,7 +562,7 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
private func updateScrollingOffset(transition: Transition) {
|
||||
let isInteracting = scrollView.isDragging || scrollView.isTracking || scrollView.isDecelerating
|
||||
let isInteracting = scrollView.isDragging || scrollView.isDecelerating
|
||||
if let previousScrollingOffsetValue = self.previousScrollingOffset {
|
||||
let currentBounds = scrollView.bounds
|
||||
let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0)
|
||||
|
||||
@ -14,14 +14,27 @@ public func cacheLottieAnimation(data: Data, width: Int, height: Int, writer: An
|
||||
return
|
||||
}
|
||||
|
||||
let frameDuration = 1.0 / Double(animation.frameRate)
|
||||
for i in 0 ..< animation.frameCount {
|
||||
|
||||
let frameSkip: Int
|
||||
if animation.frameRate >= 60 {
|
||||
if ProcessInfo.processInfo.activeProcessorCount > 2 {
|
||||
frameSkip = 1
|
||||
} else {
|
||||
frameSkip = 2
|
||||
}
|
||||
} else {
|
||||
frameSkip = 1
|
||||
}
|
||||
|
||||
let frameDuration = Double(frameSkip) / Double(animation.frameRate)
|
||||
for i in stride(from: 0, through: animation.frameCount - 1, by: frameSkip) {
|
||||
if writer.isCancelled {
|
||||
break
|
||||
}
|
||||
writer.add(with: { surface in
|
||||
animation.renderFrame(with: i, into: surface.argb, width: Int32(surface.width), height: Int32(surface.height), bytesPerRow: Int32(surface.bytesPerRow))
|
||||
}, proposedWidth: width, proposedHeight: height, duration: frameDuration)
|
||||
return frameDuration
|
||||
}, proposedWidth: width, proposedHeight: height)
|
||||
}
|
||||
|
||||
writer.finish()
|
||||
@ -39,7 +52,8 @@ public func cacheStillSticker(path: String, width: Int, height: Int, writer: Ani
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
memcpy(surface.argb, context.bytes, surface.height * surface.bytesPerRow)
|
||||
}, proposedWidth: width, proposedHeight: height, duration: 1.0)
|
||||
return 1.0
|
||||
}, proposedWidth: width, proposedHeight: height)
|
||||
}
|
||||
|
||||
writer.finish()
|
||||
|
||||
@ -249,14 +249,18 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
|
||||
private final class Frame {
|
||||
let timestamp: Double
|
||||
let duration: Double
|
||||
let textureY: TextureStorage
|
||||
let textureU: TextureStorage
|
||||
let textureV: TextureStorage
|
||||
let textureA: TextureStorage
|
||||
|
||||
init?(device: MTLDevice, textureY: TextureStorage, textureU: TextureStorage, textureV: TextureStorage, textureA: TextureStorage, data: AnimationCacheItemFrame, timestamp: Double) {
|
||||
self.timestamp = timestamp
|
||||
var remainingDuration: Double
|
||||
|
||||
init?(device: MTLDevice, textureY: TextureStorage, textureU: TextureStorage, textureV: TextureStorage, textureA: TextureStorage, data: AnimationCacheItemFrame, duration: Double) {
|
||||
self.duration = duration
|
||||
self.remainingDuration = duration
|
||||
|
||||
self.textureY = textureY
|
||||
self.textureU = textureU
|
||||
self.textureV = textureV
|
||||
@ -281,7 +285,6 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
private let stateUpdated: () -> Void
|
||||
|
||||
private var disposable: Disposable?
|
||||
private var timestamp: Double = 0.0
|
||||
private var item: AnimationCacheItem?
|
||||
|
||||
private(set) var currentFrame: Frame?
|
||||
@ -354,18 +357,35 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
return self.update(device: device, texturePoolFullPlane: texturePoolFullPlane, texturePoolHalfPlane: texturePoolHalfPlane, advanceTimestamp: advanceTimestamp)
|
||||
}
|
||||
|
||||
private func update(device: MTLDevice, texturePoolFullPlane: TextureStoragePool, texturePoolHalfPlane: TextureStoragePool, advanceTimestamp: Double?) -> LoadFrameTask? {
|
||||
private func update(device: MTLDevice, texturePoolFullPlane: TextureStoragePool, texturePoolHalfPlane: TextureStoragePool, advanceTimestamp: Double) -> LoadFrameTask? {
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let timestamp = self.timestamp
|
||||
if let advanceTimestamp = advanceTimestamp {
|
||||
self.timestamp += advanceTimestamp
|
||||
if let currentFrame = self.currentFrame, !self.isLoadingFrame {
|
||||
currentFrame.remainingDuration -= advanceTimestamp
|
||||
}
|
||||
|
||||
if let currentFrame = self.currentFrame, currentFrame.timestamp == self.timestamp {
|
||||
} else if !self.isLoadingFrame {
|
||||
var frameAdvance: AnimationCacheItem.Advance?
|
||||
if !self.isLoadingFrame {
|
||||
if let currentFrame = self.currentFrame, advanceTimestamp > 0.0 {
|
||||
let divisionFactor = advanceTimestamp / currentFrame.remainingDuration
|
||||
let wholeFactor = round(divisionFactor)
|
||||
if abs(wholeFactor - divisionFactor) < 0.005 {
|
||||
currentFrame.remainingDuration = 0.0
|
||||
frameAdvance = .frames(Int(wholeFactor))
|
||||
} else {
|
||||
currentFrame.remainingDuration -= advanceTimestamp
|
||||
if currentFrame.remainingDuration <= 0.0 {
|
||||
frameAdvance = .duration(currentFrame.duration + max(0.0, -currentFrame.remainingDuration))
|
||||
}
|
||||
}
|
||||
} else if self.currentFrame == nil {
|
||||
frameAdvance = .frames(1)
|
||||
}
|
||||
}
|
||||
|
||||
if let frameAdvance = frameAdvance, !self.isLoadingFrame {
|
||||
self.isLoadingFrame = true
|
||||
|
||||
let fullParameters = texturePoolFullPlane.parameters
|
||||
@ -378,7 +398,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
let preferredRowAlignment = self.preferredRowAlignment
|
||||
|
||||
return LoadFrameTask(task: { [weak self] in
|
||||
let frame = item.getFrame(at: timestamp, requestedFormat: .yuva(rowAlignment: preferredRowAlignment))
|
||||
let frame = item.advance(advance: frameAdvance, requestedFormat: .yuva(rowAlignment: preferredRowAlignment))
|
||||
|
||||
let textureY = readyTextureY ?? TextureStoragePool.takeNew(device: device, parameters: fullParameters, pool: texturePoolFullPlane)
|
||||
let textureU = readyTextureU ?? TextureStoragePool.takeNew(device: device, parameters: halfParameters, pool: texturePoolHalfPlane)
|
||||
@ -387,7 +407,7 @@ public final class MultiAnimationMetalRendererImpl: MultiAnimationRenderer {
|
||||
|
||||
var currentFrame: Frame?
|
||||
if let frame = frame, let textureY = textureY, let textureU = textureU, let textureV = textureV, let textureA = textureA {
|
||||
currentFrame = Frame(device: device, textureY: textureY, textureU: textureU, textureV: textureV, textureA: textureA, data: frame, timestamp: timestamp)
|
||||
currentFrame = Frame(device: device, textureY: textureY, textureU: textureU, textureV: textureV, textureA: textureA, data: frame, duration: frame.duration)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -29,6 +29,17 @@ open class MultiAnimationRenderTarget: SimpleLayer {
|
||||
}
|
||||
}
|
||||
|
||||
public var blurredRepresentationBackgroundColor: UIColor?
|
||||
public var blurredRepresentationTarget: CALayer? {
|
||||
didSet {
|
||||
if self.blurredRepresentationTarget !== oldValue {
|
||||
for f in self.updateStateCallbacks.copyItems() {
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override init() {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
@ -65,60 +76,6 @@ open class MultiAnimationRenderTarget: SimpleLayer {
|
||||
}
|
||||
}
|
||||
|
||||
private final class FrameGroup {
|
||||
let image: UIImage
|
||||
let badgeImage: UIImage?
|
||||
let size: CGSize
|
||||
let timestamp: Double
|
||||
|
||||
init?(item: AnimationCacheItem, timestamp: Double) {
|
||||
guard let firstFrame = item.getFrame(at: timestamp, requestedFormat: .rgba) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch firstFrame.format {
|
||||
case let .rgba(data, width, height, bytesPerRow):
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: false, bytesPerRow: bytesPerRow)
|
||||
|
||||
data.withUnsafeBytes { bytes -> Void in
|
||||
memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow)
|
||||
|
||||
/*var sourceBuffer = vImage_Buffer()
|
||||
sourceBuffer.width = UInt(width)
|
||||
sourceBuffer.height = UInt(height)
|
||||
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!.advanced(by: firstFrame.range.lowerBound))
|
||||
sourceBuffer.rowBytes = bytesPerRow
|
||||
|
||||
var destinationBuffer = vImage_Buffer()
|
||||
destinationBuffer.width = UInt(32)
|
||||
destinationBuffer.height = UInt(32)
|
||||
destinationBuffer.data = context.bytes
|
||||
destinationBuffer.rowBytes = bytesPerRow
|
||||
|
||||
vImageBoxConvolve_ARGB8888(&sourceBuffer,
|
||||
&destinationBuffer,
|
||||
nil,
|
||||
UInt(width - 32 - 16), UInt(height - 32 - 16),
|
||||
UInt32(31),
|
||||
UInt32(31),
|
||||
nil,
|
||||
vImage_Flags(kvImageEdgeExtend))*/
|
||||
}
|
||||
|
||||
guard let image = context.generateImage() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.image = image
|
||||
self.size = CGSize(width: CGFloat(width), height: CGFloat(height))
|
||||
self.timestamp = timestamp
|
||||
self.badgeImage = nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoadFrameGroupTask {
|
||||
let task: () -> () -> Void
|
||||
|
||||
@ -128,6 +85,190 @@ private final class LoadFrameGroupTask {
|
||||
}
|
||||
|
||||
private final class ItemAnimationContext {
|
||||
fileprivate final class Frame {
|
||||
let frame: AnimationCacheItemFrame
|
||||
let duration: Double
|
||||
let image: UIImage
|
||||
let badgeImage: UIImage?
|
||||
let size: CGSize
|
||||
|
||||
var remainingDuration: Double
|
||||
|
||||
private var blurredRepresentationValue: UIImage?
|
||||
|
||||
init?(frame: AnimationCacheItemFrame) {
|
||||
self.frame = frame
|
||||
self.duration = frame.duration
|
||||
self.remainingDuration = frame.duration
|
||||
|
||||
switch frame.format {
|
||||
case let .rgba(data, width, height, bytesPerRow):
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: false, bytesPerRow: bytesPerRow)
|
||||
|
||||
data.withUnsafeBytes { bytes -> Void in
|
||||
memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow)
|
||||
|
||||
/*var sourceBuffer = vImage_Buffer()
|
||||
sourceBuffer.width = UInt(width)
|
||||
sourceBuffer.height = UInt(height)
|
||||
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!.advanced(by: firstFrame.range.lowerBound))
|
||||
sourceBuffer.rowBytes = bytesPerRow
|
||||
|
||||
var destinationBuffer = vImage_Buffer()
|
||||
destinationBuffer.width = UInt(32)
|
||||
destinationBuffer.height = UInt(32)
|
||||
destinationBuffer.data = context.bytes
|
||||
destinationBuffer.rowBytes = bytesPerRow
|
||||
|
||||
vImageBoxConvolve_ARGB8888(&sourceBuffer,
|
||||
&destinationBuffer,
|
||||
nil,
|
||||
UInt(width - 32 - 16), UInt(height - 32 - 16),
|
||||
UInt32(31),
|
||||
UInt32(31),
|
||||
nil,
|
||||
vImage_Flags(kvImageEdgeExtend))*/
|
||||
}
|
||||
|
||||
guard let image = context.generateImage() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.image = image
|
||||
self.size = CGSize(width: CGFloat(width), height: CGFloat(height))
|
||||
self.badgeImage = nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func blurredRepresentation(color: UIColor?) -> UIImage? {
|
||||
if let blurredRepresentationValue = self.blurredRepresentationValue {
|
||||
return blurredRepresentationValue
|
||||
}
|
||||
|
||||
switch frame.format {
|
||||
case let .rgba(data, width, height, bytesPerRow):
|
||||
let blurredWidth = 12
|
||||
let blurredHeight = 12
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true, bytesPerRow: bytesPerRow)
|
||||
|
||||
data.withUnsafeBytes { bytes -> Void in
|
||||
if let dataProvider = CGDataProvider(dataInfo: nil, data: bytes.baseAddress!, size: bytes.count, releaseData: { _, _, _ in }) {
|
||||
let image = CGImage(
|
||||
width: width,
|
||||
height: height,
|
||||
bitsPerComponent: 8,
|
||||
bitsPerPixel: 32,
|
||||
bytesPerRow: bytesPerRow,
|
||||
space: DeviceGraphicsContextSettings.shared.colorSpace,
|
||||
bitmapInfo: DeviceGraphicsContextSettings.shared.transparentBitmapInfo,
|
||||
provider: dataProvider,
|
||||
decode: nil,
|
||||
shouldInterpolate: true,
|
||||
intent: .defaultIntent
|
||||
)
|
||||
if let image = image {
|
||||
let size = CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight))
|
||||
context.withFlippedContext { c in
|
||||
c.setFillColor((color ?? .white).cgColor)
|
||||
c.fill(CGRect(origin: CGPoint(), size: size))
|
||||
c.draw(image, in: CGRect(origin: CGPoint(x: -size.width / 2.0, y: -size.height / 2.0), size: CGSize(width: size.width * 1.8, height: size.height * 1.8)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var destinationBuffer = vImage_Buffer()
|
||||
destinationBuffer.width = UInt(blurredWidth)
|
||||
destinationBuffer.height = UInt(blurredHeight)
|
||||
destinationBuffer.data = context.bytes
|
||||
destinationBuffer.rowBytes = context.bytesPerRow
|
||||
|
||||
/*var sourceBuffer = vImage_Buffer()
|
||||
sourceBuffer.width = UInt(width)
|
||||
sourceBuffer.height = UInt(height)
|
||||
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!)
|
||||
sourceBuffer.rowBytes = bytesPerRow
|
||||
|
||||
let tempBufferBytes = malloc(blurredHeight * context.bytesPerRow)
|
||||
defer {
|
||||
free(tempBufferBytes)
|
||||
}
|
||||
let temp2BufferBytes = malloc(blurredHeight * context.bytesPerRow)
|
||||
defer {
|
||||
free(temp2BufferBytes)
|
||||
}
|
||||
memset(temp2BufferBytes, Int32(bitPattern: color?.argb ?? 0xffffffff), blurredHeight * context.bytesPerRow)
|
||||
|
||||
var tempBuffer = vImage_Buffer()
|
||||
tempBuffer.width = UInt(blurredWidth)
|
||||
tempBuffer.height = UInt(blurredHeight)
|
||||
tempBuffer.data = tempBufferBytes
|
||||
tempBuffer.rowBytes = context.bytesPerRow
|
||||
|
||||
var temp2Buffer = vImage_Buffer()
|
||||
temp2Buffer.width = UInt(blurredWidth)
|
||||
temp2Buffer.height = UInt(blurredHeight)
|
||||
temp2Buffer.data = temp2BufferBytes
|
||||
temp2Buffer.rowBytes = context.bytesPerRow
|
||||
|
||||
|
||||
|
||||
vImageScale_ARGB8888(&sourceBuffer, &tempBuffer, nil, vImage_Flags(kvImageDoNotTile))
|
||||
//vImageUnpremultiplyData_ARGB8888(&tempBuffer, &tempBuffer, vImage_Flags(kvImageDoNotTile))
|
||||
|
||||
vImagePremultipliedAlphaBlend_ARGB8888(&tempBuffer, &temp2Buffer, &destinationBuffer, vImage_Flags(kvImageDoNotTile))
|
||||
//vImageCopyBuffer(&tempBuffer, &destinationBuffer, 4, vImage_Flags(kvImageDoNotTile))*/
|
||||
|
||||
vImageBoxConvolve_ARGB8888(&destinationBuffer,
|
||||
&destinationBuffer,
|
||||
nil,
|
||||
0, 0,
|
||||
UInt32(15),
|
||||
UInt32(15),
|
||||
nil,
|
||||
vImage_Flags(kvImageTruncateKernel))
|
||||
|
||||
let divisor: Int32 = 0x1000
|
||||
|
||||
let rwgt: CGFloat = 0.3086
|
||||
let gwgt: CGFloat = 0.6094
|
||||
let bwgt: CGFloat = 0.0820
|
||||
|
||||
let adjustSaturation: CGFloat = 1.7
|
||||
|
||||
let a = (1.0 - adjustSaturation) * rwgt + adjustSaturation
|
||||
let b = (1.0 - adjustSaturation) * rwgt
|
||||
let c = (1.0 - adjustSaturation) * rwgt
|
||||
let d = (1.0 - adjustSaturation) * gwgt
|
||||
let e = (1.0 - adjustSaturation) * gwgt + adjustSaturation
|
||||
let f = (1.0 - adjustSaturation) * gwgt
|
||||
let g = (1.0 - adjustSaturation) * bwgt
|
||||
let h = (1.0 - adjustSaturation) * bwgt
|
||||
let i = (1.0 - adjustSaturation) * bwgt + adjustSaturation
|
||||
|
||||
let satMatrix: [CGFloat] = [
|
||||
a, b, c, 0,
|
||||
d, e, f, 0,
|
||||
g, h, i, 0,
|
||||
0, 0, 0, 1
|
||||
]
|
||||
|
||||
var matrix: [Int16] = satMatrix.map { value in
|
||||
return Int16(value * CGFloat(divisor))
|
||||
}
|
||||
|
||||
vImageMatrixMultiply_ARGB8888(&destinationBuffer, &destinationBuffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile))
|
||||
}
|
||||
|
||||
self.blurredRepresentationValue = context.generateImage()
|
||||
return self.blurredRepresentationValue
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static let queue = Queue(name: "ItemAnimationContext", qos: .default)
|
||||
|
||||
private let cache: AnimationCache
|
||||
@ -135,11 +276,10 @@ private final class ItemAnimationContext {
|
||||
|
||||
private var disposable: Disposable?
|
||||
private var displayLink: ConstantDisplayLinkAnimator?
|
||||
private var timestamp: Double = 0.0
|
||||
private var item: AnimationCacheItem?
|
||||
|
||||
private var currentFrameGroup: FrameGroup?
|
||||
private var isLoadingFrameGroup: Bool = false
|
||||
private var currentFrame: Frame?
|
||||
private var isLoadingFrame: Bool = false
|
||||
|
||||
private(set) var isPlaying: Bool = false {
|
||||
didSet {
|
||||
@ -180,9 +320,13 @@ private final class ItemAnimationContext {
|
||||
}
|
||||
|
||||
func updateAddedTarget(target: MultiAnimationRenderTarget) {
|
||||
if let currentFrameGroup = self.currentFrameGroup {
|
||||
if let cgImage = currentFrameGroup.image.cgImage {
|
||||
if let currentFrame = self.currentFrame {
|
||||
if let cgImage = currentFrame.image.cgImage {
|
||||
target.transitionToContents(cgImage)
|
||||
|
||||
if let blurredRepresentationTarget = target.blurredRepresentationTarget {
|
||||
blurredRepresentationTarget.contents = currentFrame.blurredRepresentation(color: target.blurredRepresentationBackgroundColor)?.cgImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,35 +359,57 @@ private final class ItemAnimationContext {
|
||||
return self.update(advanceTimestamp: advanceTimestamp)
|
||||
}
|
||||
|
||||
private func update(advanceTimestamp: Double?) -> LoadFrameGroupTask? {
|
||||
private func update(advanceTimestamp: Double) -> LoadFrameGroupTask? {
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let timestamp = self.timestamp
|
||||
if let advanceTimestamp = advanceTimestamp {
|
||||
self.timestamp += advanceTimestamp
|
||||
var frameAdvance: AnimationCacheItem.Advance?
|
||||
if !self.isLoadingFrame {
|
||||
if let currentFrame = self.currentFrame, advanceTimestamp > 0.0 {
|
||||
let divisionFactor = advanceTimestamp / currentFrame.remainingDuration
|
||||
let wholeFactor = round(divisionFactor)
|
||||
if abs(wholeFactor - divisionFactor) < 0.005 {
|
||||
currentFrame.remainingDuration = 0.0
|
||||
frameAdvance = .frames(Int(wholeFactor))
|
||||
} else {
|
||||
currentFrame.remainingDuration -= advanceTimestamp
|
||||
if currentFrame.remainingDuration <= 0.0 {
|
||||
frameAdvance = .duration(currentFrame.duration + max(0.0, -currentFrame.remainingDuration))
|
||||
}
|
||||
}
|
||||
} else if self.currentFrame == nil {
|
||||
frameAdvance = .frames(1)
|
||||
}
|
||||
}
|
||||
|
||||
if let currentFrameGroup = self.currentFrameGroup, currentFrameGroup.timestamp == self.timestamp {
|
||||
} else if !self.isLoadingFrameGroup {
|
||||
self.isLoadingFrameGroup = true
|
||||
if let frameAdvance = frameAdvance, !self.isLoadingFrame {
|
||||
self.isLoadingFrame = true
|
||||
|
||||
return LoadFrameGroupTask(task: { [weak self] in
|
||||
let currentFrameGroup = FrameGroup(item: item, timestamp: timestamp)
|
||||
let currentFrame: Frame?
|
||||
if let frame = item.advance(advance: frameAdvance, requestedFormat: .rgba) {
|
||||
currentFrame = Frame(frame: frame)
|
||||
} else {
|
||||
currentFrame = nil
|
||||
}
|
||||
|
||||
return {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.isLoadingFrameGroup = false
|
||||
strongSelf.isLoadingFrame = false
|
||||
|
||||
if let currentFrameGroup = currentFrameGroup {
|
||||
strongSelf.currentFrameGroup = currentFrameGroup
|
||||
if let currentFrame = currentFrame {
|
||||
strongSelf.currentFrame = currentFrame
|
||||
for target in strongSelf.targets.copyItems() {
|
||||
if let target = target.value {
|
||||
target.transitionToContents(currentFrameGroup.image.cgImage!)
|
||||
target.transitionToContents(currentFrame.image.cgImage!)
|
||||
|
||||
if let blurredRepresentationTarget = target.blurredRepresentationTarget {
|
||||
blurredRepresentationTarget.contents = currentFrame.blurredRepresentation(color: target.blurredRepresentationBackgroundColor)?.cgImage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -251,7 +417,7 @@ private final class ItemAnimationContext {
|
||||
})
|
||||
}
|
||||
|
||||
if let _ = self.currentFrameGroup {
|
||||
if let _ = self.currentFrame {
|
||||
for target in self.targets.copyItems() {
|
||||
if let target = target.value {
|
||||
target.updateDisplayPlaceholder(displayPlaceholder: false)
|
||||
@ -268,7 +434,13 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
private let firstFrameQueue: Queue
|
||||
private let stateUpdated: () -> Void
|
||||
|
||||
private var itemContexts: [String: ItemAnimationContext] = [:]
|
||||
private struct ItemKey: Hashable {
|
||||
var id: String
|
||||
var width: Int
|
||||
var height: Int
|
||||
}
|
||||
|
||||
private var itemContexts: [ItemKey: ItemAnimationContext] = [:]
|
||||
|
||||
private(set) var isPlaying: Bool = false {
|
||||
didSet {
|
||||
@ -284,8 +456,9 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
|
||||
func add(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (CGSize, AnimationCacheItemWriter) -> Disposable) -> Disposable {
|
||||
let itemKey = ItemKey(id: itemId, width: Int(size.width), height: Int(size.height))
|
||||
let itemContext: ItemAnimationContext
|
||||
if let current = self.itemContexts[itemId] {
|
||||
if let current = self.itemContexts[itemKey] {
|
||||
itemContext = current
|
||||
} else {
|
||||
itemContext = ItemAnimationContext(cache: cache, itemId: itemId, size: size, fetch: fetch, stateUpdated: { [weak self] in
|
||||
@ -294,7 +467,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
strongSelf.updateIsPlaying()
|
||||
})
|
||||
self.itemContexts[itemId] = itemContext
|
||||
self.itemContexts[itemKey] = itemContext
|
||||
}
|
||||
|
||||
let index = itemContext.targets.add(Weak(target))
|
||||
@ -302,12 +475,12 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
|
||||
let deinitIndex = target.deinitCallbacks.add { [weak self, weak itemContext] in
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self, let itemContext = itemContext, strongSelf.itemContexts[itemId] === itemContext else {
|
||||
guard let strongSelf = self, let itemContext = itemContext, strongSelf.itemContexts[itemKey] === itemContext else {
|
||||
return
|
||||
}
|
||||
itemContext.targets.remove(index)
|
||||
if itemContext.targets.isEmpty {
|
||||
strongSelf.itemContexts.removeValue(forKey: itemId)
|
||||
strongSelf.itemContexts.removeValue(forKey: itemKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,7 +493,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
|
||||
return ActionDisposable { [weak self, weak itemContext, weak target] in
|
||||
guard let strongSelf = self, let itemContext = itemContext, strongSelf.itemContexts[itemId] === itemContext else {
|
||||
guard let strongSelf = self, let itemContext = itemContext, strongSelf.itemContexts[itemKey] === itemContext else {
|
||||
return
|
||||
}
|
||||
if let target = target {
|
||||
@ -329,18 +502,25 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
}
|
||||
itemContext.targets.remove(index)
|
||||
if itemContext.targets.isEmpty {
|
||||
strongSelf.itemContexts.removeValue(forKey: itemId)
|
||||
strongSelf.itemContexts.removeValue(forKey: itemKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadFirstFrameSynchronously(target: MultiAnimationRenderTarget, cache: AnimationCache, itemId: String, size: CGSize) -> Bool {
|
||||
if let item = cache.getFirstFrameSynchronously(sourceId: itemId, size: size) {
|
||||
guard let frameGroup = FrameGroup(item: item, timestamp: 0.0) else {
|
||||
guard let frame = item.advance(advance: .frames(1), requestedFormat: .rgba) else {
|
||||
return false
|
||||
}
|
||||
guard let loadedFrame = ItemAnimationContext.Frame(frame: frame) else {
|
||||
return false
|
||||
}
|
||||
|
||||
target.contents = frameGroup.image.cgImage
|
||||
target.contents = loadedFrame.image.cgImage
|
||||
|
||||
if let blurredRepresentationTarget = target.blurredRepresentationTarget {
|
||||
blurredRepresentationTarget.contents = loadedFrame.blurredRepresentation(color: target.blurredRepresentationBackgroundColor)?.cgImage
|
||||
}
|
||||
|
||||
return true
|
||||
} else {
|
||||
@ -357,15 +537,24 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer {
|
||||
return
|
||||
}
|
||||
|
||||
let frameGroup = FrameGroup(item: item, timestamp: 0.0)
|
||||
let loadedFrame: ItemAnimationContext.Frame?
|
||||
if let frame = item.advance(advance: .frames(1), requestedFormat: .rgba) {
|
||||
loadedFrame = ItemAnimationContext.Frame(frame: frame)
|
||||
} else {
|
||||
loadedFrame = nil
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
guard let target = target else {
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
if let frameGroup = frameGroup {
|
||||
target.contents = frameGroup.image.cgImage
|
||||
if let loadedFrame = loadedFrame {
|
||||
target.contents = loadedFrame.image.cgImage
|
||||
|
||||
if let blurredRepresentationTarget = target.blurredRepresentationTarget {
|
||||
blurredRepresentationTarget.contents = loadedFrame.blurredRepresentation(color: target.blurredRepresentationBackgroundColor)?.cgImage
|
||||
}
|
||||
|
||||
completion(true)
|
||||
} else {
|
||||
|
||||
@ -226,7 +226,7 @@ public final class TextNodeWithEntities {
|
||||
if let current = self.inlineStickerItemLayers[id] {
|
||||
itemLayer = current
|
||||
} else {
|
||||
itemLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: itemSize, height: itemSize))
|
||||
itemLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: floor(itemSize * 1.2), height: floor(itemSize * 1.2)))
|
||||
self.inlineStickerItemLayers[id] = itemLayer
|
||||
self.textNode.layer.addSublayer(itemLayer)
|
||||
|
||||
|
||||
@ -44,7 +44,8 @@ public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: A
|
||||
}
|
||||
}
|
||||
}
|
||||
}, proposedWidth: frame.width, proposedHeight: frame.height, duration: frameDuration)
|
||||
return frameDuration
|
||||
}, proposedWidth: frame.width, proposedHeight: frame.height)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelBadgeAdd.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelBadgeAdd.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "addstickers.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
103
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelBadgeAdd.imageset/addstickers.pdf
vendored
Normal file
103
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelBadgeAdd.imageset/addstickers.pdf
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 8.000000 2.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.000000 10.000000 m
|
||||
1.000000 10.552285 0.552285 11.000000 0.000000 11.000000 c
|
||||
-0.552285 11.000000 -1.000000 10.552285 -1.000000 10.000000 c
|
||||
1.000000 10.000000 l
|
||||
h
|
||||
-1.000000 2.000000 m
|
||||
-1.000000 1.447715 -0.552285 1.000000 0.000000 1.000000 c
|
||||
0.552285 1.000000 1.000000 1.447715 1.000000 2.000000 c
|
||||
-1.000000 2.000000 l
|
||||
h
|
||||
-1.000000 10.000000 m
|
||||
-1.000000 2.000000 l
|
||||
1.000000 2.000000 l
|
||||
1.000000 10.000000 l
|
||||
-1.000000 10.000000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
0.000000 1.000000 -1.000000 0.000000 6.000000 8.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.000000 2.000000 m
|
||||
1.000000 2.552285 0.552285 3.000000 0.000000 3.000000 c
|
||||
-0.552285 3.000000 -1.000000 2.552285 -1.000000 2.000000 c
|
||||
1.000000 2.000000 l
|
||||
h
|
||||
-1.000000 -6.000000 m
|
||||
-1.000000 -6.552285 -0.552285 -7.000000 0.000000 -7.000000 c
|
||||
0.552285 -7.000000 1.000000 -6.552285 1.000000 -6.000000 c
|
||||
-1.000000 -6.000000 l
|
||||
h
|
||||
-1.000000 2.000000 m
|
||||
-1.000000 -6.000000 l
|
||||
1.000000 -6.000000 l
|
||||
1.000000 2.000000 l
|
||||
-1.000000 2.000000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1083
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 16.000000 16.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001173 00000 n
|
||||
0000001196 00000 n
|
||||
0000001369 00000 n
|
||||
0000001443 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1502
|
||||
%%EOF
|
||||
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelBadgeLock.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelBadgeLock.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lockedstickers.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 4.000000 2.999512 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.165000 7.500039 m
|
||||
2.165000 8.513481 2.986557 9.335039 4.000000 9.335039 c
|
||||
5.013443 9.335039 5.835000 8.513481 5.835000 7.500039 c
|
||||
5.835000 5.996800 l
|
||||
5.637455 6.000156 5.413168 6.000156 5.155556 6.000156 c
|
||||
2.844445 6.000156 l
|
||||
2.586832 6.000156 2.362546 6.000156 2.165000 5.996800 c
|
||||
2.165000 7.500039 l
|
||||
h
|
||||
0.835000 5.729585 m
|
||||
0.835000 7.500039 l
|
||||
0.835000 9.248020 2.252019 10.665039 4.000000 10.665039 c
|
||||
5.747981 10.665039 7.165000 9.248020 7.165000 7.500039 c
|
||||
7.165000 5.729585 l
|
||||
7.437430 5.559180 7.659470 5.317513 7.806234 5.029473 c
|
||||
8.000000 4.649185 8.000000 4.151361 8.000000 3.155712 c
|
||||
8.000000 2.844601 l
|
||||
8.000000 1.848951 8.000000 1.351128 7.806234 0.970840 c
|
||||
7.635793 0.636330 7.363827 0.364364 7.029317 0.193922 c
|
||||
6.649029 0.000156 6.151205 0.000156 5.155556 0.000156 c
|
||||
2.844444 0.000156 l
|
||||
1.848796 0.000156 1.350971 0.000156 0.970684 0.193922 c
|
||||
0.636173 0.364364 0.364208 0.636330 0.193766 0.970840 c
|
||||
0.000000 1.351128 0.000000 1.848951 0.000000 2.844601 c
|
||||
0.000000 3.155712 l
|
||||
0.000000 4.151361 0.000000 4.649185 0.193766 5.029473 c
|
||||
0.340530 5.317513 0.562570 5.559180 0.835000 5.729585 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1229
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 16.000000 16.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001319 00000 n
|
||||
0000001342 00000 n
|
||||
0000001515 00000 n
|
||||
0000001589 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1648
|
||||
%%EOF
|
||||
@ -147,6 +147,10 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
|
||||
}
|
||||
}
|
||||
|
||||
func ready() -> Signal<Bool, NoError> {
|
||||
return .single(true)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let imageLayout = self.imageNode.asyncLayout()
|
||||
let currentImageResource = self.currentImageResource
|
||||
|
||||
@ -8606,6 +8606,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode)
|
||||
}
|
||||
|
||||
strongSelf.chatDisplayNode.updateTypingActivity(true)
|
||||
}, backwardsDeleteText: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
||||
@ -2836,12 +2836,21 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if peerId?.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = self.interactiveEmojis, interactiveEmojis.emojis.contains(trimmedInputText) {
|
||||
messages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: trimmedInputText)), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: nil))
|
||||
} else {
|
||||
var inlineStickers: [MediaId: Media] = [:]
|
||||
effectiveInputText.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: effectiveInputText.length), using: { value, _, _ in
|
||||
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||
if let file = value.file {
|
||||
inlineStickers[file.fileId] = file
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let inputText = convertMarkdownToAttributes(effectiveInputText)
|
||||
|
||||
for text in breakChatInputText(trimChatInputText(inputText)) {
|
||||
if text.length != 0 {
|
||||
var attributes: [MessageAttribute] = []
|
||||
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text, maxAnimatedEmojisInText: 0/*Int(self.context.userLimits.maxAnimatedEmojisInText)*/))
|
||||
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text, maxAnimatedEmojisInText: 0))
|
||||
if !entities.isEmpty {
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
@ -2852,17 +2861,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
webpage = self.chatPresentationInterfaceState.urlPreview?.1
|
||||
}
|
||||
|
||||
messages.append(.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: webpage.flatMap(AnyMediaReference.standalone), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: nil))
|
||||
|
||||
#if DEBUG
|
||||
if text.string == "sleep" {
|
||||
messages.removeAll()
|
||||
|
||||
for i in 0 ..< 5 {
|
||||
messages.append(.message(text: "sleep\(i)", attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
messages.append(.message(text: text.string, attributes: attributes, inlineStickers: inlineStickers, mediaReference: webpage.flatMap(AnyMediaReference.standalone), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil, correlationId: nil))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -79,19 +79,66 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||
|
||||
let emojiItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
|
||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||
hasPremium
|
||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.LocalRecentEmoji], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||
hasPremium,
|
||||
context.account.viewTracker.featuredEmojiPacks()
|
||||
)
|
||||
|> map { view, hasPremium -> EmojiPagerContentComponent in
|
||||
|> map { view, hasPremium, featuredEmojiPacks -> EmojiPagerContentComponent in
|
||||
struct ItemGroup {
|
||||
var supergroupId: AnyHashable
|
||||
var id: AnyHashable
|
||||
var isPremium: Bool
|
||||
var title: String
|
||||
var isPremiumLocked: Bool
|
||||
var isFeatured: Bool
|
||||
var items: [EmojiPagerContentComponent.Item]
|
||||
}
|
||||
var itemGroups: [ItemGroup] = []
|
||||
var itemGroupIndexById: [AnyHashable: Int] = [:]
|
||||
|
||||
var recentEmoji: OrderedItemListView?
|
||||
for orderedView in view.orderedItemListsViews {
|
||||
if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji {
|
||||
recentEmoji = orderedView
|
||||
}
|
||||
}
|
||||
|
||||
if let recentEmoji = recentEmoji {
|
||||
for item in recentEmoji.items {
|
||||
guard let item = item.contents.get(RecentEmojiItem.self) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if case let .file(file) = item.content, isPremiumDisabled, file.isPremiumEmoji {
|
||||
continue
|
||||
}
|
||||
|
||||
let resultItem: EmojiPagerContentComponent.Item
|
||||
switch item.content {
|
||||
case let .file(file):
|
||||
resultItem = EmojiPagerContentComponent.Item(
|
||||
file: file,
|
||||
staticEmoji: nil,
|
||||
subgroupId: nil
|
||||
)
|
||||
case let .text(text):
|
||||
resultItem = EmojiPagerContentComponent.Item(
|
||||
file: nil,
|
||||
staticEmoji: text,
|
||||
subgroupId: nil
|
||||
)
|
||||
}
|
||||
|
||||
let groupId = "recent"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
//TODO:localize
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", isPremiumLocked: false, isFeatured: false, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (subgroupId, list) in staticEmojiMapping {
|
||||
let groupId: AnyHashable = "static"
|
||||
for emojiString in list {
|
||||
@ -105,11 +152,17 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, isPremium: false, items: [resultItem]))
|
||||
//TODO:localize
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Emoji", isPremiumLocked: false, isFeatured: false, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var installedCollectionIds = Set<ItemCollectionId>()
|
||||
for (id, _, _) in view.collectionInfos {
|
||||
installedCollectionIds.insert(id)
|
||||
}
|
||||
|
||||
for entry in view.entries {
|
||||
guard let item = entry.item as? StickerPackItem else {
|
||||
continue
|
||||
@ -122,20 +175,50 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
let supergroupId = entry.index.collectionId
|
||||
let groupId: AnyHashable = supergroupId
|
||||
let isPremium: Bool = item.file.isPremiumEmoji && !hasPremium
|
||||
if isPremium && isPremiumDisabled {
|
||||
let isPremiumLocked: Bool = item.file.isPremiumEmoji && !hasPremium
|
||||
if isPremiumLocked && isPremiumDisabled {
|
||||
continue
|
||||
}
|
||||
/*if isPremium {
|
||||
groupId = "\(supergroupId)-p"
|
||||
} else {
|
||||
groupId = supergroupId
|
||||
}*/
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, isPremium: isPremium, items: [resultItem]))
|
||||
|
||||
var title = ""
|
||||
inner: for (id, info, _) in view.collectionInfos {
|
||||
if id == entry.index.collectionId, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title
|
||||
break inner
|
||||
}
|
||||
}
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, isPremiumLocked: isPremiumLocked, isFeatured: false, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
|
||||
for featuredEmojiPack in featuredEmojiPacks {
|
||||
if installedCollectionIds.contains(featuredEmojiPack.info.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
for item in featuredEmojiPack.topItems {
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
file: item.file,
|
||||
staticEmoji: nil,
|
||||
subgroupId: nil
|
||||
)
|
||||
|
||||
let supergroupId = featuredEmojiPack.info.id
|
||||
let groupId: AnyHashable = supergroupId
|
||||
let isPremiumLocked: Bool = item.file.isPremiumEmoji && !hasPremium
|
||||
if isPremiumLocked && isPremiumDisabled {
|
||||
continue
|
||||
}
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, isPremiumLocked: isPremiumLocked, isFeatured: true, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,20 +229,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationRenderer: animationRenderer,
|
||||
inputInteraction: inputInteraction,
|
||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||
var title: String?
|
||||
var hasClear = false
|
||||
if group.id == AnyHashable("recent") {
|
||||
//TODO:localize
|
||||
title = "Recently Used"
|
||||
} else {
|
||||
for (id, info, _) in view.collectionInfos {
|
||||
if AnyHashable(id) == group.id, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title
|
||||
break
|
||||
}
|
||||
}
|
||||
hasClear = true
|
||||
}
|
||||
|
||||
return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: title, isPremium: group.isPremium, displayPremiumBadges: false, items: group.items)
|
||||
return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: group.title, isFeatured: group.isFeatured, isPremiumLocked: group.isPremiumLocked, hasClear: hasClear, displayPremiumBadges: false, items: group.items)
|
||||
},
|
||||
itemLayoutType: .compact
|
||||
)
|
||||
@ -244,12 +319,53 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
},
|
||||
openStickerSettings: {
|
||||
},
|
||||
openPremiumSection: { [weak controllerInteraction] in
|
||||
addGroupAction: { [weak controllerInteraction] groupId, isPremiumLocked in
|
||||
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
|
||||
return
|
||||
}
|
||||
|
||||
if isPremiumLocked {
|
||||
let controller = PremiumIntroScreen(context: context, source: .stickers)
|
||||
controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
|
||||
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { views in
|
||||
guard let view = views.views[viewKey] as? OrderedItemListView else {
|
||||
return
|
||||
}
|
||||
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
|
||||
if featuredEmojiPack.info.id == collectionId {
|
||||
let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
clearGroup: { [weak controllerInteraction] groupId in
|
||||
guard let controllerInteraction = controllerInteraction else {
|
||||
return
|
||||
}
|
||||
let controller = PremiumIntroScreen(context: context, source: .stickers)
|
||||
controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
if groupId == AnyHashable("recent") {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
let _ = context.engine.stickers.clearRecentlyUsedEmoji().start()
|
||||
}))
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
controllerInteraction.presentController(actionSheet, nil)
|
||||
}
|
||||
},
|
||||
pushController: { [weak controllerInteraction] controller in
|
||||
guard let controllerInteraction = controllerInteraction else {
|
||||
@ -311,7 +427,27 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
controller.navigationPresentation = .modal
|
||||
controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
},
|
||||
openPremiumSection: {
|
||||
addGroupAction: { _, _ in
|
||||
},
|
||||
clearGroup: { [weak controllerInteraction] groupId in
|
||||
guard let controllerInteraction = controllerInteraction else {
|
||||
return
|
||||
}
|
||||
if groupId == AnyHashable("recent") {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
let _ = context.engine.stickers.clearRecentlyUsedStickers().start()
|
||||
}))
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
controllerInteraction.presentController(actionSheet, nil)
|
||||
}
|
||||
},
|
||||
pushController: { [weak controllerInteraction] controller in
|
||||
guard let controllerInteraction = controllerInteraction else {
|
||||
@ -366,6 +502,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
struct ItemGroup {
|
||||
var supergroupId: AnyHashable
|
||||
var id: AnyHashable
|
||||
var title: String
|
||||
var isPremiumLocked: Bool
|
||||
var isFeatured: Bool
|
||||
var displayPremiumBadges: Bool
|
||||
var items: [EmojiPagerContentComponent.Item]
|
||||
}
|
||||
@ -408,7 +547,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, displayPremiumBadges: false, items: [resultItem]))
|
||||
//TODO:localize
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Saved", isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -434,7 +574,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, displayPremiumBadges: false, items: [resultItem]))
|
||||
//TODO:localize
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
}
|
||||
|
||||
count += 1
|
||||
@ -483,7 +624,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, displayPremiumBadges: false, items: [resultItem]))
|
||||
//TODO:localize
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Premium", isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -502,7 +644,15 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, displayPremiumBadges: true, items: [resultItem]))
|
||||
|
||||
var title = ""
|
||||
inner: for (id, info, _) in view.collectionInfos {
|
||||
if id == groupId, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title
|
||||
break inner
|
||||
}
|
||||
}
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,26 +663,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
animationRenderer: animationRenderer,
|
||||
inputInteraction: stickerInputInteraction,
|
||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||
var title: String?
|
||||
if group.id == AnyHashable("saved") {
|
||||
//TODO:localize
|
||||
title = "Saved"
|
||||
} else if group.id == AnyHashable("recent") {
|
||||
//TODO:localize
|
||||
title = "Recently Used"
|
||||
} else if group.id == AnyHashable("premium") {
|
||||
//TODO:localize
|
||||
title = "Premium"
|
||||
} else {
|
||||
for (id, info, _) in view.collectionInfos {
|
||||
if AnyHashable(id) == group.id, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title
|
||||
break
|
||||
}
|
||||
}
|
||||
var hasClear = false
|
||||
if group.id == AnyHashable("recent") {
|
||||
hasClear = true
|
||||
}
|
||||
|
||||
return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: title, isPremium: false, displayPremiumBadges: group.displayPremiumBadges, items: group.items)
|
||||
return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: group.title, isFeatured: group.isFeatured, isPremiumLocked: group.isPremiumLocked, hasClear: hasClear, displayPremiumBadges: group.displayPremiumBadges, items: group.items)
|
||||
},
|
||||
itemLayoutType: .detailed
|
||||
)
|
||||
@ -592,6 +728,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
loadMoreToken: nil
|
||||
))
|
||||
|
||||
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
|
||||
return combineLatest(queue: .mainQueue(),
|
||||
emojiItems,
|
||||
stickerItems,
|
||||
@ -603,7 +740,37 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] = []
|
||||
for reaction in reactions {
|
||||
if let file = animatedEmojiStickers[reaction]?.first?.file {
|
||||
availableGifSearchEmojies.append(EntityKeyboardComponent.GifSearchEmoji(emoji: reaction, file: file, title: reaction))
|
||||
var title: String?
|
||||
switch reaction {
|
||||
case "😡":
|
||||
title = strings.Gif_Emotion_Angry
|
||||
case "😮":
|
||||
title = strings.Gif_Emotion_Surprised
|
||||
case "😂":
|
||||
title = strings.Gif_Emotion_Joy
|
||||
case "😘":
|
||||
title = strings.Gif_Emotion_Kiss
|
||||
case "😍":
|
||||
title = strings.Gif_Emotion_Hearts
|
||||
case "👍":
|
||||
title = strings.Gif_Emotion_ThumbsUp
|
||||
case "👎":
|
||||
title = strings.Gif_Emotion_ThumbsDown
|
||||
case "🙄":
|
||||
title = strings.Gif_Emotion_RollEyes
|
||||
case "😎":
|
||||
title = strings.Gif_Emotion_Cool
|
||||
case "🥳":
|
||||
title = strings.Gif_Emotion_Party
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
guard let title = title else {
|
||||
continue
|
||||
}
|
||||
|
||||
availableGifSearchEmojies.append(EntityKeyboardComponent.GifSearchEmoji(emoji: reaction, file: file, title: title))
|
||||
}
|
||||
}
|
||||
|
||||
@ -995,6 +1162,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
gifContent = nil
|
||||
}
|
||||
|
||||
if let gifContentValue = gifContent {
|
||||
if gifContentValue.items.isEmpty {
|
||||
gifContent = nil
|
||||
}
|
||||
}
|
||||
|
||||
let entityKeyboardSize = self.entityKeyboardView.update(
|
||||
transition: mappedTransition,
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
@ -1340,7 +1513,9 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
||||
},
|
||||
openStickerSettings: {
|
||||
},
|
||||
openPremiumSection: {
|
||||
addGroupAction: { _, _ in
|
||||
},
|
||||
clearGroup: { _ in
|
||||
},
|
||||
pushController: { _ in
|
||||
},
|
||||
|
||||
@ -184,9 +184,9 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode {
|
||||
component: AnyComponent(LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: !isEmoji ? "anim_stickertosmile" : "anim_smiletosticker",
|
||||
colors: colors,
|
||||
mode: .animateTransitionFromPrevious
|
||||
),
|
||||
colors: colors,
|
||||
size: animationFrame.size
|
||||
)),
|
||||
environment: {},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user