mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Emoji 1.1 WIP
This commit is contained in:
parent
47239681a7
commit
095b9d5058
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 ¤t0, const int16x4_t ¤t1, const int16x4_t ¤t2, const int16x4_t ¤t3, 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user