mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Emoji input improvements
This commit is contained in:
parent
fdea4fc967
commit
806536a4cd
@ -107,6 +107,7 @@
|
||||
"PUSH_MESSAGE_THEME" = "%1$@|changed chat theme to %2$@";
|
||||
"PUSH_MESSAGE_NOTHEME" = "%1$@|disabled chat theme";
|
||||
"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_NOTEXT" = "%1$@|posted a message";
|
||||
|
||||
@ -5,9 +5,18 @@
|
||||
|
||||
#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
|
||||
|
||||
- (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)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));
|
||||
|
||||
@ -118,6 +118,16 @@ static DCTELEM std_luminance_quant_tbl[DCTSIZE2] = {
|
||||
49, 64, 78, 87, 103, 121, 120, 101,
|
||||
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)
|
||||
/* Convert a user-specified quality rating to a percentage scaling factor
|
||||
@ -143,7 +153,7 @@ int jpeg_quality_scaling(int 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
|
||||
* a scale factor (given as a percentage).
|
||||
* 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.
|
||||
* This is the standard quality-adjusting entry point for typical user
|
||||
* 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);
|
||||
|
||||
/* 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 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));
|
||||
|
||||
DCTELEM qtable[DCTSIZE2];
|
||||
jpeg_set_quality(qtable, quality);
|
||||
|
||||
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));
|
||||
IFAST_MULT_TYPE *ifmtbl = (IFAST_MULT_TYPE *)data.data();
|
||||
|
||||
DCTELEM qtable[DCTSIZE2];
|
||||
jpeg_set_quality(qtable, quality);
|
||||
|
||||
#define CONST_BITS 14
|
||||
static const int16_t aanscales[DCTSIZE2] = {
|
||||
/* precomputed values scaled up by 14 bits */
|
||||
@ -338,13 +341,32 @@ void performInverseDct(int16_t const * coefficients, uint8_t *pixels, int width,
|
||||
|
||||
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 {
|
||||
public:
|
||||
DCTInternal(int quality) {
|
||||
DCTInternal(DCTTable const &dctTable) {
|
||||
auxiliaryData = createDctAuxiliaryData();
|
||||
|
||||
generateForwardDctData(quality, forwardDctData);
|
||||
generateInverseDctData(quality, inverseDctData);
|
||||
generateForwardDctData(dctTable.table.data(), forwardDctData);
|
||||
generateInverseDctData(dctTable.table.data(), inverseDctData);
|
||||
}
|
||||
|
||||
~DCTInternal() {
|
||||
@ -357,8 +379,8 @@ public:
|
||||
std::vector<uint8_t> inverseDctData;
|
||||
};
|
||||
|
||||
DCT::DCT(int quality) {
|
||||
_internal = new DCTInternal(quality);
|
||||
DCT::DCT(DCTTable const &dctTable) {
|
||||
_internal = new DCTInternal(dctTable);
|
||||
}
|
||||
|
||||
DCT::~DCT() {
|
||||
|
||||
@ -3,15 +3,23 @@
|
||||
|
||||
#include "DCTCommon.h"
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace dct {
|
||||
|
||||
class DCTInternal;
|
||||
|
||||
struct DCTTable {
|
||||
static DCTTable generate(int quality, bool isChroma);
|
||||
static DCTTable initializeEmpty();
|
||||
|
||||
std::vector<int16_t> table;
|
||||
};
|
||||
|
||||
class DCT {
|
||||
public:
|
||||
DCT(int quality);
|
||||
DCT(DCTTable const &dctTable);
|
||||
~DCT();
|
||||
|
||||
void forward(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow);
|
||||
|
||||
@ -4,6 +4,41 @@
|
||||
|
||||
#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 () {
|
||||
std::unique_ptr<dct::DCT> _dct;
|
||||
}
|
||||
@ -12,10 +47,10 @@
|
||||
|
||||
@implementation ImageDCT
|
||||
|
||||
- (instancetype _Nonnull)initWithQuality:(NSInteger)quality {
|
||||
- (instancetype _Nonnull)initWithTable:(ImageDCTTable * _Nonnull)table {
|
||||
self = [super init];
|
||||
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;
|
||||
}
|
||||
|
||||
@ -294,10 +294,10 @@ private final class AnimationCacheItemWriterInternal {
|
||||
}
|
||||
|
||||
let dctData: DctData
|
||||
if let current = self.currentDctData, current.quality == self.dctQuality {
|
||||
if let current = self.currentDctData {
|
||||
dctData = current
|
||||
} else {
|
||||
dctData = DctData(quality: self.dctQuality)
|
||||
dctData = DctData(generatingTablesAtQuality: self.dctQuality)
|
||||
self.currentDctData = dctData
|
||||
}
|
||||
|
||||
@ -310,11 +310,18 @@ private final class AnimationCacheItemWriterInternal {
|
||||
yuvaSurface.dct(dctData: dctData, target: dctCoefficients)
|
||||
|
||||
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.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.file.write(0 as UInt32)
|
||||
@ -501,10 +508,10 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
||||
}
|
||||
|
||||
let dctData: DctData
|
||||
if let current = self.currentDctData, current.quality == self.dctQuality {
|
||||
if let current = self.currentDctData {
|
||||
dctData = current
|
||||
} else {
|
||||
dctData = DctData(quality: self.dctQuality)
|
||||
dctData = DctData(generatingTablesAtQuality: self.dctQuality)
|
||||
self.currentDctData = dctData
|
||||
}
|
||||
|
||||
@ -526,11 +533,18 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
||||
yuvaSurface.dct(dctData: dctData, target: dctCoefficients)
|
||||
|
||||
if isFirstFrame {
|
||||
file.write(3 as UInt32)
|
||||
file.write(4 as UInt32)
|
||||
|
||||
file.write(UInt32(dctCoefficients.yPlane.width))
|
||||
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())
|
||||
file.write(0 as UInt32)
|
||||
@ -652,10 +666,10 @@ private final class AnimationCacheItemAccessor {
|
||||
private var currentFrame: CurrentFrame?
|
||||
|
||||
private var currentYUVASurface: ImageYUVA420?
|
||||
private var currentDctData: DctData
|
||||
private let currentDctData: DctData
|
||||
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.range = range
|
||||
self.width = width
|
||||
@ -673,7 +687,7 @@ private final class AnimationCacheItemAccessor {
|
||||
self.frameMapping = resultFrameMapping
|
||||
self.durationMapping = durationMapping
|
||||
|
||||
self.currentDctData = DctData(quality: dctQuality)
|
||||
self.currentDctData = dctData
|
||||
}
|
||||
|
||||
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 {
|
||||
var value: UInt32 = 0
|
||||
withUnsafeMutableBytes(of: &value, { bytes -> Void in
|
||||
@ -1071,7 +1095,7 @@ private func loadItem(path: String) throws -> AnimationCacheItem {
|
||||
}
|
||||
let formatVersion = readUInt32(data: compressedData, offset: offset)
|
||||
offset += 4
|
||||
if formatVersion != 3 {
|
||||
if formatVersion != 4 {
|
||||
throw LoadItemError.dataError
|
||||
}
|
||||
|
||||
@ -1090,9 +1114,27 @@ private func loadItem(path: String) throws -> AnimationCacheItem {
|
||||
if offset + 4 > dataLength {
|
||||
throw LoadItemError.dataError
|
||||
}
|
||||
let dctQuality = readUInt32(data: compressedData, offset: offset)
|
||||
let dctLumaTableLength = readUInt32(data: compressedData, offset: offset)
|
||||
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 {
|
||||
throw LoadItemError.dataError
|
||||
}
|
||||
@ -1119,7 +1161,11 @@ private func loadItem(path: String) throws -> AnimationCacheItem {
|
||||
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 itemAccessor.advance(advance: advance, requestedFormat: requestedFormat)
|
||||
@ -1397,7 +1443,9 @@ public final class AnimationCacheImpl: AnimationCache {
|
||||
let itemFirstFramePath = "\(itemDirectoryPath)/\(sourceIdPath.fileName)-f"
|
||||
|
||||
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)) {
|
||||
|
||||
@ -152,12 +152,33 @@ extension ImageYUVA420 {
|
||||
}
|
||||
|
||||
final class DctData {
|
||||
let quality: Int
|
||||
let dct: ImageDCT
|
||||
let lumaTable: ImageDCTTable
|
||||
let lumaDct: ImageDCT
|
||||
|
||||
init(quality: Int) {
|
||||
self.quality = quality
|
||||
self.dct = ImageDCT(quality: quality)
|
||||
let chromaTable: ImageDCTTable
|
||||
let chromaDct: ImageDCT
|
||||
|
||||
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 {
|
||||
let sourcePlane: ImagePlane
|
||||
let targetPlane: DctCoefficientPlane
|
||||
let isChroma: Bool
|
||||
switch i {
|
||||
case 0:
|
||||
sourcePlane = self.yPlane
|
||||
targetPlane = target.yPlane
|
||||
isChroma = false
|
||||
case 1:
|
||||
sourcePlane = self.uPlane
|
||||
targetPlane = target.uPlane
|
||||
isChroma = true
|
||||
case 2:
|
||||
sourcePlane = self.vPlane
|
||||
targetPlane = target.vPlane
|
||||
isChroma = true
|
||||
case 3:
|
||||
sourcePlane = self.aPlane
|
||||
targetPlane = target.aPlane
|
||||
isChroma = false
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
@ -191,7 +217,8 @@ extension ImageYUVA420 {
|
||||
targetPlane.data.withUnsafeMutableBytes { bytes in
|
||||
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 {
|
||||
let sourcePlane: DctCoefficientPlane
|
||||
let targetPlane: ImagePlane
|
||||
let isChroma: Bool
|
||||
switch i {
|
||||
case 0:
|
||||
sourcePlane = self.yPlane
|
||||
targetPlane = target.yPlane
|
||||
isChroma = false
|
||||
case 1:
|
||||
sourcePlane = self.uPlane
|
||||
targetPlane = target.uPlane
|
||||
isChroma = true
|
||||
case 2:
|
||||
sourcePlane = self.vPlane
|
||||
targetPlane = target.vPlane
|
||||
isChroma = true
|
||||
case 3:
|
||||
sourcePlane = self.aPlane
|
||||
targetPlane = target.aPlane
|
||||
isChroma = false
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
@ -234,7 +266,8 @@ extension DctCoefficientsYUVA420 {
|
||||
targetPlane.data.withUnsafeMutableBytes { bytes in
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,16 @@ private func traceScrollView(view: UIView, point: CGPoint) -> (UIScrollView?, Bo
|
||||
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 {
|
||||
enum LockDirection {
|
||||
case up
|
||||
@ -87,8 +97,8 @@ private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecogn
|
||||
if let _ = hitView as? UIButton {
|
||||
} else if let hitView = hitView, hitView.asyncdisplaykit_node is ASButtonNode {
|
||||
} else {
|
||||
if let scrollView = traceScrollView(view: view, point: point).0 {
|
||||
if scrollView is ListViewScroller || scrollView is GridNodeScrollerView {
|
||||
if let scrollView = traceScrollView(view: view, point: point).0 ?? hitView.flatMap(traceScrollViewUp) {
|
||||
if scrollView is ListViewScroller || scrollView is GridNodeScrollerView || scrollView.asyncdisplaykit_node is ASScrollNode {
|
||||
found = false
|
||||
} else {
|
||||
found = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -78,6 +78,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let emojiContent: EmojiPagerContentComponent
|
||||
public let stickerContent: EmojiPagerContentComponent?
|
||||
public let gifContent: GifPagerContentComponent?
|
||||
public let hasRecentGifs: Bool
|
||||
public let availableGifSearchEmojies: [GifSearchEmoji]
|
||||
public let defaultToEmojiTab: Bool
|
||||
public let externalTopPanelContainer: PagerExternalTopPanelContainer?
|
||||
@ -96,6 +97,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
emojiContent: EmojiPagerContentComponent,
|
||||
stickerContent: EmojiPagerContentComponent?,
|
||||
gifContent: GifPagerContentComponent?,
|
||||
hasRecentGifs: Bool,
|
||||
availableGifSearchEmojies: [GifSearchEmoji],
|
||||
defaultToEmojiTab: Bool,
|
||||
externalTopPanelContainer: PagerExternalTopPanelContainer?,
|
||||
@ -113,6 +115,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
self.emojiContent = emojiContent
|
||||
self.stickerContent = stickerContent
|
||||
self.gifContent = gifContent
|
||||
self.hasRecentGifs = hasRecentGifs
|
||||
self.availableGifSearchEmojies = availableGifSearchEmojies
|
||||
self.defaultToEmojiTab = defaultToEmojiTab
|
||||
self.externalTopPanelContainer = externalTopPanelContainer
|
||||
@ -142,6 +145,9 @@ public final class EntityKeyboardComponent: Component {
|
||||
if lhs.gifContent != rhs.gifContent {
|
||||
return false
|
||||
}
|
||||
if lhs.hasRecentGifs != rhs.hasRecentGifs {
|
||||
return false
|
||||
}
|
||||
if lhs.availableGifSearchEmojies != rhs.availableGifSearchEmojies {
|
||||
return false
|
||||
}
|
||||
@ -219,18 +225,20 @@ public final class EntityKeyboardComponent: Component {
|
||||
contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(gifContent)))
|
||||
var topGifItems: [EntityKeyboardTopPanelComponent.Item] = []
|
||||
//TODO:localize
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: "recent",
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: "Chat/Input/Media/RecentTabIcon",
|
||||
theme: component.theme,
|
||||
title: "Recent",
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.recent)
|
||||
}
|
||||
if component.hasRecentGifs {
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: "recent",
|
||||
isReorderable: false,
|
||||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
imageName: "Chat/Input/Media/RecentTabIcon",
|
||||
theme: component.theme,
|
||||
title: "Recent",
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.recent)
|
||||
}
|
||||
))
|
||||
))
|
||||
))
|
||||
}
|
||||
topGifItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||
id: "trending",
|
||||
isReorderable: false,
|
||||
|
||||
@ -437,6 +437,7 @@ final class EntityKeyboardStaticStickersPanelComponent: Component {
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
|
||||
self.scrollViewContainer.addSubview(self.scrollView)
|
||||
@ -1114,6 +1115,7 @@ final class EntityKeyboardTopPanelComponent: Component {
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = true
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
|
||||
@ -439,6 +439,7 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
|
||||
@ -107,27 +107,6 @@ private final class ItemAnimationContext {
|
||||
|
||||
data.withUnsafeBytes { bytes -> Void in
|
||||
memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow)
|
||||
|
||||
/*var sourceBuffer = vImage_Buffer()
|
||||
sourceBuffer.width = UInt(width)
|
||||
sourceBuffer.height = UInt(height)
|
||||
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!.advanced(by: firstFrame.range.lowerBound))
|
||||
sourceBuffer.rowBytes = bytesPerRow
|
||||
|
||||
var destinationBuffer = vImage_Buffer()
|
||||
destinationBuffer.width = UInt(32)
|
||||
destinationBuffer.height = UInt(32)
|
||||
destinationBuffer.data = context.bytes
|
||||
destinationBuffer.rowBytes = bytesPerRow
|
||||
|
||||
vImageBoxConvolve_ARGB8888(&sourceBuffer,
|
||||
&destinationBuffer,
|
||||
nil,
|
||||
UInt(width - 32 - 16), UInt(height - 32 - 16),
|
||||
UInt32(31),
|
||||
UInt32(31),
|
||||
nil,
|
||||
vImage_Flags(kvImageEdgeExtend))*/
|
||||
}
|
||||
|
||||
guard let image = context.generateImage() else {
|
||||
@ -184,42 +163,6 @@ private final class ItemAnimationContext {
|
||||
destinationBuffer.data = context.bytes
|
||||
destinationBuffer.rowBytes = context.bytesPerRow
|
||||
|
||||
/*var sourceBuffer = vImage_Buffer()
|
||||
sourceBuffer.width = UInt(width)
|
||||
sourceBuffer.height = UInt(height)
|
||||
sourceBuffer.data = UnsafeMutableRawPointer(mutating: bytes.baseAddress!)
|
||||
sourceBuffer.rowBytes = bytesPerRow
|
||||
|
||||
let tempBufferBytes = malloc(blurredHeight * context.bytesPerRow)
|
||||
defer {
|
||||
free(tempBufferBytes)
|
||||
}
|
||||
let temp2BufferBytes = malloc(blurredHeight * context.bytesPerRow)
|
||||
defer {
|
||||
free(temp2BufferBytes)
|
||||
}
|
||||
memset(temp2BufferBytes, Int32(bitPattern: color?.argb ?? 0xffffffff), blurredHeight * context.bytesPerRow)
|
||||
|
||||
var tempBuffer = vImage_Buffer()
|
||||
tempBuffer.width = UInt(blurredWidth)
|
||||
tempBuffer.height = UInt(blurredHeight)
|
||||
tempBuffer.data = tempBufferBytes
|
||||
tempBuffer.rowBytes = context.bytesPerRow
|
||||
|
||||
var temp2Buffer = vImage_Buffer()
|
||||
temp2Buffer.width = UInt(blurredWidth)
|
||||
temp2Buffer.height = UInt(blurredHeight)
|
||||
temp2Buffer.data = temp2BufferBytes
|
||||
temp2Buffer.rowBytes = context.bytesPerRow
|
||||
|
||||
|
||||
|
||||
vImageScale_ARGB8888(&sourceBuffer, &tempBuffer, nil, vImage_Flags(kvImageDoNotTile))
|
||||
//vImageUnpremultiplyData_ARGB8888(&tempBuffer, &tempBuffer, vImage_Flags(kvImageDoNotTile))
|
||||
|
||||
vImagePremultipliedAlphaBlend_ARGB8888(&tempBuffer, &temp2Buffer, &destinationBuffer, vImage_Flags(kvImageDoNotTile))
|
||||
//vImageCopyBuffer(&tempBuffer, &destinationBuffer, 4, vImage_Flags(kvImageDoNotTile))*/
|
||||
|
||||
vImageBoxConvolve_ARGB8888(&destinationBuffer,
|
||||
&destinationBuffer,
|
||||
nil,
|
||||
@ -302,14 +245,6 @@ private final class ItemAnimationContext {
|
||||
}
|
||||
strongSelf.item = result.item
|
||||
strongSelf.updateIsPlaying()
|
||||
|
||||
if result.item == nil {
|
||||
for target in strongSelf.targets.copyItems() {
|
||||
if let target = target.value {
|
||||
target.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import ContextUI
|
||||
import GalleryUI
|
||||
import AttachmentTextInputPanelNode
|
||||
import TelegramPresentationData
|
||||
import TelegramNotices
|
||||
|
||||
private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = {
|
||||
guard let path = getAppBundle().path(forResource: "emoji1016", ofType: "txt") else {
|
||||
@ -45,17 +46,37 @@ private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment,
|
||||
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 {
|
||||
struct InputData: Equatable {
|
||||
var emoji: EmojiPagerContentComponent
|
||||
var stickers: EmojiPagerContentComponent?
|
||||
var gifs: GifPagerContentComponent?
|
||||
var gifs: EntityKeyboardGifContent?
|
||||
var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji]
|
||||
|
||||
init(
|
||||
emoji: EmojiPagerContentComponent,
|
||||
stickers: EmojiPagerContentComponent?,
|
||||
gifs: GifPagerContentComponent?,
|
||||
gifs: EntityKeyboardGifContent?,
|
||||
availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji]
|
||||
) {
|
||||
self.emoji = emoji
|
||||
@ -88,8 +109,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var supergroupId: AnyHashable
|
||||
var id: AnyHashable
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var isPremiumLocked: Bool
|
||||
var isFeatured: Bool
|
||||
var isExpandable: Bool
|
||||
var items: [EmojiPagerContentComponent.Item]
|
||||
}
|
||||
var itemGroups: [ItemGroup] = []
|
||||
@ -134,7 +157,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
//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 {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
//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
|
||||
}
|
||||
}
|
||||
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)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
@ -256,7 +292,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|> distinctUntilChanged
|
||||
|
||||
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
|
||||
guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
|
||||
return
|
||||
@ -397,12 +433,44 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
chatPeerId: chatPeerId
|
||||
)
|
||||
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
|
||||
guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else {
|
||||
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 {
|
||||
let controller = PremiumIntroScreen(context: context, source: .stickers)
|
||||
controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
@ -427,7 +495,52 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
controller.navigationPresentation = .modal
|
||||
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
|
||||
guard let controllerInteraction = controllerInteraction else {
|
||||
@ -447,6 +560,20 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
})
|
||||
])])
|
||||
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
|
||||
@ -494,16 +621,22 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
|
||||
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(
|
||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000),
|
||||
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 {
|
||||
var supergroupId: AnyHashable
|
||||
var id: AnyHashable
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var actionButtonTitle: String?
|
||||
var isPremiumLocked: Bool
|
||||
var isFeatured: 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 {
|
||||
for item in savedStickers.items {
|
||||
guard let item = item.contents.get(SavedStickerItem.self) else {
|
||||
@ -549,13 +726,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
//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 {
|
||||
var count = 0
|
||||
for item in recentStickers.items {
|
||||
guard let item = item.contents.get(RecentMediaItem.self) else {
|
||||
continue
|
||||
@ -576,12 +752,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
//TODO:localize
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
}
|
||||
|
||||
count += 1
|
||||
if count >= 5 {
|
||||
break
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "Recently Used", subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -626,16 +797,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
//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 {
|
||||
guard let item = entry.item as? StickerPackItem else {
|
||||
continue
|
||||
@ -658,7 +824,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
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)
|
||||
} else {
|
||||
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,
|
||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||
var hasClear = false
|
||||
var isEmbedded = false
|
||||
if group.id == AnyHashable("recent") {
|
||||
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
|
||||
)
|
||||
@ -752,16 +938,18 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
)
|
||||
|
||||
// We are going to subscribe to the actual data when the view is loaded
|
||||
let gifItems: Signal<GifPagerContentComponent, NoError> = .single(GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: .recent,
|
||||
items: [],
|
||||
isLoading: false,
|
||||
loadMoreToken: nil
|
||||
let gifItems: Signal<EntityKeyboardGifContent, NoError> = .single(EntityKeyboardGifContent(
|
||||
hasRecentGifs: true,
|
||||
component: GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: .recent,
|
||||
items: [],
|
||||
isLoading: false,
|
||||
loadMoreToken: nil
|
||||
)
|
||||
))
|
||||
|
||||
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
|
||||
return combineLatest(queue: .mainQueue(),
|
||||
emojiItems,
|
||||
stickerItems,
|
||||
@ -822,6 +1010,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
private let defaultToEmojiTab: Bool
|
||||
private var currentInputData: InputData
|
||||
private var inputDataDisposable: Disposable?
|
||||
private var hasRecentGifsDisposable: Disposable?
|
||||
|
||||
private let controllerInteraction: ChatControllerInteraction?
|
||||
|
||||
@ -839,10 +1028,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
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 scheduledInnerTransition: Transition?
|
||||
|
||||
private var gifMode: GifPagerContentComponent.Subject = .recent {
|
||||
private var gifMode: GifPagerContentComponent.Subject? {
|
||||
didSet {
|
||||
if self.gifMode != oldValue {
|
||||
if let gifMode = self.gifMode, gifMode != oldValue {
|
||||
self.reloadGifContext()
|
||||
}
|
||||
}
|
||||
@ -858,17 +1048,17 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
private final class GifContext {
|
||||
private var componentValue: GifPagerContentComponent? {
|
||||
private var componentValue: EntityKeyboardGifContent? {
|
||||
didSet {
|
||||
if let componentValue = self.componentValue {
|
||||
self.componentResult.set(.single(componentValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
private let componentPromise = Promise<GifPagerContentComponent>()
|
||||
private let componentPromise = Promise<EntityKeyboardGifContent>()
|
||||
|
||||
private let componentResult = Promise<GifPagerContentComponent>()
|
||||
var component: Signal<GifPagerContentComponent, NoError> {
|
||||
private let componentResult = Promise<EntityKeyboardGifContent>()
|
||||
var component: Signal<EntityKeyboardGifContent, NoError> {
|
||||
return self.componentResult.get()
|
||||
}
|
||||
private var componentDisposable: Disposable?
|
||||
@ -884,29 +1074,37 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
self.subject = subject
|
||||
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 {
|
||||
case .recent:
|
||||
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] = []
|
||||
for gifItem in savedGifs {
|
||||
items.append(GifPagerContentComponent.Item(
|
||||
file: gifItem.contents.get(RecentMediaItem.self)!.media
|
||||
))
|
||||
}
|
||||
return GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: false,
|
||||
loadMoreToken: nil
|
||||
return EntityKeyboardGifContent(
|
||||
hasRecentGifs: true,
|
||||
component: GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: false,
|
||||
loadMoreToken: nil
|
||||
)
|
||||
)
|
||||
}
|
||||
case .trending:
|
||||
gifItems = trendingGifs
|
||||
|> map { trendingGifs -> GifPagerContentComponent in
|
||||
gifItems = combineLatest(hasRecentGifs, trendingGifs)
|
||||
|> map { hasRecentGifs, trendingGifs -> EntityKeyboardGifContent in
|
||||
var items: [GifPagerContentComponent.Item] = []
|
||||
|
||||
var isLoading = false
|
||||
@ -920,18 +1118,21 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
return GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: nil
|
||||
return EntityKeyboardGifContent(
|
||||
hasRecentGifs: hasRecentGifs,
|
||||
component: GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: nil
|
||||
)
|
||||
)
|
||||
}
|
||||
case let .emojiSearch(query):
|
||||
gifItems = paneGifSearchForQuery(context: context, query: query, offset: nil, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil)
|
||||
|> map { result -> GifPagerContentComponent in
|
||||
gifItems = combineLatest(hasRecentGifs, paneGifSearchForQuery(context: context, query: query, offset: nil, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil))
|
||||
|> map { hasRecentGifs, result -> EntityKeyboardGifContent in
|
||||
var items: [GifPagerContentComponent.Item] = []
|
||||
|
||||
var loadMoreToken: String?
|
||||
@ -947,13 +1148,16 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
return GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken
|
||||
return EntityKeyboardGifContent(
|
||||
hasRecentGifs: hasRecentGifs,
|
||||
component: GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -988,12 +1192,17 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
switch self.subject {
|
||||
case let .emojiSearch(query):
|
||||
let gifItems: Signal<GifPagerContentComponent, NoError>
|
||||
gifItems = paneGifSearchForQuery(context: context, query: query, offset: token, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil)
|
||||
|> map { result -> GifPagerContentComponent in
|
||||
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>
|
||||
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 existingIds = Set<MediaId>()
|
||||
for item in componentValue.items {
|
||||
for item in componentValue.component.items {
|
||||
items.append(item)
|
||||
existingIds.insert(item.file.fileId)
|
||||
}
|
||||
@ -1015,13 +1224,16 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
return GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken
|
||||
return EntityKeyboardGifContent(
|
||||
hasRecentGifs: hasRecentGifs,
|
||||
component: GifPagerContentComponent(
|
||||
context: context,
|
||||
inputInteraction: gifInputInteraction,
|
||||
subject: subject,
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1038,7 +1250,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
private let gifComponent = Promise<GifPagerContentComponent>()
|
||||
private let gifComponent = Promise<EntityKeyboardGifContent>()
|
||||
private var gifInputInteraction: GifPagerContentComponent.InputInteraction?
|
||||
|
||||
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.inputDataDisposable = (combineLatest(queue: .mainQueue(),
|
||||
updatedInputData,
|
||||
self.gifComponent.get()
|
||||
@ -1070,8 +1283,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
var inputData = inputData
|
||||
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.performLayout()
|
||||
strongSelf.performLayout(transition: transition)
|
||||
})
|
||||
|
||||
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 {
|
||||
self.inputDataDisposable?.dispose()
|
||||
self.hasRecentGifsDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func reloadGifContext() {
|
||||
if let gifInputInteraction = self.gifInputInteraction {
|
||||
self.gifContext = GifContext(context: self.context, subject: self.gifMode, gifInputInteraction: gifInputInteraction, trendingGifs: self.trendingGifsPromise.get())
|
||||
if let gifInputInteraction = self.gifInputInteraction, let gifMode = self.gifMode {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
let innerTransition: Transition
|
||||
if let scheduledInnerTransition = self.scheduledInnerTransition {
|
||||
self.scheduledInnerTransition = nil
|
||||
innerTransition = scheduledInnerTransition
|
||||
} else {
|
||||
innerTransition = Transition(transition)
|
||||
}
|
||||
|
||||
let wasMarkedInputCollapsed = self.isMarkInputCollapsed
|
||||
self.isMarkInputCollapsed = false
|
||||
|
||||
@ -1179,14 +1424,14 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let inputNodeInteraction = self.inputNodeInteraction!
|
||||
let trendingGifsPromise = self.trendingGifsPromise
|
||||
|
||||
var mappedTransition = Transition(transition)
|
||||
var mappedTransition = innerTransition
|
||||
|
||||
if wasMarkedInputCollapsed || !isExpanded {
|
||||
mappedTransition = mappedTransition.withUserData(EntityKeyboardComponent.MarkInputCollapsed())
|
||||
}
|
||||
|
||||
var stickerContent: EmojiPagerContentComponent? = self.currentInputData.stickers
|
||||
var gifContent: GifPagerContentComponent? = self.currentInputData.gifs
|
||||
var gifContent: EntityKeyboardGifContent? = self.currentInputData.gifs
|
||||
|
||||
var stickersEnabled = true
|
||||
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
@ -1204,12 +1449,6 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
gifContent = nil
|
||||
}
|
||||
|
||||
if let gifContentValue = gifContent {
|
||||
if gifContentValue.items.isEmpty {
|
||||
gifContent = nil
|
||||
}
|
||||
}
|
||||
|
||||
let entityKeyboardSize = self.entityKeyboardView.update(
|
||||
transition: mappedTransition,
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
@ -1217,7 +1456,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
bottomInset: bottomInset,
|
||||
emojiContent: self.currentInputData.emoji,
|
||||
stickerContent: stickerContent,
|
||||
gifContent: gifContent,
|
||||
gifContent: gifContent?.component,
|
||||
hasRecentGifs: gifContent?.hasRecentGifs ?? false,
|
||||
availableGifSearchEmojies: self.currentInputData.availableGifSearchEmojies,
|
||||
defaultToEmojiTab: self.defaultToEmojiTab,
|
||||
externalTopPanelContainer: self.externalTopPanelContainerImpl,
|
||||
@ -1483,7 +1723,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
||||
self.clipsToBounds = true
|
||||
|
||||
let inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { [weak self] item, _, _, _ in
|
||||
performItemAction: { [weak self] _, item, _, _, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user