Emoji 1.1 WIP

This commit is contained in:
Ali 2022-08-01 02:22:35 +04:00
parent 47239681a7
commit 095b9d5058
17 changed files with 395 additions and 279 deletions

View File

@ -242,7 +242,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
for index in 0 ..< items.count {
let item = items[index]
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: 0, fileId: item.file.fileId, staticEmoji: nil)
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: 0, itemId: .file(item.file.fileId), staticEmoji: nil)
validIds.insert(itemId)
let itemDimensions = item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
@ -259,10 +259,10 @@ final class StickerPackEmojisItemNode: GridItemNode {
itemTransition = .immediate
itemLayer = EmojiPagerContentComponent.View.ItemLayer(
item: EmojiPagerContentComponent.Item(file: item.file, staticEmoji: nil, subgroupId: nil),
item: EmojiPagerContentComponent.Item(animationData: EntityKeyboardAnimationData(file: item.file), itemFile: item.file, staticEmoji: nil, subgroupId: nil),
context: context,
attemptSynchronousLoad: attemptSynchronousLoads,
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
staticEmoji: nil,
cache: animationCache,
renderer: animationRenderer,
@ -281,7 +281,8 @@ final class StickerPackEmojisItemNode: GridItemNode {
} else {
placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
context: context,
file: item.file,
dimensions: item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0),
immediateThumbnailData: item.file.immediateThumbnailData,
shimmerView: strongSelf.shimmerHostView,
color: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08),
size: itemNativeFitSize

View File

@ -27,7 +27,7 @@ typedef NS_ENUM(NSUInteger, ImageDCTTableType) {
- (void)forwardWithPixels:(uint8_t const * _Nonnull)pixels coefficients:(int16_t * _Nonnull)coefficients width:(NSInteger)width height:(NSInteger)height bytesPerRow:(NSInteger)bytesPerRow __attribute__((objc_direct));
- (void)inverseWithCoefficients:(int16_t const * _Nonnull)coefficients pixels:(uint8_t * _Nonnull)pixels width:(NSInteger)width height:(NSInteger)height coefficientsPerRow:(NSInteger)coefficientsPerRow bytesPerRow:(NSInteger)bytesPerRow __attribute__((objc_direct));
- (void)forward4x4:(int16_t const * _Nonnull)normalizedCoefficients coefficients:(int16_t * _Nonnull)coefficients width:(NSInteger)width height:(NSInteger)height __attribute__((objc_direct));
- (void)inverse4x4:(int16_t const * _Nonnull)coefficients normalizedCoefficients:(int16_t * _Nonnull)normalizedCoefficients width:(NSInteger)width height:(NSInteger)height __attribute__((objc_direct));
- (void)inverse4x4Add:(int16_t const * _Nonnull)coefficients normalizedCoefficients:(int16_t * _Nonnull)normalizedCoefficients width:(NSInteger)width height:(NSInteger)height __attribute__((objc_direct));
@end

View File

@ -727,7 +727,7 @@ static inline void transpose_idct4x4_16_bd8(int16x8_t *const a) {
idct4x4_16_kernel_bd8(a);
}
inline void vpx_idct4x4_16_add_neon(const int16x8_t &top64, const int16x8_t &bottom64, int16_t *dest, int16_t multiplier) {
inline void vpx_idct4x4_16_add_neon(const int16x8_t &top64, const int16x8_t &bottom64, const int16x4_t &current0, const int16x4_t &current1, const int16x4_t &current2, const int16x4_t &current3, int16_t multiplier, int16_t *dest, int destRowIncrement) {
int16x8_t a[2];
assert(!((intptr_t)dest % sizeof(uint32_t)));
@ -745,11 +745,19 @@ inline void vpx_idct4x4_16_add_neon(const int16x8_t &top64, const int16x8_t &bot
a[0] = vrshrq_n_s16(a[0], 4);
a[1] = vrshrq_n_s16(a[1], 4);
vst1q_s16(dest, a[0]);
dest += 2 * 4;
vst1_s16(dest, vget_high_s16(a[1]));
dest += 4;
vst1_s16(dest, vget_low_s16(a[1]));
a[0] = vaddq_s16(a[0], vcombine_s16(current0, current1));
a[1] = vaddq_s16(a[1], vcombine_s16(current3, current2));
vst1_s16(dest + destRowIncrement * 0, vget_low_s16(a[0]));
vst1_s16(dest + destRowIncrement * 1, vget_high_s16(a[0]));
vst1_s16(dest + destRowIncrement * 2, vget_high_s16(a[1]));
vst1_s16(dest + destRowIncrement * 3, vget_low_s16(a[1]));
//vst1q_s16(dest, a[0]);
//dest += 2 * 4;
//vst1_s16(dest, vget_high_s16(a[1]));
//dest += 4;
//vst1_s16(dest, vget_low_s16(a[1]));
}
static int dct4x4QuantDC = 58;
@ -803,11 +811,14 @@ void performForward4x4Dct(int16_t const *normalizedCoefficients, int16_t *coeffi
}
}
void performInverse4x4Dct(int16_t const * coefficients, int16_t *normalizedCoefficients, int width, int height, DctAuxiliaryData *auxiliaryData, IFAST_MULT_TYPE *ifmtbl) {
DCTELEM resultBlock[4 * 4];
void performInverse4x4DctAdd(int16_t const *coefficients, int16_t *normalizedCoefficients, int width, int height, DctAuxiliaryData *auxiliaryData, IFAST_MULT_TYPE *ifmtbl) {
for (int y = 0; y < height; y += 4) {
for (int x = 0; x < width; x += 4) {
int16x4_t current0 = vld1_s16(&normalizedCoefficients[(y + 0) * width + x]);
int16x4_t current1 = vld1_s16(&normalizedCoefficients[(y + 1) * width + x]);
int16x4_t current2 = vld1_s16(&normalizedCoefficients[(y + 2) * width + x]);
int16x4_t current3 = vld1_s16(&normalizedCoefficients[(y + 3) * width + x]);
uint32x2_t sa = vld1_u32((uint32_t *)&coefficients[(y + 0) * width + x]);
uint32x2_t sb = vld1_u32((uint32_t *)&coefficients[(y + 1) * width + x]);
uint32x2_t sc = vld1_u32((uint32_t *)&coefficients[(y + 2) * width + x]);
@ -829,34 +840,7 @@ void performInverse4x4Dct(int16_t const * coefficients, int16_t *normalizedCoeff
int16x8_t top64 = vreinterpretq_s16_u16(qtop16);
int16x8_t bottom64 = vreinterpretq_s16_u16(qbottom16);
/*DCTELEM coefficientBlock[4 * 4];
for (int blockY = 0; blockY < 4; blockY++) {
for (int blockX = 0; blockX < 4; blockX++) {
coefficientBlock[zigZag4x4Inv[blockY * 4 + blockX]] = coefficients[(y + blockY) * width + (x + blockX)];
}
}
top64 = vreinterpretq_s16_u64(vld1q_u64((uint64_t *)&coefficientBlock[0]));
bottom64 = vreinterpretq_s16_u64(vld1q_u64((uint64_t *)&coefficientBlock[8]));*/
vpx_idct4x4_16_add_neon(top64, bottom64, resultBlock, dct4x4QuantAC);
uint32x2_t a = vld1_u32((uint32_t *)&resultBlock[4 * 0]);
uint32x2_t b = vld1_u32((uint32_t *)&resultBlock[4 * 1]);
uint32x2_t c = vld1_u32((uint32_t *)&resultBlock[4 * 2]);
uint32x2_t d = vld1_u32((uint32_t *)&resultBlock[4 * 3]);
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 0) * width + x], a);
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 1) * width + x], b);
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 2) * width + x], c);
vst1_u32((uint32_t *)&normalizedCoefficients[(y + 3) * width + x], d);
/*for (int blockY = 0; blockY < 4; blockY++) {
for (int blockX = 0; blockX < 4; blockX++) {
normalizedCoefficients[(y + blockY) * width + (x + blockX)] = resultBlock[blockY * 4 + blockX];
}
}*/
vpx_idct4x4_16_add_neon(top64, bottom64, current0, current1, current2, current3, dct4x4QuantAC, normalizedCoefficients + y * width + x, width);
}
}
}
@ -932,8 +916,8 @@ void DCT::forward4x4(int16_t const *normalizedCoefficients, int16_t *coefficient
performForward4x4Dct(normalizedCoefficients, coefficients, width, height, (DCTELEM *)_internal->forwardDctData.data());
}
void DCT::inverse4x4(int16_t const *coefficients, int16_t *normalizedCoefficients, int width, int height) {
performInverse4x4Dct(coefficients, normalizedCoefficients, width, height, _internal->auxiliaryData, (IFAST_MULT_TYPE *)_internal->inverseDctData.data());
void DCT::inverse4x4Add(int16_t const *coefficients, int16_t *normalizedCoefficients, int width, int height) {
performInverse4x4DctAdd(coefficients, normalizedCoefficients, width, height, _internal->auxiliaryData, (IFAST_MULT_TYPE *)_internal->inverseDctData.data());
}
}

View File

@ -31,7 +31,7 @@ public:
void forward(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow);
void inverse(int16_t const *coefficients, uint8_t *pixels, int width, int height, int coefficientsPerRow, int bytesPerRow);
void forward4x4(int16_t const *normalizedCoefficients, int16_t *coefficients, int width, int height);
void inverse4x4(int16_t const *coefficients, int16_t *normalizedCoefficients, int width, int height);
void inverse4x4Add(int16_t const *coefficients, int16_t *normalizedCoefficients, int width, int height);
private:
DCTInternal *_internal;

View File

@ -82,8 +82,8 @@
_dct->forward4x4(normalizedCoefficients, coefficients, (int)width, (int)height);
}
- (void)inverse4x4:(int16_t const * _Nonnull)coefficients normalizedCoefficients:(int16_t * _Nonnull)normalizedCoefficients width:(NSInteger)width height:(NSInteger)height {
_dct->inverse4x4(coefficients, normalizedCoefficients, (int)width, (int)height);
- (void)inverse4x4Add:(int16_t const * _Nonnull)coefficients normalizedCoefficients:(int16_t * _Nonnull)normalizedCoefficients width:(NSInteger)width height:(NSInteger)height {
_dct->inverse4x4Add(coefficients, normalizedCoefficients, (int)width, (int)height);
}
@end

View File

@ -118,14 +118,17 @@ void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outByte
}
void convertUInt8toInt16(uint8_t const *source, int16_t *dest, int length) {
for (int i = 0; i < length; i += 8) {
uint8x8_t lhs8 = vld1_u8(&source[i]);
int16x8_t lhs = vreinterpretq_s16_u16(vmovl_u8(lhs8));
vst1q_s16(&dest[i], lhs);
for (int i = 0; i < length; i += 8 * 4) {
#pragma unroll
for (int j = 0; j < 4; j++) {
uint8x8_t lhs8 = vld1_u8(&source[i + j * 8]);
int16x8_t lhs = vreinterpretq_s16_u16(vmovl_u8(lhs8));
vst1q_s16(&dest[i + j * 8], lhs);
}
}
if (length % 8 != 0) {
for (int i = length - (length % 8); i < length; i++) {
if (length % (8 * 4) != 0) {
for (int i = length - (length % (8 * 4)); i < length; i++) {
dest[i] = (int16_t)source[i];
}
}
@ -167,14 +170,17 @@ void subtractArraysInt16(int16_t const *a, int16_t const *b, int16_t *dest, int
}
void addArraysInt16(int16_t const *a, int16_t const *b, int16_t *dest, int length) {
for (int i = 0; i < length; i += 8) {
int16x8_t lhs = vld1q_s16((int16_t *)&a[i]);
int16x8_t rhs = vld1q_s16((int16_t *)&b[i]);
int16x8_t result = vaddq_s16(lhs, rhs);
vst1q_s16((int16_t *)&dest[i], result);
for (int i = 0; i < length; i += 8 * 4) {
#pragma unroll
for (int j = 0; j < 4; j++) {
int16x8_t lhs = vld1q_s16((int16_t *)&a[i + j * 8]);
int16x8_t rhs = vld1q_s16((int16_t *)&b[i + j * 8]);
int16x8_t result = vaddq_s16(lhs, rhs);
vst1q_s16((int16_t *)&dest[i + j * 8], result);
}
}
if (length % 8 != 0) {
for (int i = length - (length % 8); i < length; i++) {
if (length % (8 * 4) != 0) {
for (int i = length - (length % (8 * 4)); i < length; i++) {
dest[i] = a[i] - b[i];
}
}

View File

@ -473,8 +473,8 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
differenceCoefficients.dct4x4(dctData: dctData, target: dctCoefficients)
//previous + delta = current
dctCoefficients.idct4x4(dctData: dctData, target: differenceCoefficients)
previousFrameCoefficients.add(other: differenceCoefficients)
dctCoefficients.idct4x4Add(dctData: dctData, target: previousFrameCoefficients)
//previousFrameCoefficients.add(other: differenceCoefficients)
} else {
isKeyframe = true
@ -746,30 +746,23 @@ private final class AnimationCacheItemAccessor {
self.currentCoefficients = currentCoefficients
}
let deltaCoefficients: DctCoefficientsYUVA420
/*let deltaCoefficients: DctCoefficientsYUVA420
if let current = self.deltaCoefficients {
deltaCoefficients = current
} else {
deltaCoefficients = DctCoefficientsYUVA420(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height)
self.deltaCoefficients = deltaCoefficients
}
}*/
switch frameType {
case 1:
dctCoefficients.idct8x8(dctData: self.currentDctData, target: yuvaSurface)
yuvaSurface.toCoefficients(target: currentCoefficients)
default:
dctCoefficients.idct4x4(dctData: self.currentDctData, target: deltaCoefficients)
currentCoefficients.add(other: deltaCoefficients)
dctCoefficients.idct4x4Add(dctData: self.currentDctData, target: currentCoefficients)
//currentCoefficients.add(other: deltaCoefficients)
if !"".isEmpty {
let deltaFloatCoefficients = FloatCoefficientsYUVA420(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height)
deltaCoefficients.toFloatCoefficients(target: deltaFloatCoefficients)
deltaFloatCoefficients.add(constant: 128.0)
deltaFloatCoefficients.toYUVA420(target: yuvaSurface)
} else {
currentCoefficients.toYUVA420(target: yuvaSurface)
}
currentCoefficients.toYUVA420(target: yuvaSurface)
}
self.currentFrame = CurrentFrame(index: index, duration: self.durationMapping[index], yuva: yuvaSurface)

View File

@ -663,7 +663,7 @@ extension DctCoefficientsYUVA420 {
}
}
func idct4x4(dctData: DctData, target: DctCoefficientsYUVA420) {
func idct4x4Add(dctData: DctData, target: DctCoefficientsYUVA420) {
precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height)
for i in 0 ..< 4 {
@ -694,7 +694,7 @@ extension DctCoefficientsYUVA420 {
//memcpy(coefficients, sourceCoefficients, sourceBytes.count)
dctData.deltaDct.inverse4x4(sourceCoefficients, normalizedCoefficients: coefficients, width: sourcePlane.width, height: sourcePlane.height)
dctData.deltaDct.inverse4x4Add(sourceCoefficients, normalizedCoefficients: coefficients, width: sourcePlane.width, height: sourcePlane.height)
}
}
}

View File

@ -16,29 +16,48 @@ import MultiAnimationRenderer
import ShimmerEffect
import TextFormat
public func animationCacheFetchFile(context: AccountContext, file: TelegramMediaFile, keyframeOnly: Bool) -> (AnimationCacheFetchOptions) -> Disposable {
public enum AnimationCacheAnimationType {
case still
case lottie
case video
}
public extension AnimationCacheAnimationType {
init(file: TelegramMediaFile) {
if file.isVideoSticker || file.isVideoEmoji {
self = .video
} else if file.isAnimatedSticker {
self = .lottie
} else {
self = .still
}
}
}
public func animationCacheFetchFile(context: AccountContext, resource: MediaResourceReference, type: AnimationCacheAnimationType, keyframeOnly: Bool) -> (AnimationCacheFetchOptions) -> Disposable {
return { options in
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
let source = AnimatedStickerResourceSource(account: context.account, resource: resource.resource, fitzModifier: nil, isVideo: false)
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
guard let result = result else {
return
}
if file.isVideoEmoji || file.isVideoSticker {
switch type {
case .video:
cacheVideoAnimation(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly)
} else if file.isAnimatedSticker {
case .lottie:
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
options.writer.finish()
return
}
cacheLottieAnimation(data: data, width: Int(options.size.width), height: Int(options.size.height), keyframeOnly: keyframeOnly, writer: options.writer, firstFrameOnly: options.firstFrameOnly)
} else {
case .still:
cacheStillSticker(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer)
}
})
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: file.resource).start()
let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: resource).start()
return ActionDisposable {
dataDisposable.dispose()
@ -165,7 +184,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
} else {
let pointSize = self.pointSize
let placeholderColor = self.placeholderColor
self.loadDisposable = self.renderer.loadFirstFrame(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(context: self.context, file: file, keyframeOnly: true), completion: { [weak self] result, isFinal in
self.loadDisposable = self.renderer.loadFirstFrame(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(context: self.context, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: true), completion: { [weak self] result, isFinal in
if !result {
if !isFinal {
return
@ -204,7 +223,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
if file.isAnimatedSticker || file.isVideoEmoji {
let keyframeOnly = self.pixelSize.width >= 120.0
self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: keyframeOnly))
self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(context: context, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: keyframeOnly))
} else {
self.disposable = renderer.add(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: { options in
let dataDisposable = context.account.postbox.mediaBox.resourceData(file.resource).start(next: { result in

View File

@ -30,6 +30,77 @@ private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bund
private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white)
private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white)
public final class EntityKeyboardAnimationData: Equatable {
public enum Id: Hashable {
case file(MediaId)
case stickerPackThumbnail(ItemCollectionId)
}
public enum ItemType {
case still
case lottie
case video
var animationCacheAnimationType: AnimationCacheAnimationType {
switch self {
case .still:
return .still
case .lottie:
return .lottie
case .video:
return .video
}
}
}
public let id: Id
public let type: ItemType
public let resource: MediaResourceReference
public let dimensions: CGSize
public let immediateThumbnailData: Data?
public init(id: Id, type: ItemType, resource: MediaResourceReference, dimensions: CGSize, immediateThumbnailData: Data?) {
self.id = id
self.type = type
self.resource = resource
self.dimensions = dimensions
self.immediateThumbnailData = immediateThumbnailData
}
public convenience init(file: TelegramMediaFile) {
let type: ItemType
if file.isVideoSticker || file.isVideoEmoji {
type = .video
} else if file.isAnimatedSticker {
type = .lottie
} else {
type = .still
}
self.init(id: .file(file.fileId), type: type, resource: .standalone(resource: file.resource), dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData)
}
public static func ==(lhs: EntityKeyboardAnimationData, rhs: EntityKeyboardAnimationData) -> Bool {
if lhs === rhs {
return true
}
if lhs.resource.resource.id != rhs.resource.resource.id {
return false
}
if lhs.dimensions != rhs.dimensions {
return false
}
if lhs.type != rhs.type {
return false
}
if lhs.immediateThumbnailData != rhs.immediateThumbnailData {
return false
}
return true
}
}
private final class PassthroughLayer: CALayer {
var mirrorLayer: CALayer?
@ -1039,7 +1110,7 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager
if let itemRange = itemLayout.visibleItems(for: self.bounds) {
for index in itemRange.lowerBound ..< itemRange.upperBound {
let item = items[index]
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: AnyHashable(0), fileId: item.file?.fileId, staticEmoji: item.staticEmoji)
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: AnyHashable(0), itemId: item.animationData?.id, staticEmoji: item.staticEmoji)
validIds.insert(itemId)
let itemLayer: EmojiPagerContentComponent.View.ItemLayer
@ -1050,7 +1121,7 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager
item: item,
context: context,
attemptSynchronousLoad: attemptSynchronousLoad,
file: item.file,
animationData: item.animationData,
staticEmoji: item.staticEmoji,
cache: cache,
renderer: renderer,
@ -1316,16 +1387,19 @@ public final class EmojiPagerContentComponent: Component {
}
public final class Item: Equatable {
public let file: TelegramMediaFile?
public let animationData: EntityKeyboardAnimationData?
public let itemFile: TelegramMediaFile?
public let staticEmoji: String?
public let subgroupId: Int32?
public init(
file: TelegramMediaFile?,
animationData: EntityKeyboardAnimationData?,
itemFile: TelegramMediaFile?,
staticEmoji: String?,
subgroupId: Int32?
) {
self.file = file
self.animationData = animationData
self.itemFile = itemFile
self.staticEmoji = staticEmoji
self.subgroupId = subgroupId
}
@ -1334,7 +1408,10 @@ public final class EmojiPagerContentComponent: Component {
if lhs === rhs {
return true
}
if lhs.file?.fileId != rhs.file?.fileId {
if lhs.animationData?.resource.resource.id != rhs.animationData?.resource.resource.id {
return false
}
if lhs.itemFile?.fileId != rhs.itemFile?.fileId {
return false
}
if lhs.staticEmoji != rhs.staticEmoji {
@ -1360,7 +1437,7 @@ public final class EmojiPagerContentComponent: Component {
public let hasClear: Bool
public let isExpandable: Bool
public let displayPremiumBadges: Bool
public let headerItem: EntityKeyboardGroupHeaderItem?
public let headerItem: EntityKeyboardAnimationData?
public let items: [Item]
public init(
@ -1375,7 +1452,7 @@ public final class EmojiPagerContentComponent: Component {
hasClear: Bool,
isExpandable: Bool,
displayPremiumBadges: Bool,
headerItem: EntityKeyboardGroupHeaderItem?,
headerItem: EntityKeyboardAnimationData?,
items: [Item]
) {
self.supergroupId = supergroupId
@ -1739,7 +1816,8 @@ public final class EmojiPagerContentComponent: Component {
public init(
context: AccountContext,
file: TelegramMediaFile,
dimensions: CGSize?,
immediateThumbnailData: Data?,
shimmerView: PortalSourceView?,
color: UIColor,
size: CGSize
@ -1760,7 +1838,7 @@ public final class EmojiPagerContentComponent: Component {
let useDirectContent = self.placeholderView == nil
Queue.concurrentDefaultQueue().async { [weak self] in
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: size, scale: min(2.0, UIScreenScale), imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: useDirectContent ? color : .black) {
if let image = generateStickerPlaceholderImage(data: immediateThumbnailData, size: size, scale: min(2.0, UIScreenScale), imageSize: dimensions ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: useDirectContent ? color : .black) {
Queue.mainQueue().async {
guard let strongSelf = self else {
return
@ -1791,16 +1869,16 @@ public final class EmojiPagerContentComponent: Component {
public final class ItemLayer: MultiAnimationRenderTarget {
public struct Key: Hashable {
var groupId: AnyHashable
var fileId: MediaId?
var itemId: EntityKeyboardAnimationData.Id?
var staticEmoji: String?
public init(
groupId: AnyHashable,
fileId: MediaId?,
itemId: EntityKeyboardAnimationData.Id?,
staticEmoji: String?
) {
self.groupId = groupId
self.fileId = fileId
self.itemId = itemId
self.staticEmoji = staticEmoji
}
}
@ -1813,7 +1891,7 @@ public final class EmojiPagerContentComponent: Component {
let item: Item
private let file: TelegramMediaFile?
private let animationData: EntityKeyboardAnimationData?
private let staticEmoji: String?
private let placeholderColor: UIColor
private let size: CGSize
@ -1839,7 +1917,7 @@ public final class EmojiPagerContentComponent: Component {
item: Item,
context: AccountContext,
attemptSynchronousLoad: Bool,
file: TelegramMediaFile?,
animationData: EntityKeyboardAnimationData?,
staticEmoji: String?,
cache: AnimationCache,
renderer: MultiAnimationRenderer,
@ -1849,7 +1927,7 @@ public final class EmojiPagerContentComponent: Component {
onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void
) {
self.item = item
self.file = file
self.animationData = animationData
self.staticEmoji = staticEmoji
self.placeholderColor = placeholderColor
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
@ -1860,20 +1938,20 @@ public final class EmojiPagerContentComponent: Component {
super.init()
if let file = file {
if let animationData = animationData {
let loadAnimation: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: pixelSize.width >= 120.0))
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: pixelSize.width >= 120.0))
}
if attemptSynchronousLoad {
if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize) {
self.updateDisplayPlaceholder(displayPlaceholder: true)
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: true), completion: { [weak self] success, isFinal in
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true), completion: { [weak self] success, isFinal in
if !isFinal {
if !success {
Queue.mainQueue().async {
@ -1905,7 +1983,7 @@ public final class EmojiPagerContentComponent: Component {
loadAnimation()
}
} else {
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, file: file, keyframeOnly: true), completion: { [weak self] success, isFinal in
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true), completion: { [weak self] success, isFinal in
if !isFinal {
if !success {
Queue.mainQueue().async {
@ -1961,7 +2039,7 @@ public final class EmojiPagerContentComponent: Component {
self.item = layer.item
self.file = layer.file
self.animationData = layer.animationData
self.staticEmoji = layer.staticEmoji
self.placeholderColor = layer.placeholderColor
self.size = layer.size
@ -2255,7 +2333,7 @@ public final class EmojiPagerContentComponent: Component {
guard let strongSelf = self, let component = strongSelf.component else {
return nil
}
guard let item = strongSelf.item(atPoint: point), let itemLayer = strongSelf.visibleItemLayers[item.1], let file = item.0.file else {
guard let item = strongSelf.item(atPoint: point), let itemLayer = strongSelf.visibleItemLayers[item.1], let file = item.0.itemFile else {
return nil
}
if itemLayer.displayPlaceholder {
@ -3369,15 +3447,11 @@ public final class EmojiPagerContentComponent: Component {
}
}
let itemId = ItemLayer.Key(groupId: itemGroup.groupId, fileId: item.file?.fileId, staticEmoji: item.staticEmoji)
let itemId = ItemLayer.Key(groupId: itemGroup.groupId, itemId: item.animationData?.id, staticEmoji: item.staticEmoji)
validIds.insert(itemId)
let itemDimensions: CGSize
if let file = item.file {
itemDimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
} else {
itemDimensions = CGSize(width: 512.0, height: 512.0)
}
let itemDimensions: CGSize = item.animationData?.dimensions ?? CGSize(width: 512.0, height: 512.0)
let itemNativeFitSize = itemDimensions.aspectFitted(CGSize(width: itemLayout.nativeItemSize, height: itemLayout.nativeItemSize))
let itemVisibleFitSize = itemDimensions.aspectFitted(CGSize(width: itemLayout.visibleItemSize, height: itemLayout.visibleItemSize))
let itemPlaybackSize = itemDimensions.aspectFitted(CGSize(width: itemLayout.playbackItemSize, height: itemLayout.playbackItemSize))
@ -3398,7 +3472,7 @@ public final class EmojiPagerContentComponent: Component {
item: item,
context: component.context,
attemptSynchronousLoad: attemptSynchronousLoads,
file: item.file,
animationData: item.animationData,
staticEmoji: item.staticEmoji,
cache: component.animationCache,
renderer: component.animationRenderer,
@ -3409,7 +3483,7 @@ public final class EmojiPagerContentComponent: Component {
guard let strongSelf = self else {
return
}
if displayPlaceholder, let file = item.file {
if displayPlaceholder, let animationData = item.animationData {
if let itemLayer = strongSelf.visibleItemLayers[itemId] {
let placeholderView: ItemPlaceholderView
if let current = strongSelf.visibleItemPlaceholderViews[itemId] {
@ -3417,7 +3491,8 @@ public final class EmojiPagerContentComponent: Component {
} else {
placeholderView = ItemPlaceholderView(
context: component.context,
file: file,
dimensions: animationData.dimensions,
immediateThumbnailData: animationData.immediateThumbnailData,
shimmerView: strongSelf.shimmerHostView,
color: placeholderColor,
size: itemNativeFitSize
@ -3482,7 +3557,7 @@ public final class EmojiPagerContentComponent: Component {
itemTransition.setPosition(layer: itemLayer, position: itemPosition)
var badge: ItemLayer.Badge?
if itemGroup.displayPremiumBadges, let file = item.file, file.isPremiumSticker {
if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker {
badge = .premium
}
itemLayer.update(transition: transition, size: itemFrame.size, badge: badge, blurredBadgeColor: UIColor(white: 0.0, alpha: 0.1), blurredBadgeBackgroundColor: keyboardChildEnvironment.theme.list.plainBackgroundColor)
@ -3798,10 +3873,10 @@ public final class EmojiPagerContentComponent: Component {
for itemIndex in 0 ..< itemGroup.items.count {
let item = itemGroup.items[itemIndex]
let itemKey: ItemLayer.Key
if let file = item.file {
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, fileId: file.fileId, staticEmoji: nil)
if let animationData = item.animationData {
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, itemId: animationData.id, staticEmoji: nil)
} else if let staticEmoji = item.staticEmoji {
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, fileId: nil, staticEmoji: staticEmoji)
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, itemId: nil, staticEmoji: staticEmoji)
} else {
continue
}
@ -3971,10 +4046,10 @@ public final class EmojiPagerContentComponent: Component {
}
for j in 0 ..< component.itemGroups[i].items.count {
let itemKey: ItemLayer.Key
if let file = component.itemGroups[i].items[j].file {
itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: file.fileId, staticEmoji: nil)
if let animationData = component.itemGroups[i].items[j].animationData {
itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, itemId: animationData.id, staticEmoji: nil)
} else if let staticEmoji = component.itemGroups[i].items[j].staticEmoji {
itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: nil, staticEmoji: staticEmoji)
itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, itemId: nil, staticEmoji: staticEmoji)
} else {
continue
}
@ -4013,10 +4088,10 @@ public final class EmojiPagerContentComponent: Component {
for itemIndex in 0 ..< itemGroup.items.count {
let item = itemGroup.items[itemIndex]
let itemKey: ItemLayer.Key
if let file = item.file {
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, fileId: file.fileId, staticEmoji: nil)
if let animationData = item.animationData {
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, itemId: animationData.id, staticEmoji: nil)
} else if let staticEmoji = item.staticEmoji {
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, fileId: nil, staticEmoji: staticEmoji)
itemKey = ItemLayer.Key(groupId: itemGroup.groupId, itemId: nil, staticEmoji: staticEmoji)
} else {
continue
}

View File

@ -271,7 +271,7 @@ public final class EntityKeyboardComponent: Component {
isReorderable: false,
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
context: component.emojiContent.context,
item: .file(file: emoji.file),
item: EntityKeyboardAnimationData(file: emoji.file),
isFeatured: false,
isPremiumLocked: false,
animationCache: component.emojiContent.animationCache,
@ -359,13 +359,13 @@ public final class EntityKeyboardComponent: Component {
}
} else {
if !itemGroup.items.isEmpty {
if let file = itemGroup.items[0].file {
if let animationData = itemGroup.items[0].animationData {
topStickerItems.append(EntityKeyboardTopPanelComponent.Item(
id: itemGroup.supergroupId,
isReorderable: !itemGroup.isFeatured,
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
context: stickerContent.context,
item: itemGroup.headerItem ?? .file(file: file),
item: itemGroup.headerItem ?? animationData,
isFeatured: itemGroup.isFeatured,
isPremiumLocked: itemGroup.isPremiumLocked,
animationCache: stickerContent.animationCache,
@ -462,13 +462,13 @@ public final class EntityKeyboardComponent: Component {
))
}
} else {
if let file = itemGroup.items[0].file {
if let animationData = itemGroup.items[0].animationData {
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
id: itemGroup.supergroupId,
isReorderable: !itemGroup.isFeatured,
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
context: component.emojiContent.context,
item: itemGroup.headerItem ?? .file(file: file),
item: itemGroup.headerItem ?? animationData,
isFeatured: itemGroup.isFeatured,
isPremiumLocked: itemGroup.isPremiumLocked,
animationCache: component.emojiContent.animationCache,

View File

@ -14,65 +14,11 @@ import MultilineTextComponent
import LottieAnimationComponent
import MurMurHash32
public enum EntityKeyboardGroupHeaderItem: Equatable {
public enum ThumbnailType {
case still
case lottie
case video
}
case file(file: TelegramMediaFile)
case packThumbnail(resource: MediaResourceReference, immediateThumbnailData: Data?, dimensions: CGSize, type: ThumbnailType)
}
private func fileFromItem(_ item: EntityKeyboardGroupHeaderItem) -> TelegramMediaFile {
let file: TelegramMediaFile
switch item {
case let .file(fileValue):
file = fileValue
case let .packThumbnail(resource, immediateThumbnailData, _, type):
let mimeType: String
let attributes: [TelegramMediaFileAttribute]
switch type {
case .still:
mimeType = "image/webp"
attributes = [.FileName(fileName: "image.webp")]
case .lottie:
mimeType = "application/x-tgsticker"
attributes = [
.FileName(fileName: "sticker.tgs"),
.Sticker(displayText: "", packReference: nil, maskData: nil)
]
case .video:
mimeType = "video/webm"
attributes = [
.FileName(fileName: "sticker.webm"),
.Sticker(displayText: "", packReference: nil, maskData: nil)
]
}
file = TelegramMediaFile(
fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: Int64(murMurHashString32(resource.resource.id.stringRepresentation))),
partialReference: nil,
resource: resource.resource as! TelegramMediaResource,
previewRepresentations: [],
videoThumbnails: [],
immediateThumbnailData: immediateThumbnailData,
mimeType: mimeType,
size: nil,
attributes: attributes
)
}
return file
}
final class EntityKeyboardAnimationTopPanelComponent: Component {
typealias EnvironmentType = EntityKeyboardTopPanelItemEnvironment
let context: AccountContext
let item: EntityKeyboardGroupHeaderItem
let item: EntityKeyboardAnimationData
let isFeatured: Bool
let isPremiumLocked: Bool
let animationCache: AnimationCache
@ -83,7 +29,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
init(
context: AccountContext,
item: EntityKeyboardGroupHeaderItem,
item: EntityKeyboardAnimationData,
isFeatured: Bool,
isPremiumLocked: Bool,
animationCache: AnimationCache,
@ -159,27 +105,20 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
let itemEnvironment = environment[EntityKeyboardTopPanelItemEnvironment.self].value
let dimensions: CGSize
switch component.item {
case let .file(file):
dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
case let .packThumbnail(_, _, dimensionsValue, _):
dimensions = dimensionsValue
}
let dimensions: CGSize = component.item.dimensions
let displaySize = dimensions.aspectFitted(CGSize(width: 44.0, height: 44.0))
if self.itemLayer == nil {
let file = fileFromItem(component.item)
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
item: EmojiPagerContentComponent.Item(
file: file,
animationData: component.item,
itemFile: nil,
staticEmoji: nil,
subgroupId: nil
),
context: component.context,
attemptSynchronousLoad: false,
file: file,
animationData: component.item,
staticEmoji: nil,
cache: component.animationCache,
renderer: component.animationRenderer,
@ -267,7 +206,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
if self.placeholderView == nil, let component = self.component {
let placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
context: component.context,
file: fileFromItem(component.item),
dimensions: component.item.dimensions,
immediateThumbnailData: component.item.immediateThumbnailData,
shimmerView: nil,
color: component.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08),
size: CGSize(width: 28.0, height: 28.0)

View File

@ -115,7 +115,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
var isPremiumLocked: Bool
var isFeatured: Bool
var isExpandable: Bool
var headerItem: EntityKeyboardGroupHeaderItem?
var headerItem: EntityKeyboardAnimationData?
var items: [EmojiPagerContentComponent.Item]
}
var itemGroups: [ItemGroup] = []
@ -146,13 +146,15 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
switch item.content {
case let .file(file):
resultItem = EmojiPagerContentComponent.Item(
file: file,
animationData: EntityKeyboardAnimationData(file: file),
itemFile: file,
staticEmoji: nil,
subgroupId: nil
)
case let .text(text):
resultItem = EmojiPagerContentComponent.Item(
file: nil,
animationData: nil,
itemFile: nil,
staticEmoji: text,
subgroupId: nil
)
@ -172,7 +174,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
let groupId: AnyHashable = "static"
for emojiString in list {
let resultItem = EmojiPagerContentComponent.Item(
file: nil,
animationData: nil,
itemFile: nil,
staticEmoji: emojiString,
subgroupId: subgroupId.rawValue
)
@ -197,7 +200,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
continue
}
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
itemFile: item.file,
staticEmoji: nil,
subgroupId: nil
)
@ -214,13 +218,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemGroupIndexById[groupId] = itemGroups.count
var title = ""
var headerItem: EntityKeyboardGroupHeaderItem?
var headerItem: EntityKeyboardAnimationData?
inner: for (id, info, _) in view.collectionInfos {
if id == entry.index.collectionId, let info = info as? StickerPackCollectionInfo {
title = info.title
if let thumbnail = info.thumbnail {
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
@ -229,7 +233,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
type = .still
}
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource), immediateThumbnailData: info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource),
dimensions: thumbnail.dimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData
)
}
break inner
@ -247,7 +257,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
for item in featuredEmojiPack.topItems {
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
itemFile: item.file,
staticEmoji: nil,
subgroupId: nil
)
@ -263,10 +274,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} else {
itemGroupIndexById[groupId] = itemGroups.count
var headerItem: EntityKeyboardGroupHeaderItem?
var headerItem: EntityKeyboardAnimationData?
if let thumbnail = featuredEmojiPack.info.thumbnail {
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
let info = featuredEmojiPack.info
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
@ -275,7 +286,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
type = .still
}
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash), resource: thumbnail.resource), immediateThumbnailData: featuredEmojiPack.info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource),
dimensions: thumbnail.dimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData
)
}
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, headerItem: headerItem, items: [resultItem]))
@ -366,7 +383,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
var isPremiumLocked: Bool
var isFeatured: Bool
var displayPremiumBadges: Bool
var headerItem: EntityKeyboardGroupHeaderItem?
var headerItem: EntityKeyboardAnimationData?
var items: [EmojiPagerContentComponent.Item]
}
var itemGroups: [ItemGroup] = []
@ -405,7 +422,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
}
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
itemFile: item.file,
staticEmoji: nil,
subgroupId: nil
)
@ -439,7 +457,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
}
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
itemFile: item.file,
staticEmoji: nil,
subgroupId: nil
)
@ -464,7 +483,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
}
let resultItem = EmojiPagerContentComponent.Item(
file: item.media,
animationData: EntityKeyboardAnimationData(file: item.media),
itemFile: item.media,
staticEmoji: nil,
subgroupId: nil
)
@ -512,7 +532,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
processedIds.insert(item.file.fileId)
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
itemFile: item.file,
staticEmoji: nil,
subgroupId: nil
)
@ -532,7 +553,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
continue
}
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
itemFile: item.file,
staticEmoji: nil,
subgroupId: nil
)
@ -543,13 +565,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemGroupIndexById[groupId] = itemGroups.count
var title = ""
var headerItem: EntityKeyboardGroupHeaderItem?
var headerItem: EntityKeyboardAnimationData?
inner: for (id, info, _) in view.collectionInfos {
if id == groupId, let info = info as? StickerPackCollectionInfo {
title = info.title
if let thumbnail = info.thumbnail {
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
@ -558,7 +580,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
type = .still
}
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource), immediateThumbnailData: info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource),
dimensions: thumbnail.dimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData
)
}
break inner
@ -575,7 +603,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
for item in featuredStickerPack.topItems {
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
animationData: EntityKeyboardAnimationData(file: item.file),
itemFile: item.file,
staticEmoji: nil,
subgroupId: nil
)
@ -592,10 +621,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemGroupIndexById[groupId] = itemGroups.count
let subtitle: String = strings.StickerPack_StickerCount(Int32(featuredStickerPack.info.count))
var headerItem: EntityKeyboardGroupHeaderItem?
var headerItem: EntityKeyboardAnimationData?
if let thumbnail = featuredStickerPack.info.thumbnail {
let type: EntityKeyboardGroupHeaderItem.ThumbnailType
let info = featuredStickerPack.info
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
@ -604,7 +634,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
type = .still
}
headerItem = .packThumbnail(resource: MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), resource: thumbnail.resource), immediateThumbnailData: featuredStickerPack.info.immediateThumbnailData, dimensions: thumbnail.dimensions.cgSize, type: type)
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource),
dimensions: thumbnail.dimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData
)
}
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, subtitle: subtitle, actionButtonTitle: strings.Stickers_Install, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, headerItem: headerItem, items: [resultItem]))
@ -1055,7 +1091,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
return
}
if let file = item.file {
if let file = item.itemFile {
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
@ -1215,7 +1251,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
return
}
guard let file = item.file else {
guard let file = item.itemFile else {
return
}
@ -2041,7 +2077,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
return
}
if let file = item.file {
if let file = item.itemFile {
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {

View File

@ -114,6 +114,15 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState) ->
if inputText.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) == nil {
return [(NSRange(location: 0, length: inputString.length - (string.count - trimmedString.count)), [.emoji], nil)]
}
} else {
let activeString = inputText.attributedSubstring(from: NSRange(location: 0, length: inputState.selectionRange.upperBound))
if let lastCharacter = activeString.string.last, String(lastCharacter).isSingleEmoji {
let matchLength = (String(lastCharacter) as NSString).length
if activeString.attribute(ChatTextInputAttributes.customEmoji, at: activeString.length - matchLength, effectiveRange: nil) == nil {
return [(NSRange(location: inputState.selectionRange.upperBound - matchLength, length: matchLength), [.emojiSearch], nil)]
}
}
}
var possibleTypes = PossibleContextQueryTypes([.command, .mention, .hashtag, .emojiSearch])

View File

@ -328,32 +328,16 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
return signal |> then(contextBot)
case let .emojiSearch(query, languageCode, range):
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: query.count < 2)
if !languageCode.lowercased().hasPrefix("en") {
signal = signal
|> mapToSignal { keywords in
return .single(keywords)
|> then(
context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|> map { englishKeywords in
return keywords + englishKeywords
}
)
if query.isSingleEmoji {
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
guard case let .user(user) = peer else {
return false
}
return user.isPremium
}
}
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
guard case let .user(user) = peer else {
return false
}
return user.isPremium
}
|> distinctUntilChanged
return signal
|> castError(ChatContextQueryError.self)
|> mapToSignal { keywords -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in
|> distinctUntilChanged
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
hasPremium
@ -361,13 +345,6 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] in
var result: [(String, TelegramMediaFile?, String)] = []
var allEmoticons: [String: String] = [:]
for keyword in keywords {
for emoticon in keyword.emoticons {
allEmoticons[emoticon] = keyword.keyword
}
}
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
continue
@ -375,9 +352,9 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
for attribute in item.file.attributes {
switch attribute {
case let .CustomEmoji(_, alt, _):
if !alt.isEmpty, let keyword = allEmoticons[alt] {
if alt == query {
if !item.file.isPremiumEmoji || hasPremium {
result.append((alt, item.file, keyword))
result.append((alt, item.file, alt))
}
}
default:
@ -385,18 +362,83 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
}
}
}
for keyword in keywords {
for emoticon in keyword.emoticons {
result.append((emoticon, nil, keyword.keyword))
}
}
return result
}
|> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in return .emojis(result, range) }
}
|> castError(ChatContextQueryError.self)
} else {
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: query.count < 2)
if !languageCode.lowercased().hasPrefix("en") {
signal = signal
|> mapToSignal { keywords in
return .single(keywords)
|> then(
context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|> map { englishKeywords in
return keywords + englishKeywords
}
)
}
}
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
guard case let .user(user) = peer else {
return false
}
return user.isPremium
}
|> distinctUntilChanged
return signal
|> castError(ChatContextQueryError.self)
|> mapToSignal { keywords -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
hasPremium
)
|> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] in
var result: [(String, TelegramMediaFile?, String)] = []
var allEmoticons: [String: String] = [:]
for keyword in keywords {
for emoticon in keyword.emoticons {
allEmoticons[emoticon] = keyword.keyword
}
}
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
continue
}
for attribute in item.file.attributes {
switch attribute {
case let .CustomEmoji(_, alt, _):
if !alt.isEmpty, let keyword = allEmoticons[alt] {
if !item.file.isPremiumEmoji || hasPremium {
result.append((alt, item.file, keyword))
}
}
default:
break
}
}
}
for keyword in keywords {
for emoticon in keyword.emoticons {
result.append((emoticon, nil, keyword.keyword))
}
}
return result
}
|> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in return .emojis(result, range) }
}
|> castError(ChatContextQueryError.self)
}
}
}
}

View File

@ -461,7 +461,7 @@ final class CustomEmojiContainerView: UIView {
let itemSize: CGFloat = floor(24.0 * fontSize / 17.0)
let size = CGSize(width: itemSize, height: itemSize)
view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0)), size: size)
view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0) + 1.0), size: size)
validKeys.insert(key)
}

View File

@ -20,8 +20,8 @@ private enum EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
}
private func backgroundCenterImage(_ theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 55.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
return generateImage(CGSize(width: 8.0, height: 16.0), rotatedContext: { size, context in
/*context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
let lineWidth = UIScreenPixel
@ -37,8 +37,17 @@ private func backgroundCenterImage(_ theme: PresentationTheme) -> UIImage? {
context.translateBy(x: -460.5, y: -lineWidth / 2.0 - 364.0 + 27.0)
context.move(to: CGPoint(x: 0.0, y: lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0))
context.strokePath()
})
context.strokePath()*/
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
let lineWidth = UIScreenPixel
context.setLineWidth(lineWidth)
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.height, height: size.height)))
context.stroke(CGRect(origin: CGPoint(x: -lineWidth / 2.0, y: lineWidth / 2.0), size: CGSize(width: size.height + lineWidth, height: size.height - lineWidth)))
})?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 8)
}
private func backgroundLeftImage(_ theme: PresentationTheme) -> UIImage? {
@ -192,8 +201,10 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
inner: for (range, type, _) in textInputStateContextQueryRangeAndType(textInputState) {
if type == [.emojiSearch] {
var range = range
range.location -= 1
range.length += 1
if textInputState.inputText.attributedSubstring(from: range).string.hasPrefix(":") {
range.location -= 1
range.length += 1
}
hashtagQueryRange = range
break inner
}
@ -267,7 +278,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
self.presentationInterfaceState = interfaceState
let sideInsets: CGFloat = 10.0 + leftInset
let contentWidth = min(size.width - sideInsets - sideInsets, max(24.0, CGFloat(self.currentEntries?.count ?? 0) * 45.0))
let contentWidth = min(size.width - sideInsets - sideInsets, max(24.0, CGFloat(self.currentEntries?.count ?? 0) * 45.0 + 5.0))
var contentLeftInset: CGFloat = 40.0
var leftOffset: CGFloat = 0.0
@ -279,7 +290,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
let backgroundFrame = CGRect(origin: CGPoint(x: sideInsets + leftOffset, y: size.height - 55.0 + 4.0), size: CGSize(width: contentWidth, height: 55.0))
let backgroundLeftFrame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: contentLeftInset, height: backgroundFrame.size.height - 10.0 + UIScreenPixel))
let backgroundCenterFrame = CGRect(origin: CGPoint(x: backgroundLeftFrame.maxX, y: backgroundFrame.minY), size: CGSize(width: 30.0, height: 55.0))
let backgroundCenterFrame = CGRect(origin: CGPoint(x: backgroundLeftFrame.maxX, y: backgroundFrame.minY), size: CGSize(width: 30.0, height: backgroundFrame.size.height - 10.0 + UIScreenPixel))
let backgroundRightFrame = CGRect(origin: CGPoint(x: backgroundCenterFrame.maxX, y: backgroundFrame.minY), size: CGSize(width: max(0.0, backgroundFrame.minX + backgroundFrame.size.width - backgroundCenterFrame.maxX), height: backgroundFrame.size.height - 10.0 + UIScreenPixel))
transition.updateFrame(node: self.backgroundLeftNode, frame: backgroundLeftFrame)
transition.updateFrame(node: self.backgroundNode, frame: backgroundCenterFrame)