Emoji input improvements

This commit is contained in:
Ali 2022-07-21 03:30:22 +02:00
parent fdea4fc967
commit 806536a4cd
14 changed files with 1604 additions and 320 deletions

View File

@ -107,6 +107,7 @@
"PUSH_MESSAGE_THEME" = "%1$@|changed chat theme to %2$@"; "PUSH_MESSAGE_THEME" = "%1$@|changed chat theme to %2$@";
"PUSH_MESSAGE_NOTHEME" = "%1$@|disabled chat theme"; "PUSH_MESSAGE_NOTHEME" = "%1$@|disabled chat theme";
"PUSH_MESSAGE_RECURRING_PAY" = "%1$@|You were charged %2$@"; "PUSH_MESSAGE_RECURRING_PAY" = "%1$@|You were charged %2$@";
"CHAT_MESSAGE_RECURRING_PAY" = "%1$@|You were charged %2$@";
"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@"; "PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@";
"PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message"; "PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message";

View File

@ -5,9 +5,18 @@
#import <ImageDCT/YuvConversion.h> #import <ImageDCT/YuvConversion.h>
@interface ImageDCTTable : NSObject
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality isChroma:(bool)isChroma;
- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data;
- (NSData * _Nonnull)serializedData;
@end
@interface ImageDCT : NSObject @interface ImageDCT : NSObject
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality; - (instancetype _Nonnull)initWithTable:(ImageDCTTable * _Nonnull)table;
- (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)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)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));

View File

@ -118,6 +118,16 @@ static DCTELEM std_luminance_quant_tbl[DCTSIZE2] = {
49, 64, 78, 87, 103, 121, 120, 101, 49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99 72, 92, 95, 98, 112, 100, 103, 99
}; };
static DCTELEM std_chrominance_quant_tbl[DCTSIZE2] = {
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
};
int jpeg_quality_scaling(int quality) int jpeg_quality_scaling(int quality)
/* Convert a user-specified quality rating to a percentage scaling factor /* Convert a user-specified quality rating to a percentage scaling factor
@ -143,7 +153,7 @@ int jpeg_quality_scaling(int quality)
return quality; return quality;
} }
void jpeg_add_quant_table(DCTELEM *qtable, DCTELEM *basicTable, int scale_factor, bool forceBaseline) void jpeg_add_quant_table(DCTELEM *qtable, DCTELEM const *basicTable, int scale_factor, bool forceBaseline)
/* Define a quantization table equal to the basic_table times /* Define a quantization table equal to the basic_table times
* a scale factor (given as a percentage). * a scale factor (given as a percentage).
* If force_baseline is TRUE, the computed quantization table entries * If force_baseline is TRUE, the computed quantization table entries
@ -164,7 +174,7 @@ void jpeg_add_quant_table(DCTELEM *qtable, DCTELEM *basicTable, int scale_factor
} }
} }
void jpeg_set_quality(DCTELEM *qtable, int quality) void jpeg_set_quality(DCTELEM *qtable, DCTELEM const *basicTable, int quality)
/* Set or change the 'quality' (quantization) setting, using default tables. /* Set or change the 'quality' (quantization) setting, using default tables.
* This is the standard quality-adjusting entry point for typical user * This is the standard quality-adjusting entry point for typical user
* interfaces; only those who want detailed control over quantization tables * interfaces; only those who want detailed control over quantization tables
@ -175,10 +185,10 @@ void jpeg_set_quality(DCTELEM *qtable, int quality)
quality = jpeg_quality_scaling(quality); quality = jpeg_quality_scaling(quality);
/* Set up standard quality tables */ /* Set up standard quality tables */
jpeg_add_quant_table(qtable, std_luminance_quant_tbl, quality, false); jpeg_add_quant_table(qtable, basicTable, quality, false);
} }
void getDivisors(DCTELEM *dtbl, DCTELEM *qtable) { void getDivisors(DCTELEM *dtbl, DCTELEM const *qtable) {
#define CONST_BITS 14 #define CONST_BITS 14
#define RIGHT_SHIFT(x, shft) ((x) >> (shft)) #define RIGHT_SHIFT(x, shft) ((x) >> (shft))
@ -234,22 +244,15 @@ void quantize(JCOEFPTR coef_block, DCTELEM *divisors, DCTELEM *workspace)
} }
} }
void generateForwardDctData(int quality, std::vector<uint8_t> &data) { void generateForwardDctData(DCTELEM const *qtable, std::vector<uint8_t> &data) {
data.resize(DCTSIZE2 * 4 * sizeof(DCTELEM)); data.resize(DCTSIZE2 * 4 * sizeof(DCTELEM));
DCTELEM qtable[DCTSIZE2];
jpeg_set_quality(qtable, quality);
getDivisors((DCTELEM *)data.data(), qtable); getDivisors((DCTELEM *)data.data(), qtable);
} }
void generateInverseDctData(int quality, std::vector<uint8_t> &data) { void generateInverseDctData(DCTELEM const *qtable, std::vector<uint8_t> &data) {
data.resize(DCTSIZE2 * sizeof(IFAST_MULT_TYPE)); data.resize(DCTSIZE2 * sizeof(IFAST_MULT_TYPE));
IFAST_MULT_TYPE *ifmtbl = (IFAST_MULT_TYPE *)data.data(); IFAST_MULT_TYPE *ifmtbl = (IFAST_MULT_TYPE *)data.data();
DCTELEM qtable[DCTSIZE2];
jpeg_set_quality(qtable, quality);
#define CONST_BITS 14 #define CONST_BITS 14
static const int16_t aanscales[DCTSIZE2] = { static const int16_t aanscales[DCTSIZE2] = {
/* precomputed values scaled up by 14 bits */ /* precomputed values scaled up by 14 bits */
@ -338,13 +341,32 @@ void performInverseDct(int16_t const * coefficients, uint8_t *pixels, int width,
namespace dct { namespace dct {
DCTTable DCTTable::generate(int quality, bool isChroma) {
DCTTable result;
result.table.resize(DCTSIZE2);
if (isChroma) {
jpeg_set_quality(result.table.data(), std_chrominance_quant_tbl, quality);
} else {
jpeg_set_quality(result.table.data(), std_luminance_quant_tbl, quality);
}
return result;
}
DCTTable DCTTable::initializeEmpty() {
DCTTable result;
result.table.resize(DCTSIZE2);
return result;
}
class DCTInternal { class DCTInternal {
public: public:
DCTInternal(int quality) { DCTInternal(DCTTable const &dctTable) {
auxiliaryData = createDctAuxiliaryData(); auxiliaryData = createDctAuxiliaryData();
generateForwardDctData(quality, forwardDctData); generateForwardDctData(dctTable.table.data(), forwardDctData);
generateInverseDctData(quality, inverseDctData); generateInverseDctData(dctTable.table.data(), inverseDctData);
} }
~DCTInternal() { ~DCTInternal() {
@ -357,8 +379,8 @@ public:
std::vector<uint8_t> inverseDctData; std::vector<uint8_t> inverseDctData;
}; };
DCT::DCT(int quality) { DCT::DCT(DCTTable const &dctTable) {
_internal = new DCTInternal(quality); _internal = new DCTInternal(dctTable);
} }
DCT::~DCT() { DCT::~DCT() {

View File

@ -3,15 +3,23 @@
#include "DCTCommon.h" #include "DCTCommon.h"
#include <vector>
#include <stdint.h> #include <stdint.h>
namespace dct { namespace dct {
class DCTInternal; class DCTInternal;
struct DCTTable {
static DCTTable generate(int quality, bool isChroma);
static DCTTable initializeEmpty();
std::vector<int16_t> table;
};
class DCT { class DCT {
public: public:
DCT(int quality); DCT(DCTTable const &dctTable);
~DCT(); ~DCT();
void forward(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow); void forward(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow);

View File

@ -4,6 +4,41 @@
#include "DCT.h" #include "DCT.h"
@interface ImageDCTTable () {
@public
dct::DCTTable _table;
}
@end
@implementation ImageDCTTable
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality isChroma:(bool)isChroma {
self = [super init];
if (self != nil) {
_table = dct::DCTTable::generate((int)quality, isChroma);
}
return self;
}
- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data {
self = [super init];
if (self != nil) {
_table = dct::DCTTable::initializeEmpty();
if (data.length != _table.table.size() * 2) {
return nil;
}
memcpy(_table.table.data(), data.bytes, data.length);
}
return self;
}
- (NSData * _Nonnull)serializedData {
return [[NSData alloc] initWithBytes:_table.table.data() length:_table.table.size() * 2];
}
@end
@interface ImageDCT () { @interface ImageDCT () {
std::unique_ptr<dct::DCT> _dct; std::unique_ptr<dct::DCT> _dct;
} }
@ -12,10 +47,10 @@
@implementation ImageDCT @implementation ImageDCT
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality { - (instancetype _Nonnull)initWithTable:(ImageDCTTable * _Nonnull)table {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_dct = std::unique_ptr<dct::DCT>(new dct::DCT((int)quality)); _dct = std::unique_ptr<dct::DCT>(new dct::DCT(table->_table));
} }
return self; return self;
} }

View File

@ -294,10 +294,10 @@ private final class AnimationCacheItemWriterInternal {
} }
let dctData: DctData let dctData: DctData
if let current = self.currentDctData, current.quality == self.dctQuality { if let current = self.currentDctData {
dctData = current dctData = current
} else { } else {
dctData = DctData(quality: self.dctQuality) dctData = DctData(generatingTablesAtQuality: self.dctQuality)
self.currentDctData = dctData self.currentDctData = dctData
} }
@ -310,11 +310,18 @@ private final class AnimationCacheItemWriterInternal {
yuvaSurface.dct(dctData: dctData, target: dctCoefficients) yuvaSurface.dct(dctData: dctData, target: dctCoefficients)
if isFirstFrame { if isFirstFrame {
self.file.write(3 as UInt32) self.file.write(4 as UInt32)
self.file.write(UInt32(dctCoefficients.yPlane.width)) self.file.write(UInt32(dctCoefficients.yPlane.width))
self.file.write(UInt32(dctCoefficients.yPlane.height)) self.file.write(UInt32(dctCoefficients.yPlane.height))
self.file.write(UInt32(dctData.quality))
let lumaDctTable = dctData.lumaTable.serializedData()
self.file.write(UInt32(lumaDctTable.count))
let _ = self.file.write(lumaDctTable)
let chromaDctTable = dctData.chromaTable.serializedData()
self.file.write(UInt32(chromaDctTable.count))
let _ = self.file.write(chromaDctTable)
self.contentLengthOffset = Int(self.file.position()) self.contentLengthOffset = Int(self.file.position())
self.file.write(0 as UInt32) self.file.write(0 as UInt32)
@ -501,10 +508,10 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
} }
let dctData: DctData let dctData: DctData
if let current = self.currentDctData, current.quality == self.dctQuality { if let current = self.currentDctData {
dctData = current dctData = current
} else { } else {
dctData = DctData(quality: self.dctQuality) dctData = DctData(generatingTablesAtQuality: self.dctQuality)
self.currentDctData = dctData self.currentDctData = dctData
} }
@ -526,11 +533,18 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
yuvaSurface.dct(dctData: dctData, target: dctCoefficients) yuvaSurface.dct(dctData: dctData, target: dctCoefficients)
if isFirstFrame { if isFirstFrame {
file.write(3 as UInt32) file.write(4 as UInt32)
file.write(UInt32(dctCoefficients.yPlane.width)) file.write(UInt32(dctCoefficients.yPlane.width))
file.write(UInt32(dctCoefficients.yPlane.height)) file.write(UInt32(dctCoefficients.yPlane.height))
file.write(UInt32(dctData.quality))
let lumaDctTable = dctData.lumaTable.serializedData()
file.write(UInt32(lumaDctTable.count))
let _ = file.write(lumaDctTable)
let chromaDctTable = dctData.chromaTable.serializedData()
file.write(UInt32(chromaDctTable.count))
let _ = file.write(chromaDctTable)
self.contentLengthOffset = Int(file.position()) self.contentLengthOffset = Int(file.position())
file.write(0 as UInt32) file.write(0 as UInt32)
@ -652,10 +666,10 @@ private final class AnimationCacheItemAccessor {
private var currentFrame: CurrentFrame? private var currentFrame: CurrentFrame?
private var currentYUVASurface: ImageYUVA420? private var currentYUVASurface: ImageYUVA420?
private var currentDctData: DctData private let currentDctData: DctData
private var sharedDctCoefficients: DctCoefficientsYUVA420? private var sharedDctCoefficients: DctCoefficientsYUVA420?
init(data: Data, range: Range<Int>, frameMapping: [FrameInfo], width: Int, height: Int, dctQuality: Int) { init(data: Data, range: Range<Int>, frameMapping: [FrameInfo], width: Int, height: Int, dctData: DctData) {
self.data = data self.data = data
self.range = range self.range = range
self.width = width self.width = width
@ -673,7 +687,7 @@ private final class AnimationCacheItemAccessor {
self.frameMapping = resultFrameMapping self.frameMapping = resultFrameMapping
self.durationMapping = durationMapping self.durationMapping = durationMapping
self.currentDctData = DctData(quality: dctQuality) self.currentDctData = dctData
} }
private func loadNextFrame() { private func loadNextFrame() {
@ -835,6 +849,16 @@ private final class AnimationCacheItemAccessor {
} }
} }
private func readData(data: Data, offset: Int, count: Int) -> Data {
var result = Data(count: count)
result.withUnsafeMutableBytes { bytes -> Void in
data.withUnsafeBytes { dataBytes -> Void in
memcpy(bytes.baseAddress!, dataBytes.baseAddress!.advanced(by: offset), count)
}
}
return result
}
private func readUInt32(data: Data, offset: Int) -> UInt32 { private func readUInt32(data: Data, offset: Int) -> UInt32 {
var value: UInt32 = 0 var value: UInt32 = 0
withUnsafeMutableBytes(of: &value, { bytes -> Void in withUnsafeMutableBytes(of: &value, { bytes -> Void in
@ -1071,7 +1095,7 @@ private func loadItem(path: String) throws -> AnimationCacheItem {
} }
let formatVersion = readUInt32(data: compressedData, offset: offset) let formatVersion = readUInt32(data: compressedData, offset: offset)
offset += 4 offset += 4
if formatVersion != 3 { if formatVersion != 4 {
throw LoadItemError.dataError throw LoadItemError.dataError
} }
@ -1090,9 +1114,27 @@ private func loadItem(path: String) throws -> AnimationCacheItem {
if offset + 4 > dataLength { if offset + 4 > dataLength {
throw LoadItemError.dataError throw LoadItemError.dataError
} }
let dctQuality = readUInt32(data: compressedData, offset: offset) let dctLumaTableLength = readUInt32(data: compressedData, offset: offset)
offset += 4 offset += 4
if offset + Int(dctLumaTableLength) > dataLength {
throw LoadItemError.dataError
}
let dctLumaData = readData(data: compressedData, offset: offset, count: Int(dctLumaTableLength))
offset += Int(dctLumaTableLength)
if offset + 4 > dataLength {
throw LoadItemError.dataError
}
let dctChromaTableLength = readUInt32(data: compressedData, offset: offset)
offset += 4
if offset + Int(dctChromaTableLength) > dataLength {
throw LoadItemError.dataError
}
let dctChromaData = readData(data: compressedData, offset: offset, count: Int(dctChromaTableLength))
offset += Int(dctChromaTableLength)
if offset + 4 > dataLength { if offset + 4 > dataLength {
throw LoadItemError.dataError throw LoadItemError.dataError
} }
@ -1119,7 +1161,11 @@ private func loadItem(path: String) throws -> AnimationCacheItem {
frameMapping.append(AnimationCacheItemAccessor.FrameInfo(duration: Double(frameDuration))) frameMapping.append(AnimationCacheItemAccessor.FrameInfo(duration: Double(frameDuration)))
} }
let itemAccessor = AnimationCacheItemAccessor(data: compressedData, range: compressedFrameDataRange, frameMapping: frameMapping, width: Int(width), height: Int(height), dctQuality: Int(dctQuality)) guard let dctData = DctData(lumaTable: dctLumaData, chromaTable: dctChromaData) else {
throw LoadItemError.dataError
}
let itemAccessor = AnimationCacheItemAccessor(data: compressedData, range: compressedFrameDataRange, frameMapping: frameMapping, width: Int(width), height: Int(height), dctData: dctData)
return AnimationCacheItem(numFrames: frameMapping.count, advanceImpl: { advance, requestedFormat in return AnimationCacheItem(numFrames: frameMapping.count, advanceImpl: { advance, requestedFormat in
return itemAccessor.advance(advance: advance, requestedFormat: requestedFormat) return itemAccessor.advance(advance: advance, requestedFormat: requestedFormat)
@ -1397,7 +1443,9 @@ public final class AnimationCacheImpl: AnimationCache {
let itemFirstFramePath = "\(itemDirectoryPath)/\(sourceIdPath.fileName)-f" let itemFirstFramePath = "\(itemDirectoryPath)/\(sourceIdPath.fileName)-f"
if FileManager.default.fileExists(atPath: itemFirstFramePath) { if FileManager.default.fileExists(atPath: itemFirstFramePath) {
return try? loadItem(path: itemFirstFramePath) if let item = try? loadItem(path: itemFirstFramePath) {
return item
}
} }
if let adaptationItemPath = findHigherResolutionFileForAdaptation(itemDirectoryPath: itemDirectoryPath, baseName: "\(hashString)_", baseSuffix: "-f", width: Int(size.width), height: Int(size.height)) { if let adaptationItemPath = findHigherResolutionFileForAdaptation(itemDirectoryPath: itemDirectoryPath, baseName: "\(hashString)_", baseSuffix: "-f", width: Int(size.width), height: Int(size.height)) {

View File

@ -152,12 +152,33 @@ extension ImageYUVA420 {
} }
final class DctData { final class DctData {
let quality: Int let lumaTable: ImageDCTTable
let dct: ImageDCT let lumaDct: ImageDCT
init(quality: Int) { let chromaTable: ImageDCTTable
self.quality = quality let chromaDct: ImageDCT
self.dct = ImageDCT(quality: quality)
init?(lumaTable: Data, chromaTable: Data) {
guard let lumaTableData = ImageDCTTable(data: lumaTable) else {
return nil
}
guard let chromaTableData = ImageDCTTable(data: chromaTable) else {
return nil
}
self.lumaTable = lumaTableData
self.lumaDct = ImageDCT(table: lumaTableData)
self.chromaTable = chromaTableData
self.chromaDct = ImageDCT(table: chromaTableData)
}
init(generatingTablesAtQuality quality: Int) {
self.lumaTable = ImageDCTTable(quality: quality, isChroma: false)
self.lumaDct = ImageDCT(table: self.lumaTable)
self.chromaTable = ImageDCTTable(quality: quality, isChroma: true)
self.chromaDct = ImageDCT(table: self.chromaTable)
} }
} }
@ -168,19 +189,24 @@ extension ImageYUVA420 {
for i in 0 ..< 4 { for i in 0 ..< 4 {
let sourcePlane: ImagePlane let sourcePlane: ImagePlane
let targetPlane: DctCoefficientPlane let targetPlane: DctCoefficientPlane
let isChroma: Bool
switch i { switch i {
case 0: case 0:
sourcePlane = self.yPlane sourcePlane = self.yPlane
targetPlane = target.yPlane targetPlane = target.yPlane
isChroma = false
case 1: case 1:
sourcePlane = self.uPlane sourcePlane = self.uPlane
targetPlane = target.uPlane targetPlane = target.uPlane
isChroma = true
case 2: case 2:
sourcePlane = self.vPlane sourcePlane = self.vPlane
targetPlane = target.vPlane targetPlane = target.vPlane
isChroma = true
case 3: case 3:
sourcePlane = self.aPlane sourcePlane = self.aPlane
targetPlane = target.aPlane targetPlane = target.aPlane
isChroma = false
default: default:
preconditionFailure() preconditionFailure()
} }
@ -191,7 +217,8 @@ extension ImageYUVA420 {
targetPlane.data.withUnsafeMutableBytes { bytes in targetPlane.data.withUnsafeMutableBytes { bytes in
let coefficients = bytes.baseAddress!.assumingMemoryBound(to: Int16.self) let coefficients = bytes.baseAddress!.assumingMemoryBound(to: Int16.self)
dctData.dct.forward(withPixels: sourcePixels, coefficients: coefficients, width: sourcePlane.width, height: sourcePlane.height, bytesPerRow: sourcePlane.bytesPerRow) let dct = isChroma ? dctData.chromaDct : dctData.lumaDct
dct.forward(withPixels: sourcePixels, coefficients: coefficients, width: sourcePlane.width, height: sourcePlane.height, bytesPerRow: sourcePlane.bytesPerRow)
} }
} }
} }
@ -211,19 +238,24 @@ extension DctCoefficientsYUVA420 {
for i in 0 ..< 4 { for i in 0 ..< 4 {
let sourcePlane: DctCoefficientPlane let sourcePlane: DctCoefficientPlane
let targetPlane: ImagePlane let targetPlane: ImagePlane
let isChroma: Bool
switch i { switch i {
case 0: case 0:
sourcePlane = self.yPlane sourcePlane = self.yPlane
targetPlane = target.yPlane targetPlane = target.yPlane
isChroma = false
case 1: case 1:
sourcePlane = self.uPlane sourcePlane = self.uPlane
targetPlane = target.uPlane targetPlane = target.uPlane
isChroma = true
case 2: case 2:
sourcePlane = self.vPlane sourcePlane = self.vPlane
targetPlane = target.vPlane targetPlane = target.vPlane
isChroma = true
case 3: case 3:
sourcePlane = self.aPlane sourcePlane = self.aPlane
targetPlane = target.aPlane targetPlane = target.aPlane
isChroma = false
default: default:
preconditionFailure() preconditionFailure()
} }
@ -234,7 +266,8 @@ extension DctCoefficientsYUVA420 {
targetPlane.data.withUnsafeMutableBytes { bytes in targetPlane.data.withUnsafeMutableBytes { bytes in
let pixels = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self) let pixels = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
dctData.dct.inverse(withCoefficients: coefficients, pixels: pixels, width: sourcePlane.width, height: sourcePlane.height, coefficientsPerRow: targetPlane.width, bytesPerRow: targetPlane.bytesPerRow) let dct = isChroma ? dctData.chromaDct : dctData.lumaDct
dct.inverse(withCoefficients: coefficients, pixels: pixels, width: sourcePlane.width, height: sourcePlane.height, coefficientsPerRow: targetPlane.width, bytesPerRow: targetPlane.bytesPerRow)
} }
} }
} }

View File

@ -24,6 +24,16 @@ private func traceScrollView(view: UIView, point: CGPoint) -> (UIScrollView?, Bo
return (nil, true) return (nil, true)
} }
private func traceScrollViewUp(view: UIView) -> UIScrollView? {
if let scrollView = view as? UIScrollView {
return scrollView
} else if let superview = view.superview {
return traceScrollViewUp(view: superview)
} else {
return nil
}
}
private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
enum LockDirection { enum LockDirection {
case up case up
@ -87,8 +97,8 @@ private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecogn
if let _ = hitView as? UIButton { if let _ = hitView as? UIButton {
} else if let hitView = hitView, hitView.asyncdisplaykit_node is ASButtonNode { } else if let hitView = hitView, hitView.asyncdisplaykit_node is ASButtonNode {
} else { } else {
if let scrollView = traceScrollView(view: view, point: point).0 { if let scrollView = traceScrollView(view: view, point: point).0 ?? hitView.flatMap(traceScrollViewUp) {
if scrollView is ListViewScroller || scrollView is GridNodeScrollerView { if scrollView is ListViewScroller || scrollView is GridNodeScrollerView || scrollView.asyncdisplaykit_node is ASScrollNode {
found = false found = false
} else { } else {
found = true found = true

View File

@ -78,6 +78,7 @@ public final class EntityKeyboardComponent: Component {
public let emojiContent: EmojiPagerContentComponent public let emojiContent: EmojiPagerContentComponent
public let stickerContent: EmojiPagerContentComponent? public let stickerContent: EmojiPagerContentComponent?
public let gifContent: GifPagerContentComponent? public let gifContent: GifPagerContentComponent?
public let hasRecentGifs: Bool
public let availableGifSearchEmojies: [GifSearchEmoji] public let availableGifSearchEmojies: [GifSearchEmoji]
public let defaultToEmojiTab: Bool public let defaultToEmojiTab: Bool
public let externalTopPanelContainer: PagerExternalTopPanelContainer? public let externalTopPanelContainer: PagerExternalTopPanelContainer?
@ -96,6 +97,7 @@ public final class EntityKeyboardComponent: Component {
emojiContent: EmojiPagerContentComponent, emojiContent: EmojiPagerContentComponent,
stickerContent: EmojiPagerContentComponent?, stickerContent: EmojiPagerContentComponent?,
gifContent: GifPagerContentComponent?, gifContent: GifPagerContentComponent?,
hasRecentGifs: Bool,
availableGifSearchEmojies: [GifSearchEmoji], availableGifSearchEmojies: [GifSearchEmoji],
defaultToEmojiTab: Bool, defaultToEmojiTab: Bool,
externalTopPanelContainer: PagerExternalTopPanelContainer?, externalTopPanelContainer: PagerExternalTopPanelContainer?,
@ -113,6 +115,7 @@ public final class EntityKeyboardComponent: Component {
self.emojiContent = emojiContent self.emojiContent = emojiContent
self.stickerContent = stickerContent self.stickerContent = stickerContent
self.gifContent = gifContent self.gifContent = gifContent
self.hasRecentGifs = hasRecentGifs
self.availableGifSearchEmojies = availableGifSearchEmojies self.availableGifSearchEmojies = availableGifSearchEmojies
self.defaultToEmojiTab = defaultToEmojiTab self.defaultToEmojiTab = defaultToEmojiTab
self.externalTopPanelContainer = externalTopPanelContainer self.externalTopPanelContainer = externalTopPanelContainer
@ -142,6 +145,9 @@ public final class EntityKeyboardComponent: Component {
if lhs.gifContent != rhs.gifContent { if lhs.gifContent != rhs.gifContent {
return false return false
} }
if lhs.hasRecentGifs != rhs.hasRecentGifs {
return false
}
if lhs.availableGifSearchEmojies != rhs.availableGifSearchEmojies { if lhs.availableGifSearchEmojies != rhs.availableGifSearchEmojies {
return false return false
} }
@ -219,6 +225,7 @@ public final class EntityKeyboardComponent: Component {
contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(gifContent))) contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(gifContent)))
var topGifItems: [EntityKeyboardTopPanelComponent.Item] = [] var topGifItems: [EntityKeyboardTopPanelComponent.Item] = []
//TODO:localize //TODO:localize
if component.hasRecentGifs {
topGifItems.append(EntityKeyboardTopPanelComponent.Item( topGifItems.append(EntityKeyboardTopPanelComponent.Item(
id: "recent", id: "recent",
isReorderable: false, isReorderable: false,
@ -231,6 +238,7 @@ public final class EntityKeyboardComponent: Component {
} }
)) ))
)) ))
}
topGifItems.append(EntityKeyboardTopPanelComponent.Item( topGifItems.append(EntityKeyboardTopPanelComponent.Item(
id: "trending", id: "trending",
isReorderable: false, isReorderable: false,

View File

@ -437,6 +437,7 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
self.scrollView.showsVerticalScrollIndicator = false self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceHorizontal = false self.scrollView.alwaysBounceHorizontal = false
self.scrollView.scrollsToTop = false
self.scrollView.delegate = self self.scrollView.delegate = self
self.scrollViewContainer.addSubview(self.scrollView) self.scrollViewContainer.addSubview(self.scrollView)
@ -1114,6 +1115,7 @@ final class EntityKeyboardTopPanelComponent: Component {
self.scrollView.showsVerticalScrollIndicator = false self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceHorizontal = true self.scrollView.alwaysBounceHorizontal = true
self.scrollView.scrollsToTop = false
self.scrollView.delegate = self self.scrollView.delegate = self
self.addSubview(self.scrollView) self.addSubview(self.scrollView)

View File

@ -439,6 +439,7 @@ public final class GifPagerContentComponent: Component {
} }
self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.scrollsToTop = false
self.scrollView.delegate = self self.scrollView.delegate = self
self.addSubview(self.scrollView) self.addSubview(self.scrollView)

View File

@ -107,27 +107,6 @@ private final class ItemAnimationContext {
data.withUnsafeBytes { bytes -> Void in data.withUnsafeBytes { bytes -> Void in
memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow) memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow)
/*var sourceBuffer = vImage_Buffer()
sourceBuffer.width = UInt(width)
sourceBuffer.height = UInt(height)
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!.advanced(by: firstFrame.range.lowerBound))
sourceBuffer.rowBytes = bytesPerRow
var destinationBuffer = vImage_Buffer()
destinationBuffer.width = UInt(32)
destinationBuffer.height = UInt(32)
destinationBuffer.data = context.bytes
destinationBuffer.rowBytes = bytesPerRow
vImageBoxConvolve_ARGB8888(&sourceBuffer,
&destinationBuffer,
nil,
UInt(width - 32 - 16), UInt(height - 32 - 16),
UInt32(31),
UInt32(31),
nil,
vImage_Flags(kvImageEdgeExtend))*/
} }
guard let image = context.generateImage() else { guard let image = context.generateImage() else {
@ -184,42 +163,6 @@ private final class ItemAnimationContext {
destinationBuffer.data = context.bytes destinationBuffer.data = context.bytes
destinationBuffer.rowBytes = context.bytesPerRow destinationBuffer.rowBytes = context.bytesPerRow
/*var sourceBuffer = vImage_Buffer()
sourceBuffer.width = UInt(width)
sourceBuffer.height = UInt(height)
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!)
sourceBuffer.rowBytes = bytesPerRow
let tempBufferBytes = malloc(blurredHeight * context.bytesPerRow)
defer {
free(tempBufferBytes)
}
let temp2BufferBytes = malloc(blurredHeight * context.bytesPerRow)
defer {
free(temp2BufferBytes)
}
memset(temp2BufferBytes, Int32(bitPattern: color?.argb ?? 0xffffffff), blurredHeight * context.bytesPerRow)
var tempBuffer = vImage_Buffer()
tempBuffer.width = UInt(blurredWidth)
tempBuffer.height = UInt(blurredHeight)
tempBuffer.data = tempBufferBytes
tempBuffer.rowBytes = context.bytesPerRow
var temp2Buffer = vImage_Buffer()
temp2Buffer.width = UInt(blurredWidth)
temp2Buffer.height = UInt(blurredHeight)
temp2Buffer.data = temp2BufferBytes
temp2Buffer.rowBytes = context.bytesPerRow
vImageScale_ARGB8888(&sourceBuffer, &tempBuffer, nil, vImage_Flags(kvImageDoNotTile))
//vImageUnpremultiplyData_ARGB8888(&tempBuffer, &tempBuffer, vImage_Flags(kvImageDoNotTile))
vImagePremultipliedAlphaBlend_ARGB8888(&tempBuffer, &temp2Buffer, &destinationBuffer, vImage_Flags(kvImageDoNotTile))
//vImageCopyBuffer(&tempBuffer, &destinationBuffer, 4, vImage_Flags(kvImageDoNotTile))*/
vImageBoxConvolve_ARGB8888(&destinationBuffer, vImageBoxConvolve_ARGB8888(&destinationBuffer,
&destinationBuffer, &destinationBuffer,
nil, nil,
@ -302,14 +245,6 @@ private final class ItemAnimationContext {
} }
strongSelf.item = result.item strongSelf.item = result.item
strongSelf.updateIsPlaying() strongSelf.updateIsPlaying()
if result.item == nil {
for target in strongSelf.targets.copyItems() {
if let target = target.value {
target.updateDisplayPlaceholder(displayPlaceholder: true)
}
}
}
} }
}) })
} }

View File

@ -23,6 +23,7 @@ import ContextUI
import GalleryUI import GalleryUI
import AttachmentTextInputPanelNode import AttachmentTextInputPanelNode
import TelegramPresentationData import TelegramPresentationData
import TelegramNotices
private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = { private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = {
guard let path = getAppBundle().path(forResource: "emoji1016", ofType: "txt") else { guard let path = getAppBundle().path(forResource: "emoji1016", ofType: "txt") else {
@ -45,17 +46,37 @@ private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment,
return result return result
}() }()
final class EntityKeyboardGifContent: Equatable {
let hasRecentGifs: Bool
let component: GifPagerContentComponent
init(hasRecentGifs: Bool, component: GifPagerContentComponent) {
self.hasRecentGifs = hasRecentGifs
self.component = component
}
static func ==(lhs: EntityKeyboardGifContent, rhs: EntityKeyboardGifContent) -> Bool {
if lhs.hasRecentGifs != rhs.hasRecentGifs {
return false
}
if lhs.component != rhs.component {
return false
}
return true
}
}
final class ChatEntityKeyboardInputNode: ChatInputNode { final class ChatEntityKeyboardInputNode: ChatInputNode {
struct InputData: Equatable { struct InputData: Equatable {
var emoji: EmojiPagerContentComponent var emoji: EmojiPagerContentComponent
var stickers: EmojiPagerContentComponent? var stickers: EmojiPagerContentComponent?
var gifs: GifPagerContentComponent? var gifs: EntityKeyboardGifContent?
var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji]
init( init(
emoji: EmojiPagerContentComponent, emoji: EmojiPagerContentComponent,
stickers: EmojiPagerContentComponent?, stickers: EmojiPagerContentComponent?,
gifs: GifPagerContentComponent?, gifs: EntityKeyboardGifContent?,
availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji]
) { ) {
self.emoji = emoji self.emoji = emoji
@ -88,8 +109,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
var supergroupId: AnyHashable var supergroupId: AnyHashable
var id: AnyHashable var id: AnyHashable
var title: String var title: String
var subtitle: String?
var isPremiumLocked: Bool var isPremiumLocked: Bool
var isFeatured: Bool var isFeatured: Bool
var isExpandable: Bool
var items: [EmojiPagerContentComponent.Item] var items: [EmojiPagerContentComponent.Item]
} }
var itemGroups: [ItemGroup] = [] var itemGroups: [ItemGroup] = []
@ -134,7 +157,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
//TODO:localize //TODO:localize
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", isPremiumLocked: false, isFeatured: false, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, items: [resultItem]))
} }
} }
} }
@ -153,7 +176,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
//TODO:localize //TODO:localize
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Emoji", isPremiumLocked: false, isFeatured: false, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Emoji", subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, items: [resultItem]))
} }
} }
} }
@ -191,7 +214,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
break inner break inner
} }
} }
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, isPremiumLocked: isPremiumLocked, isFeatured: false, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, isExpandable: false, items: [resultItem]))
} }
} }
@ -217,7 +240,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemGroups[groupIndex].items.append(resultItem) itemGroups[groupIndex].items.append(resultItem)
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, isPremiumLocked: isPremiumLocked, isFeatured: true, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, items: [resultItem]))
} }
} }
} }
@ -234,7 +257,20 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
hasClear = true hasClear = true
} }
return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: group.title, isFeatured: group.isFeatured, isPremiumLocked: group.isPremiumLocked, hasClear: hasClear, displayPremiumBadges: false, items: group.items) return EmojiPagerContentComponent.ItemGroup(
supergroupId: group.supergroupId,
groupId: group.id,
title: group.title,
subtitle: group.subtitle,
actionButtonTitle: nil,
isFeatured: group.isFeatured,
isPremiumLocked: group.isPremiumLocked,
isEmbedded: false,
hasClear: hasClear,
isExpandable: group.isExpandable,
displayPremiumBadges: false,
items: group.items
)
}, },
itemLayoutType: .compact itemLayoutType: .compact
) )
@ -256,7 +292,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|> distinctUntilChanged |> distinctUntilChanged
let emojiInputInteraction = EmojiPagerContentComponent.InputInteraction( let emojiInputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak interfaceInteraction, weak controllerInteraction] item, _, _, _ in performItemAction: { [weak interfaceInteraction, weak controllerInteraction] _, item, _, _, _ in
let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in
guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
return return
@ -397,12 +433,44 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
chatPeerId: chatPeerId chatPeerId: chatPeerId
) )
let stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( let stickerInputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak controllerInteraction, weak interfaceInteraction] item, view, rect, layer in performItemAction: { [weak controllerInteraction, weak interfaceInteraction] groupId, item, view, rect, layer in
let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in
guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
return return
} }
if let file = item.file { guard let file = item.file else {
return
}
if groupId == AnyHashable("featuredTop") {
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controllerInteraction] views in
guard let controllerInteraction = controllerInteraction else {
return
}
guard let view = views.views[viewKey] as? OrderedItemListView else {
return
}
for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen(
context: context,
highlightedPackId: featuredStickerPack.info.id,
sendSticker: { [weak controllerInteraction] fileReference, sourceNode, sourceRect in
guard let controllerInteraction = controllerInteraction else {
return false
}
return controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil)
}
))
break
}
}
})
} else {
if file.isPremiumSticker && !hasPremium { if file.isPremiumSticker && !hasPremium {
let controller = PremiumIntroScreen(context: context, source: .stickers) let controller = PremiumIntroScreen(context: context, source: .stickers)
controllerInteraction.navigationController()?.pushViewController(controller) controllerInteraction.navigationController()?.pushViewController(controller)
@ -427,7 +495,52 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
controllerInteraction.navigationController()?.pushViewController(controller) controllerInteraction.navigationController()?.pushViewController(controller)
}, },
addGroupAction: { _, _ in addGroupAction: { groupId, isPremiumLocked in
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
return
}
if isPremiumLocked {
let controller = PremiumIntroScreen(context: context, source: .stickers)
controllerInteraction.navigationController()?.pushViewController(controller)
return
}
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { views in
guard let view = views.views[viewKey] as? OrderedItemListView else {
return
}
for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredStickerPack.info.id == collectionId {
//let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false)
|> mapToSignal { result -> Signal<Void, NoError> in
switch result {
case let .result(info, items, installed):
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
}
case .fetching:
break
case .none:
break
}
return .complete()
}
|> deliverOnMainQueue).start(completed: {
})
break
}
}
})
}, },
clearGroup: { [weak controllerInteraction] groupId in clearGroup: { [weak controllerInteraction] groupId in
guard let controllerInteraction = controllerInteraction else { guard let controllerInteraction = controllerInteraction else {
@ -447,6 +560,20 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
}) })
])]) ])])
controllerInteraction.presentController(actionSheet, nil) controllerInteraction.presentController(actionSheet, nil)
} else if groupId == AnyHashable("featuredTop") {
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { views in
guard let view = views.views[viewKey] as? OrderedItemListView else {
return
}
var stickerPackIds: [Int64] = []
for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
stickerPackIds.append(featuredStickerPack.info.id.id)
}
let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start()
})
} }
}, },
pushController: { [weak controllerInteraction] controller in pushController: { [weak controllerInteraction] controller in
@ -494,16 +621,22 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks] let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers] let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers]
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
let stickerItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest( let stickerItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000), context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000),
hasPremium, hasPremium,
context.account.viewTracker.featuredStickerPacks() context.account.viewTracker.featuredStickerPacks(),
context.engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.featuredStickersConfiguration, id: ValueBoxKey(length: 0))),
ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager)
) )
|> map { view, hasPremium, featuredStickerPacks -> EmojiPagerContentComponent in |> map { view, hasPremium, featuredStickerPacks, featuredStickersConfiguration, dismissedTrendingStickerPacks -> EmojiPagerContentComponent in
struct ItemGroup { struct ItemGroup {
var supergroupId: AnyHashable var supergroupId: AnyHashable
var id: AnyHashable var id: AnyHashable
var title: String var title: String
var subtitle: String?
var actionButtonTitle: String?
var isPremiumLocked: Bool var isPremiumLocked: Bool
var isFeatured: Bool var isFeatured: Bool
var displayPremiumBadges: Bool var displayPremiumBadges: Bool
@ -528,6 +661,50 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
} }
var installedCollectionIds = Set<ItemCollectionId>()
for (id, _, _) in view.collectionInfos {
installedCollectionIds.insert(id)
}
let dismissedTrendingStickerPacksSet = Set(dismissedTrendingStickerPacks ?? [])
let featuredStickerPacksSet = Set(featuredStickerPacks.map(\.info.id.id))
if dismissedTrendingStickerPacksSet != featuredStickerPacksSet {
let featuredStickersConfiguration = featuredStickersConfiguration?.get(FeaturedStickersConfiguration.self)
for featuredStickerPack in featuredStickerPacks {
if installedCollectionIds.contains(featuredStickerPack.info.id) {
continue
}
guard let item = featuredStickerPack.topItems.first else {
continue
}
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
staticEmoji: nil,
subgroupId: nil
)
let supergroupId = "featuredTop"
let groupId: AnyHashable = supergroupId
let isPremiumLocked: Bool = item.file.isPremiumSticker && !hasPremium
if isPremiumLocked && isPremiumDisabled {
continue
}
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
let trendingIsPremium = featuredStickersConfiguration?.isPremium ?? false
let title = trendingIsPremium ? strings.Stickers_TrendingPremiumStickers : strings.StickerPacksSettings_FeaturedPacks
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
}
}
}
if let savedStickers = savedStickers { if let savedStickers = savedStickers {
for item in savedStickers.items { for item in savedStickers.items {
guard let item = item.contents.get(SavedStickerItem.self) else { guard let item = item.contents.get(SavedStickerItem.self) else {
@ -549,13 +726,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
//TODO:localize //TODO:localize
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Saved", isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Saved", subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
} }
} }
} }
if let recentStickers = recentStickers { if let recentStickers = recentStickers {
var count = 0
for item in recentStickers.items { for item in recentStickers.items {
guard let item = item.contents.get(RecentMediaItem.self) else { guard let item = item.contents.get(RecentMediaItem.self) else {
continue continue
@ -576,12 +752,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
//TODO:localize //TODO:localize
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
}
count += 1
if count >= 5 {
break
} }
} }
} }
@ -626,16 +797,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
//TODO:localize //TODO:localize
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Premium", isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Premium", subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
} }
} }
} }
var installedCollectionIds = Set<ItemCollectionId>()
for (id, _, _) in view.collectionInfos {
installedCollectionIds.insert(id)
}
for entry in view.entries { for entry in view.entries {
guard let item = entry.item as? StickerPackItem else { guard let item = entry.item as? StickerPackItem else {
continue continue
@ -658,7 +824,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
break inner break inner
} }
} }
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, items: [resultItem]))
} }
} }
@ -684,7 +850,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemGroups[groupIndex].items.append(resultItem) itemGroups[groupIndex].items.append(resultItem)
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, items: [resultItem]))
let subtitle: String = strings.StickerPack_StickerCount(Int32(featuredStickerPack.info.count))
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, subtitle: subtitle, actionButtonTitle: strings.Stickers_Install, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, items: [resultItem]))
} }
} }
} }
@ -697,11 +866,28 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
inputInteraction: stickerInputInteraction, inputInteraction: stickerInputInteraction,
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
var hasClear = false var hasClear = false
var isEmbedded = false
if group.id == AnyHashable("recent") { if group.id == AnyHashable("recent") {
hasClear = true hasClear = true
} else if group.id == AnyHashable("featuredTop") {
hasClear = true
isEmbedded = true
} }
return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: group.title, isFeatured: group.isFeatured, isPremiumLocked: group.isPremiumLocked, hasClear: hasClear, displayPremiumBadges: group.displayPremiumBadges, items: group.items) return EmojiPagerContentComponent.ItemGroup(
supergroupId: group.supergroupId,
groupId: group.id,
title: group.title,
subtitle: group.subtitle,
actionButtonTitle: group.actionButtonTitle,
isFeatured: group.isFeatured,
isPremiumLocked: group.isPremiumLocked,
isEmbedded: isEmbedded,
hasClear: hasClear,
isExpandable: false,
displayPremiumBadges: group.displayPremiumBadges,
items: group.items
)
}, },
itemLayoutType: .detailed itemLayoutType: .detailed
) )
@ -752,16 +938,18 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
) )
// We are going to subscribe to the actual data when the view is loaded // We are going to subscribe to the actual data when the view is loaded
let gifItems: Signal<GifPagerContentComponent, NoError> = .single(GifPagerContentComponent( let gifItems: Signal<EntityKeyboardGifContent, NoError> = .single(EntityKeyboardGifContent(
hasRecentGifs: true,
component: GifPagerContentComponent(
context: context, context: context,
inputInteraction: gifInputInteraction, inputInteraction: gifInputInteraction,
subject: .recent, subject: .recent,
items: [], items: [],
isLoading: false, isLoading: false,
loadMoreToken: nil loadMoreToken: nil
)
)) ))
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
return combineLatest(queue: .mainQueue(), return combineLatest(queue: .mainQueue(),
emojiItems, emojiItems,
stickerItems, stickerItems,
@ -822,6 +1010,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
private let defaultToEmojiTab: Bool private let defaultToEmojiTab: Bool
private var currentInputData: InputData private var currentInputData: InputData
private var inputDataDisposable: Disposable? private var inputDataDisposable: Disposable?
private var hasRecentGifsDisposable: Disposable?
private let controllerInteraction: ChatControllerInteraction? private let controllerInteraction: ChatControllerInteraction?
@ -839,10 +1028,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
var switchToTextInput: (() -> Void)? var switchToTextInput: (() -> Void)?
private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool)? private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool)?
private var scheduledInnerTransition: Transition?
private var gifMode: GifPagerContentComponent.Subject = .recent { private var gifMode: GifPagerContentComponent.Subject? {
didSet { didSet {
if self.gifMode != oldValue { if let gifMode = self.gifMode, gifMode != oldValue {
self.reloadGifContext() self.reloadGifContext()
} }
} }
@ -858,17 +1048,17 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
private final class GifContext { private final class GifContext {
private var componentValue: GifPagerContentComponent? { private var componentValue: EntityKeyboardGifContent? {
didSet { didSet {
if let componentValue = self.componentValue { if let componentValue = self.componentValue {
self.componentResult.set(.single(componentValue)) self.componentResult.set(.single(componentValue))
} }
} }
} }
private let componentPromise = Promise<GifPagerContentComponent>() private let componentPromise = Promise<EntityKeyboardGifContent>()
private let componentResult = Promise<GifPagerContentComponent>() private let componentResult = Promise<EntityKeyboardGifContent>()
var component: Signal<GifPagerContentComponent, NoError> { var component: Signal<EntityKeyboardGifContent, NoError> {
return self.componentResult.get() return self.componentResult.get()
} }
private var componentDisposable: Disposable? private var componentDisposable: Disposable?
@ -884,18 +1074,25 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
self.subject = subject self.subject = subject
self.gifInputInteraction = gifInputInteraction self.gifInputInteraction = gifInputInteraction
let gifItems: Signal<GifPagerContentComponent, NoError> let hasRecentGifs = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs))
|> map { savedGifs -> Bool in
return !savedGifs.isEmpty
}
let gifItems: Signal<EntityKeyboardGifContent, NoError>
switch subject { switch subject {
case .recent: case .recent:
gifItems = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs)) gifItems = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs))
|> map { savedGifs -> GifPagerContentComponent in |> map { savedGifs -> EntityKeyboardGifContent in
var items: [GifPagerContentComponent.Item] = [] var items: [GifPagerContentComponent.Item] = []
for gifItem in savedGifs { for gifItem in savedGifs {
items.append(GifPagerContentComponent.Item( items.append(GifPagerContentComponent.Item(
file: gifItem.contents.get(RecentMediaItem.self)!.media file: gifItem.contents.get(RecentMediaItem.self)!.media
)) ))
} }
return GifPagerContentComponent( return EntityKeyboardGifContent(
hasRecentGifs: true,
component: GifPagerContentComponent(
context: context, context: context,
inputInteraction: gifInputInteraction, inputInteraction: gifInputInteraction,
subject: subject, subject: subject,
@ -903,10 +1100,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
isLoading: false, isLoading: false,
loadMoreToken: nil loadMoreToken: nil
) )
)
} }
case .trending: case .trending:
gifItems = trendingGifs gifItems = combineLatest(hasRecentGifs, trendingGifs)
|> map { trendingGifs -> GifPagerContentComponent in |> map { hasRecentGifs, trendingGifs -> EntityKeyboardGifContent in
var items: [GifPagerContentComponent.Item] = [] var items: [GifPagerContentComponent.Item] = []
var isLoading = false var isLoading = false
@ -920,7 +1118,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
isLoading = true isLoading = true
} }
return GifPagerContentComponent( return EntityKeyboardGifContent(
hasRecentGifs: hasRecentGifs,
component: GifPagerContentComponent(
context: context, context: context,
inputInteraction: gifInputInteraction, inputInteraction: gifInputInteraction,
subject: subject, subject: subject,
@ -928,10 +1128,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
isLoading: isLoading, isLoading: isLoading,
loadMoreToken: nil loadMoreToken: nil
) )
)
} }
case let .emojiSearch(query): case let .emojiSearch(query):
gifItems = paneGifSearchForQuery(context: context, query: query, offset: nil, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil) gifItems = combineLatest(hasRecentGifs, paneGifSearchForQuery(context: context, query: query, offset: nil, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil))
|> map { result -> GifPagerContentComponent in |> map { hasRecentGifs, result -> EntityKeyboardGifContent in
var items: [GifPagerContentComponent.Item] = [] var items: [GifPagerContentComponent.Item] = []
var loadMoreToken: String? var loadMoreToken: String?
@ -947,7 +1148,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
isLoading = true isLoading = true
} }
return GifPagerContentComponent( return EntityKeyboardGifContent(
hasRecentGifs: hasRecentGifs,
component: GifPagerContentComponent(
context: context, context: context,
inputInteraction: gifInputInteraction, inputInteraction: gifInputInteraction,
subject: subject, subject: subject,
@ -955,6 +1158,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
isLoading: isLoading, isLoading: isLoading,
loadMoreToken: loadMoreToken loadMoreToken: loadMoreToken
) )
)
} }
} }
@ -988,12 +1192,17 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
switch self.subject { switch self.subject {
case let .emojiSearch(query): case let .emojiSearch(query):
let gifItems: Signal<GifPagerContentComponent, NoError> let hasRecentGifs = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs))
gifItems = paneGifSearchForQuery(context: context, query: query, offset: token, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil) |> map { savedGifs -> Bool in
|> map { result -> GifPagerContentComponent in return !savedGifs.isEmpty
}
let gifItems: Signal<EntityKeyboardGifContent, NoError>
gifItems = combineLatest(hasRecentGifs, paneGifSearchForQuery(context: context, query: query, offset: token, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil))
|> map { hasRecentGifs, result -> EntityKeyboardGifContent in
var items: [GifPagerContentComponent.Item] = [] var items: [GifPagerContentComponent.Item] = []
var existingIds = Set<MediaId>() var existingIds = Set<MediaId>()
for item in componentValue.items { for item in componentValue.component.items {
items.append(item) items.append(item)
existingIds.insert(item.file.fileId) existingIds.insert(item.file.fileId)
} }
@ -1015,7 +1224,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
isLoading = true isLoading = true
} }
return GifPagerContentComponent( return EntityKeyboardGifContent(
hasRecentGifs: hasRecentGifs,
component: GifPagerContentComponent(
context: context, context: context,
inputInteraction: gifInputInteraction, inputInteraction: gifInputInteraction,
subject: subject, subject: subject,
@ -1023,6 +1234,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
isLoading: isLoading, isLoading: isLoading,
loadMoreToken: loadMoreToken loadMoreToken: loadMoreToken
) )
)
} }
self.componentPromise.set(gifItems) self.componentPromise.set(gifItems)
@ -1038,7 +1250,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
} }
} }
private let gifComponent = Promise<GifPagerContentComponent>() private let gifComponent = Promise<EntityKeyboardGifContent>()
private var gifInputInteraction: GifPagerContentComponent.InputInteraction? private var gifInputInteraction: GifPagerContentComponent.InputInteraction?
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?) { init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?) {
@ -1059,6 +1271,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
self.externalTopPanelContainerImpl = PagerExternalTopPanelContainer() self.externalTopPanelContainerImpl = PagerExternalTopPanelContainer()
self.inputDataDisposable = (combineLatest(queue: .mainQueue(), self.inputDataDisposable = (combineLatest(queue: .mainQueue(),
updatedInputData, updatedInputData,
self.gifComponent.get() self.gifComponent.get()
@ -1070,8 +1283,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
var inputData = inputData var inputData = inputData
inputData.gifs = gifs inputData.gifs = gifs
var transition: Transition = .immediate
if strongSelf.currentInputData.emoji != inputData.emoji {
transition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(EmojiPagerContentComponent.ContentAnimation(type: .generic))
}
strongSelf.currentInputData = inputData strongSelf.currentInputData = inputData
strongSelf.performLayout() strongSelf.performLayout(transition: transition)
}) })
self.inputNodeInteraction = ChatMediaInputNodeInteraction( self.inputNodeInteraction = ChatMediaInputNodeInteraction(
@ -1137,16 +1354,35 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
} }
self.reloadGifContext() let hasRecentGifs = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs))
|> map { savedGifs -> Bool in
return !savedGifs.isEmpty
}
self.hasRecentGifsDisposable = (hasRecentGifs
|> deliverOnMainQueue).start(next: { [weak self] hasRecentGifs in
guard let strongSelf = self else {
return
}
if let gifMode = strongSelf.gifMode {
if !hasRecentGifs, case .recent = gifMode {
strongSelf.gifMode = .trending
}
} else {
strongSelf.gifMode = hasRecentGifs ? .recent : .trending
}
})
} }
deinit { deinit {
self.inputDataDisposable?.dispose() self.inputDataDisposable?.dispose()
self.hasRecentGifsDisposable?.dispose()
} }
private func reloadGifContext() { private func reloadGifContext() {
if let gifInputInteraction = self.gifInputInteraction { if let gifInputInteraction = self.gifInputInteraction, let gifMode = self.gifMode {
self.gifContext = GifContext(context: self.context, subject: self.gifMode, gifInputInteraction: gifInputInteraction, trendingGifs: self.trendingGifsPromise.get()) self.gifContext = GifContext(context: self.context, subject: gifMode, gifInputInteraction: gifInputInteraction, trendingGifs: self.trendingGifsPromise.get())
} }
} }
@ -1154,16 +1390,25 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
self.isMarkInputCollapsed = true self.isMarkInputCollapsed = true
} }
private func performLayout() { private func performLayout(transition: Transition) {
guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) = self.currentState else { guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) = self.currentState else {
return return
} }
self.scheduledInnerTransition = transition
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded)
} }
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) {
self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded)
let innerTransition: Transition
if let scheduledInnerTransition = self.scheduledInnerTransition {
self.scheduledInnerTransition = nil
innerTransition = scheduledInnerTransition
} else {
innerTransition = Transition(transition)
}
let wasMarkedInputCollapsed = self.isMarkInputCollapsed let wasMarkedInputCollapsed = self.isMarkInputCollapsed
self.isMarkInputCollapsed = false self.isMarkInputCollapsed = false
@ -1179,14 +1424,14 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
let inputNodeInteraction = self.inputNodeInteraction! let inputNodeInteraction = self.inputNodeInteraction!
let trendingGifsPromise = self.trendingGifsPromise let trendingGifsPromise = self.trendingGifsPromise
var mappedTransition = Transition(transition) var mappedTransition = innerTransition
if wasMarkedInputCollapsed || !isExpanded { if wasMarkedInputCollapsed || !isExpanded {
mappedTransition = mappedTransition.withUserData(EntityKeyboardComponent.MarkInputCollapsed()) mappedTransition = mappedTransition.withUserData(EntityKeyboardComponent.MarkInputCollapsed())
} }
var stickerContent: EmojiPagerContentComponent? = self.currentInputData.stickers var stickerContent: EmojiPagerContentComponent? = self.currentInputData.stickers
var gifContent: GifPagerContentComponent? = self.currentInputData.gifs var gifContent: EntityKeyboardGifContent? = self.currentInputData.gifs
var stickersEnabled = true var stickersEnabled = true
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel { if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
@ -1204,12 +1449,6 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
gifContent = nil gifContent = nil
} }
if let gifContentValue = gifContent {
if gifContentValue.items.isEmpty {
gifContent = nil
}
}
let entityKeyboardSize = self.entityKeyboardView.update( let entityKeyboardSize = self.entityKeyboardView.update(
transition: mappedTransition, transition: mappedTransition,
component: AnyComponent(EntityKeyboardComponent( component: AnyComponent(EntityKeyboardComponent(
@ -1217,7 +1456,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
bottomInset: bottomInset, bottomInset: bottomInset,
emojiContent: self.currentInputData.emoji, emojiContent: self.currentInputData.emoji,
stickerContent: stickerContent, stickerContent: stickerContent,
gifContent: gifContent, gifContent: gifContent?.component,
hasRecentGifs: gifContent?.hasRecentGifs ?? false,
availableGifSearchEmojies: self.currentInputData.availableGifSearchEmojies, availableGifSearchEmojies: self.currentInputData.availableGifSearchEmojies,
defaultToEmojiTab: self.defaultToEmojiTab, defaultToEmojiTab: self.defaultToEmojiTab,
externalTopPanelContainer: self.externalTopPanelContainerImpl, externalTopPanelContainer: self.externalTopPanelContainerImpl,
@ -1483,7 +1723,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
self.clipsToBounds = true self.clipsToBounds = true
let inputInteraction = EmojiPagerContentComponent.InputInteraction( let inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] item, _, _, _ in performItemAction: { [weak self] _, item, _, _, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }