mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 03:09:56 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
9b7bbc5ae7
298
NotificationService/Api.h
Normal file
298
NotificationService/Api.h
Normal file
@ -0,0 +1,298 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/*
|
||||
* Layer 1
|
||||
*/
|
||||
|
||||
@class Api1_Photo;
|
||||
@class Api1_Photo_photoEmpty;
|
||||
@class Api1_Photo_photo;
|
||||
|
||||
@class Api1_PhotoSize;
|
||||
@class Api1_PhotoSize_photoSizeEmpty;
|
||||
@class Api1_PhotoSize_photoSize;
|
||||
@class Api1_PhotoSize_photoCachedSize;
|
||||
@class Api1_PhotoSize_photoStrippedSize;
|
||||
|
||||
@class Api1_FileLocation;
|
||||
@class Api1_FileLocation_fileLocationToBeDeprecated;
|
||||
|
||||
@class Api1_DocumentAttribute;
|
||||
@class Api1_DocumentAttribute_documentAttributeImageSize;
|
||||
@class Api1_DocumentAttribute_documentAttributeAnimated;
|
||||
@class Api1_DocumentAttribute_documentAttributeSticker;
|
||||
@class Api1_DocumentAttribute_documentAttributeVideo;
|
||||
@class Api1_DocumentAttribute_documentAttributeAudio;
|
||||
@class Api1_DocumentAttribute_documentAttributeFilename;
|
||||
@class Api1_DocumentAttribute_documentAttributeHasStickers;
|
||||
|
||||
@class Api1_InputStickerSet;
|
||||
@class Api1_InputStickerSet_inputStickerSetEmpty;
|
||||
@class Api1_InputStickerSet_inputStickerSetID;
|
||||
@class Api1_InputStickerSet_inputStickerSetShortName;
|
||||
|
||||
@class Api1_InputFileLocation;
|
||||
@class Api1_InputFileLocation_inputPhotoFileLocation;
|
||||
@class Api1_InputFileLocation_inputDocumentFileLocation;
|
||||
|
||||
@class Api1_MaskCoords;
|
||||
@class Api1_MaskCoords_maskCoords;
|
||||
|
||||
@class Api1_Document;
|
||||
@class Api1_Document_document;
|
||||
|
||||
|
||||
@interface Api1__Environment : NSObject
|
||||
|
||||
+ (NSData *)serializeObject:(id)object;
|
||||
+ (id)parseObject:(NSData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_FunctionContext : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSData *payload;
|
||||
@property (nonatomic, copy, readonly) id (^responseParser)(NSData *);
|
||||
@property (nonatomic, strong, readonly) id metadata;
|
||||
|
||||
- (instancetype)initWithPayload:(NSData *)payload responseParser:(id (^)(NSData *))responseParser metadata:(id)metadata;
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
* Types 1
|
||||
*/
|
||||
|
||||
@interface Api1_Photo : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * pid;
|
||||
|
||||
+ (Api1_Photo_photoEmpty *)photoEmptyWithPid:(NSNumber *)pid;
|
||||
+ (Api1_Photo_photo *)photoWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date sizes:(NSArray *)sizes dcId:(NSNumber *)dcId;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_Photo_photoEmpty : Api1_Photo
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_Photo_photo : Api1_Photo
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * flags;
|
||||
@property (nonatomic, strong, readonly) NSNumber * accessHash;
|
||||
@property (nonatomic, strong, readonly) NSData * fileReference;
|
||||
@property (nonatomic, strong, readonly) NSNumber * date;
|
||||
@property (nonatomic, strong, readonly) NSArray * sizes;
|
||||
@property (nonatomic, strong, readonly) NSNumber * dcId;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface Api1_PhotoSize : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString * type;
|
||||
|
||||
+ (Api1_PhotoSize_photoSizeEmpty *)photoSizeEmptyWithType:(NSString *)type;
|
||||
+ (Api1_PhotoSize_photoSize *)photoSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h size:(NSNumber *)size;
|
||||
+ (Api1_PhotoSize_photoCachedSize *)photoCachedSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h bytes:(NSData *)bytes;
|
||||
+ (Api1_PhotoSize_photoStrippedSize *)photoStrippedSizeWithType:(NSString *)type bytes:(NSData *)bytes;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_PhotoSize_photoSizeEmpty : Api1_PhotoSize
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_PhotoSize_photoSize : Api1_PhotoSize
|
||||
|
||||
@property (nonatomic, strong, readonly) Api1_FileLocation * location;
|
||||
@property (nonatomic, strong, readonly) NSNumber * w;
|
||||
@property (nonatomic, strong, readonly) NSNumber * h;
|
||||
@property (nonatomic, strong, readonly) NSNumber * size;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_PhotoSize_photoCachedSize : Api1_PhotoSize
|
||||
|
||||
@property (nonatomic, strong, readonly) Api1_FileLocation * location;
|
||||
@property (nonatomic, strong, readonly) NSNumber * w;
|
||||
@property (nonatomic, strong, readonly) NSNumber * h;
|
||||
@property (nonatomic, strong, readonly) NSData * bytes;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_PhotoSize_photoStrippedSize : Api1_PhotoSize
|
||||
|
||||
@property (nonatomic, strong, readonly) NSData * bytes;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface Api1_FileLocation : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * volumeId;
|
||||
@property (nonatomic, strong, readonly) NSNumber * localId;
|
||||
|
||||
+ (Api1_FileLocation_fileLocationToBeDeprecated *)fileLocationToBeDeprecatedWithVolumeId:(NSNumber *)volumeId localId:(NSNumber *)localId;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_FileLocation_fileLocationToBeDeprecated : Api1_FileLocation
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface Api1_DocumentAttribute : NSObject
|
||||
|
||||
+ (Api1_DocumentAttribute_documentAttributeImageSize *)documentAttributeImageSizeWithW:(NSNumber *)w h:(NSNumber *)h;
|
||||
+ (Api1_DocumentAttribute_documentAttributeAnimated *)documentAttributeAnimated;
|
||||
+ (Api1_DocumentAttribute_documentAttributeSticker *)documentAttributeStickerWithFlags:(NSNumber *)flags alt:(NSString *)alt stickerset:(Api1_InputStickerSet *)stickerset maskCoords:(Api1_MaskCoords *)maskCoords;
|
||||
+ (Api1_DocumentAttribute_documentAttributeVideo *)documentAttributeVideoWithFlags:(NSNumber *)flags duration:(NSNumber *)duration w:(NSNumber *)w h:(NSNumber *)h;
|
||||
+ (Api1_DocumentAttribute_documentAttributeAudio *)documentAttributeAudioWithFlags:(NSNumber *)flags duration:(NSNumber *)duration title:(NSString *)title performer:(NSString *)performer waveform:(NSData *)waveform;
|
||||
+ (Api1_DocumentAttribute_documentAttributeFilename *)documentAttributeFilenameWithFileName:(NSString *)fileName;
|
||||
+ (Api1_DocumentAttribute_documentAttributeHasStickers *)documentAttributeHasStickers;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_DocumentAttribute_documentAttributeImageSize : Api1_DocumentAttribute
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * w;
|
||||
@property (nonatomic, strong, readonly) NSNumber * h;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_DocumentAttribute_documentAttributeAnimated : Api1_DocumentAttribute
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_DocumentAttribute_documentAttributeSticker : Api1_DocumentAttribute
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * flags;
|
||||
@property (nonatomic, strong, readonly) NSString * alt;
|
||||
@property (nonatomic, strong, readonly) Api1_InputStickerSet * stickerset;
|
||||
@property (nonatomic, strong, readonly) Api1_MaskCoords * maskCoords;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_DocumentAttribute_documentAttributeVideo : Api1_DocumentAttribute
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * flags;
|
||||
@property (nonatomic, strong, readonly) NSNumber * duration;
|
||||
@property (nonatomic, strong, readonly) NSNumber * w;
|
||||
@property (nonatomic, strong, readonly) NSNumber * h;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_DocumentAttribute_documentAttributeAudio : Api1_DocumentAttribute
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * flags;
|
||||
@property (nonatomic, strong, readonly) NSNumber * duration;
|
||||
@property (nonatomic, strong, readonly) NSString * title;
|
||||
@property (nonatomic, strong, readonly) NSString * performer;
|
||||
@property (nonatomic, strong, readonly) NSData * waveform;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_DocumentAttribute_documentAttributeFilename : Api1_DocumentAttribute
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString * fileName;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_DocumentAttribute_documentAttributeHasStickers : Api1_DocumentAttribute
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface Api1_InputStickerSet : NSObject
|
||||
|
||||
+ (Api1_InputStickerSet_inputStickerSetEmpty *)inputStickerSetEmpty;
|
||||
+ (Api1_InputStickerSet_inputStickerSetID *)inputStickerSetIDWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash;
|
||||
+ (Api1_InputStickerSet_inputStickerSetShortName *)inputStickerSetShortNameWithShortName:(NSString *)shortName;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_InputStickerSet_inputStickerSetEmpty : Api1_InputStickerSet
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_InputStickerSet_inputStickerSetID : Api1_InputStickerSet
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * pid;
|
||||
@property (nonatomic, strong, readonly) NSNumber * accessHash;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_InputStickerSet_inputStickerSetShortName : Api1_InputStickerSet
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString * shortName;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface Api1_InputFileLocation : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * pid;
|
||||
@property (nonatomic, strong, readonly) NSNumber * accessHash;
|
||||
@property (nonatomic, strong, readonly) NSData * fileReference;
|
||||
@property (nonatomic, strong, readonly) NSString * thumbSize;
|
||||
|
||||
+ (Api1_InputFileLocation_inputPhotoFileLocation *)inputPhotoFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize;
|
||||
+ (Api1_InputFileLocation_inputDocumentFileLocation *)inputDocumentFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_InputFileLocation_inputPhotoFileLocation : Api1_InputFileLocation
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_InputFileLocation_inputDocumentFileLocation : Api1_InputFileLocation
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface Api1_MaskCoords : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * n;
|
||||
@property (nonatomic, strong, readonly) NSNumber * x;
|
||||
@property (nonatomic, strong, readonly) NSNumber * y;
|
||||
@property (nonatomic, strong, readonly) NSNumber * zoom;
|
||||
|
||||
+ (Api1_MaskCoords_maskCoords *)maskCoordsWithN:(NSNumber *)n x:(NSNumber *)x y:(NSNumber *)y zoom:(NSNumber *)zoom;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_MaskCoords_maskCoords : Api1_MaskCoords
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface Api1_Document : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber * flags;
|
||||
@property (nonatomic, strong, readonly) NSNumber * pid;
|
||||
@property (nonatomic, strong, readonly) NSNumber * accessHash;
|
||||
@property (nonatomic, strong, readonly) NSData * fileReference;
|
||||
@property (nonatomic, strong, readonly) NSNumber * date;
|
||||
@property (nonatomic, strong, readonly) NSString * mimeType;
|
||||
@property (nonatomic, strong, readonly) NSNumber * size;
|
||||
@property (nonatomic, strong, readonly) NSArray * thumbs;
|
||||
@property (nonatomic, strong, readonly) NSNumber * dcId;
|
||||
@property (nonatomic, strong, readonly) NSArray * attributes;
|
||||
|
||||
+ (Api1_Document_document *)documentWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date mimeType:(NSString *)mimeType size:(NSNumber *)size thumbs:(NSArray *)thumbs dcId:(NSNumber *)dcId attributes:(NSArray *)attributes;
|
||||
|
||||
@end
|
||||
|
||||
@interface Api1_Document_document : Api1_Document
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/*
|
||||
* Functions 1
|
||||
*/
|
||||
|
||||
@interface Api1: NSObject
|
||||
|
||||
@end
|
||||
1979
NotificationService/Api.m
Normal file
1979
NotificationService/Api.m
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,8 +29,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
return dict
|
||||
}()
|
||||
|
||||
struct Api {
|
||||
static func parse(_ buffer: Buffer) -> Any? {
|
||||
public struct Api {
|
||||
public static func parse(_ buffer: Buffer) -> Any? {
|
||||
let reader = BufferReader(buffer)
|
||||
if let signature = reader.readInt32() {
|
||||
return parse(reader, signature: signature)
|
||||
@ -38,17 +38,17 @@ struct Api {
|
||||
return nil
|
||||
}
|
||||
|
||||
static func parse(_ reader: BufferReader, signature: Int32) -> Any? {
|
||||
public static func parse(_ reader: BufferReader, signature: Int32) -> Any? {
|
||||
if let parser = parsers[signature] {
|
||||
return parser(reader)
|
||||
}
|
||||
else {
|
||||
//Logger.shared.log("TL", "Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
|
||||
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func parseVector<T>(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? {
|
||||
public static func parseVector<T>(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? {
|
||||
if let count = reader.readInt32() {
|
||||
var array = [T]()
|
||||
var i: Int32 = 0
|
||||
@ -83,7 +83,7 @@ struct Api {
|
||||
return nil
|
||||
}
|
||||
|
||||
static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) {
|
||||
public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) {
|
||||
switch object {
|
||||
case let _1 as Api.Photo:
|
||||
_1.serialize(buffer, boxed)
|
||||
@ -107,12 +107,12 @@ struct Api {
|
||||
}
|
||||
|
||||
}
|
||||
extension Api {
|
||||
enum Photo: TypeConstructorDescription {
|
||||
public extension Api {
|
||||
public enum Photo: TypeConstructorDescription {
|
||||
case photoEmpty(id: Int64)
|
||||
case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], dcId: Int32)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .photoEmpty(let id):
|
||||
if boxed {
|
||||
@ -139,7 +139,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photoEmpty(let id):
|
||||
return ("photoEmpty", [("id", id)])
|
||||
@ -148,7 +148,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_photoEmpty(_ reader: BufferReader) -> Photo? {
|
||||
public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
@ -159,7 +159,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photo(_ reader: BufferReader) -> Photo? {
|
||||
public static func parse_photo(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
@ -192,13 +192,13 @@ extension Api {
|
||||
}
|
||||
|
||||
}
|
||||
enum PhotoSize: TypeConstructorDescription {
|
||||
public enum PhotoSize: TypeConstructorDescription {
|
||||
case photoSizeEmpty(type: String)
|
||||
case photoSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, size: Int32)
|
||||
case photoCachedSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, bytes: Buffer)
|
||||
case photoStrippedSize(type: String, bytes: Buffer)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .photoSizeEmpty(let type):
|
||||
if boxed {
|
||||
@ -236,7 +236,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photoSizeEmpty(let type):
|
||||
return ("photoSizeEmpty", [("type", type)])
|
||||
@ -249,7 +249,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? {
|
||||
public static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
@ -260,7 +260,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
public static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Api.FileLocation?
|
||||
@ -285,7 +285,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
public static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Api.FileLocation?
|
||||
@ -310,7 +310,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
public static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Buffer?
|
||||
@ -326,10 +326,10 @@ extension Api {
|
||||
}
|
||||
|
||||
}
|
||||
enum FileLocation: TypeConstructorDescription {
|
||||
public enum FileLocation: TypeConstructorDescription {
|
||||
case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .fileLocationToBeDeprecated(let volumeId, let localId):
|
||||
if boxed {
|
||||
@ -341,14 +341,14 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .fileLocationToBeDeprecated(let volumeId, let localId):
|
||||
return ("fileLocationToBeDeprecated", [("volumeId", volumeId), ("localId", localId)])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_fileLocationToBeDeprecated(_ reader: BufferReader) -> FileLocation? {
|
||||
public static func parse_fileLocationToBeDeprecated(_ reader: BufferReader) -> FileLocation? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int32?
|
||||
@ -364,7 +364,7 @@ extension Api {
|
||||
}
|
||||
|
||||
}
|
||||
enum DocumentAttribute: TypeConstructorDescription {
|
||||
public enum DocumentAttribute: TypeConstructorDescription {
|
||||
case documentAttributeImageSize(w: Int32, h: Int32)
|
||||
case documentAttributeAnimated
|
||||
case documentAttributeSticker(flags: Int32, alt: String, stickerset: Api.InputStickerSet, maskCoords: Api.MaskCoords?)
|
||||
@ -373,7 +373,7 @@ extension Api {
|
||||
case documentAttributeFilename(fileName: String)
|
||||
case documentAttributeHasStickers
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .documentAttributeImageSize(let w, let h):
|
||||
if boxed {
|
||||
@ -431,7 +431,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .documentAttributeImageSize(let w, let h):
|
||||
return ("documentAttributeImageSize", [("w", w), ("h", h)])
|
||||
@ -450,7 +450,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
public static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
@ -464,10 +464,10 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
public static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
return Api.DocumentAttribute.documentAttributeAnimated
|
||||
}
|
||||
static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
public static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
@ -491,7 +491,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
public static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
@ -511,7 +511,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
public static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
@ -534,7 +534,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
public static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
@ -545,17 +545,17 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeHasStickers(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
public static func parse_documentAttributeHasStickers(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
return Api.DocumentAttribute.documentAttributeHasStickers
|
||||
}
|
||||
|
||||
}
|
||||
enum InputStickerSet: TypeConstructorDescription {
|
||||
public enum InputStickerSet: TypeConstructorDescription {
|
||||
case inputStickerSetEmpty
|
||||
case inputStickerSetID(id: Int64, accessHash: Int64)
|
||||
case inputStickerSetShortName(shortName: String)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .inputStickerSetEmpty:
|
||||
if boxed {
|
||||
@ -579,7 +579,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .inputStickerSetEmpty:
|
||||
return ("inputStickerSetEmpty", [])
|
||||
@ -590,10 +590,10 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? {
|
||||
public static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? {
|
||||
return Api.InputStickerSet.inputStickerSetEmpty
|
||||
}
|
||||
static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? {
|
||||
public static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
@ -607,7 +607,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? {
|
||||
public static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
@ -620,11 +620,11 @@ extension Api {
|
||||
}
|
||||
|
||||
}
|
||||
enum InputFileLocation: TypeConstructorDescription {
|
||||
public enum InputFileLocation: TypeConstructorDescription {
|
||||
case inputPhotoFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String)
|
||||
case inputDocumentFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize):
|
||||
if boxed {
|
||||
@ -647,7 +647,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize):
|
||||
return ("inputPhotoFileLocation", [("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("thumbSize", thumbSize)])
|
||||
@ -656,7 +656,7 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? {
|
||||
public static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
@ -676,7 +676,7 @@ extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? {
|
||||
public static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
@ -698,10 +698,10 @@ extension Api {
|
||||
}
|
||||
|
||||
}
|
||||
enum MaskCoords: TypeConstructorDescription {
|
||||
public enum MaskCoords: TypeConstructorDescription {
|
||||
case maskCoords(n: Int32, x: Double, y: Double, zoom: Double)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .maskCoords(let n, let x, let y, let zoom):
|
||||
if boxed {
|
||||
@ -715,14 +715,14 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .maskCoords(let n, let x, let y, let zoom):
|
||||
return ("maskCoords", [("n", n), ("x", x), ("y", y), ("zoom", zoom)])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? {
|
||||
public static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Double?
|
||||
@ -744,10 +744,10 @@ extension Api {
|
||||
}
|
||||
|
||||
}
|
||||
enum Document: TypeConstructorDescription {
|
||||
public enum Document: TypeConstructorDescription {
|
||||
case document(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, mimeType: String, size: Int32, thumbs: [Api.PhotoSize]?, dcId: Int32, attributes: [Api.DocumentAttribute])
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .document(let flags, let id, let accessHash, let fileReference, let date, let mimeType, let size, let thumbs, let dcId, let attributes):
|
||||
if boxed {
|
||||
@ -775,14 +775,14 @@ extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .document(let flags, let id, let accessHash, let fileReference, let date, let mimeType, let size, let thumbs, let dcId, let attributes):
|
||||
return ("document", [("flags", flags), ("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("date", date), ("mimeType", mimeType), ("size", size), ("thumbs", thumbs), ("dcId", dcId), ("attributes", attributes)])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_document(_ reader: BufferReader) -> Document? {
|
||||
public static func parse_document(_ reader: BufferReader) -> Document? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
@ -827,8 +827,8 @@ extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
extension Api {
|
||||
struct functions {
|
||||
public extension Api {
|
||||
public struct functions {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
7
NotificationService/Attachments.h
Normal file
7
NotificationService/Attachments.h
Normal file
@ -0,0 +1,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
id _Nullable parseAttachment(NSData * _Nonnull data);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
30
NotificationService/Attachments.m
Normal file
30
NotificationService/Attachments.m
Normal file
@ -0,0 +1,30 @@
|
||||
#import "Attachments.h"
|
||||
|
||||
#import <MtProtoKitDynamic/MtProtoKitDynamic.h>
|
||||
#import "Api.h"
|
||||
|
||||
id _Nullable parseAttachment(NSData * _Nonnull data) {
|
||||
if (data.length < 4) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
MTInputStream *inputStream = [[MTInputStream alloc] initWithData:data];
|
||||
|
||||
int32_t signature = [inputStream readInt32];
|
||||
|
||||
NSData *dataToParse = nil;
|
||||
if (signature == 0x3072cfa1) {
|
||||
NSData *bytes = [inputStream readBytes];
|
||||
if (bytes != nil) {
|
||||
dataToParse = [MTGzip decompress:bytes];
|
||||
}
|
||||
} else {
|
||||
dataToParse = data;
|
||||
}
|
||||
|
||||
if (dataToParse == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [Api1__Environment parseObject:dataToParse];
|
||||
}
|
||||
11
NotificationService/FetchImage.h
Normal file
11
NotificationService/FetchImage.h
Normal file
@ -0,0 +1,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "StoredAccountInfos.h"
|
||||
#import "Api.h"
|
||||
#import <BuildConfig/BuildConfig.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _Nullable proxyConnection, StoredAccountInfo *account, Api1_InputFileLocation *inputFileLocation, int32_t datacenterId, void (^_completion)(NSData * _Nullable));
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
195
NotificationService/FetchImage.m
Normal file
195
NotificationService/FetchImage.m
Normal file
@ -0,0 +1,195 @@
|
||||
#import "FetchImage.h"
|
||||
|
||||
#import <MTProtoKitDynamic/MTProtoKitDynamic.h>
|
||||
|
||||
#import "Serialization.h"
|
||||
|
||||
@interface InMemoryKeychain : NSObject <MTKeychain> {
|
||||
NSMutableDictionary *_dict;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation InMemoryKeychain
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_dict = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setObject:(id)object forKey:(NSString *)aKey group:(NSString *)group {
|
||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
|
||||
_dict[[NSString stringWithFormat:@"%@:%@", group, aKey]] = data;
|
||||
}
|
||||
|
||||
- (id)objectForKey:(NSString *)aKey group:(NSString *)group {
|
||||
NSData *data = _dict[[NSString stringWithFormat:@"%@:%@", group, aKey]];
|
||||
if (data != nil) {
|
||||
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeObjectForKey:(NSString *)aKey group:(NSString *)group {
|
||||
[_dict removeObjectForKey:[NSString stringWithFormat:@"%@:%@", group, aKey]];
|
||||
}
|
||||
|
||||
- (void)dropGroup:(NSString *)group {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void MTLoggingFunction(NSString *string, va_list args) {
|
||||
NSLogv(string, args);
|
||||
}
|
||||
|
||||
@interface ParsedFile : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSData * _Nullable data;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ParsedFile
|
||||
|
||||
- (instancetype)initWithData:(NSData * _Nullable)data {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = data;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _Nullable proxyConnection, StoredAccountInfo *account, Api1_InputFileLocation *inputFileLocation, int32_t datacenterId, void (^completion)(NSData * _Nullable)) {
|
||||
MTLogSetEnabled(true);
|
||||
MTLogSetLoggingFunction(&MTLoggingFunction);
|
||||
|
||||
Serialization *serialization = [[Serialization alloc] init];
|
||||
|
||||
MTApiEnvironment *apiEnvironment = [[MTApiEnvironment alloc] init];
|
||||
|
||||
apiEnvironment.apiId = buildConfig.apiId;
|
||||
apiEnvironment.langPack = @"ios";
|
||||
apiEnvironment.layer = @([serialization currentLayer]);
|
||||
apiEnvironment.disableUpdates = true;
|
||||
apiEnvironment = [apiEnvironment withUpdatedLangPackCode:@"en"];
|
||||
|
||||
if (proxyConnection != nil) {
|
||||
apiEnvironment = [apiEnvironment withUpdatedSocksProxySettings:[[MTSocksProxySettings alloc] initWithIp:proxyConnection.host port:(uint16_t)proxyConnection.port username:proxyConnection.username password:proxyConnection.password secret:proxyConnection.secret]];
|
||||
}
|
||||
|
||||
MTContext *context = [[MTContext alloc] initWithSerialization:serialization apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:false];
|
||||
|
||||
NSDictionary *seedAddressList = @{};
|
||||
|
||||
if (account.isTestingEnvironment) {
|
||||
seedAddressList = @{
|
||||
@(1): @[@"149.154.175.10"],
|
||||
@(2): @[@"149.154.167.40"]
|
||||
};
|
||||
} else {
|
||||
seedAddressList = @{
|
||||
@(1): @[@"149.154.175.50", @"2001:b28:f23d:f001::a"],
|
||||
@(2): @[@"149.154.167.50", @"2001:67c:4e8:f002::a"],
|
||||
@(3): @[@"149.154.175.100", @"2001:b28:f23d:f003::a"],
|
||||
@(4): @[@"149.154.167.91", @"2001:67c:4e8:f004::a"],
|
||||
@(5): @[@"149.154.171.5", @"2001:b28:f23f:f005::a"]
|
||||
};
|
||||
}
|
||||
|
||||
for (NSNumber *datacenterId in seedAddressList) {
|
||||
NSMutableArray *addressList = [[NSMutableArray alloc] init];
|
||||
for (NSString *host in seedAddressList[datacenterId]) {
|
||||
[addressList addObject:[[MTDatacenterAddress alloc] initWithIp:host port:443 preferForMedia:false restrictToTcp:false cdn:false preferForProxy:false secret:nil]];
|
||||
}
|
||||
[context setSeedAddressSetForDatacenterWithId:[datacenterId intValue] seedAddressSet:[[MTDatacenterAddressSet alloc] initWithAddressList:addressList]];
|
||||
}
|
||||
|
||||
InMemoryKeychain *keychain = [[InMemoryKeychain alloc] init];
|
||||
context.keychain = keychain;
|
||||
|
||||
[context performBatchUpdates:^{
|
||||
for (NSNumber *datacenterId in account.datacenters) {
|
||||
AccountDatacenterInfo *info = account.datacenters[datacenterId];
|
||||
if (info.addressList.count != 0) {
|
||||
NSMutableArray *list = [[NSMutableArray alloc] init];
|
||||
for (AccountDatacenterAddress *address in info.addressList) {
|
||||
[list addObject:[[MTDatacenterAddress alloc] initWithIp:address.host port:address.port preferForMedia:address.isMedia restrictToTcp:false cdn:false preferForProxy:address.isProxy secret:address.secret]];
|
||||
}
|
||||
[context updateAddressSetForDatacenterWithId:[datacenterId intValue] addressSet:[[MTDatacenterAddressSet alloc] initWithAddressList:list] forceUpdateSchemes:true];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
for (NSNumber *datacenterId in account.datacenters) {
|
||||
AccountDatacenterInfo *info = account.datacenters[datacenterId];
|
||||
[context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{} mainTempAuthKey:nil mediaTempAuthKey:nil]];
|
||||
}
|
||||
|
||||
MTProto * mtProto = [[MTProto alloc] initWithContext:context datacenterId:datacenterId usageCalculationInfo:nil];
|
||||
mtProto.useTempAuthKeys = context.useTempAuthKeys;
|
||||
mtProto.checkForProxyConnectionIssues = false;
|
||||
|
||||
MTRequestMessageService *requestService = [[MTRequestMessageService alloc] initWithContext:context];
|
||||
[mtProto addMessageService:requestService];
|
||||
|
||||
MTRequest *request = [[MTRequest alloc] init];
|
||||
|
||||
MTOutputStream *outputStream = [[MTOutputStream alloc] init];
|
||||
[outputStream writeInt32:-475607115]; //upload.getFile
|
||||
[outputStream writeData:[Api1__Environment serializeObject:inputFileLocation]];
|
||||
|
||||
[outputStream writeInt32:0];
|
||||
[outputStream writeInt32:32 * 1024];
|
||||
|
||||
[request setPayload:[outputStream currentBytes] metadata:@"getFile" shortMetadata:@"getFile" responseParser:^id(NSData *response) {
|
||||
MTInputStream *inputStream = [[MTInputStream alloc] initWithData:response];
|
||||
int32_t signature = [inputStream readInt32];
|
||||
if (signature != 157948117) {
|
||||
return [[ParsedFile alloc] initWithData:nil];
|
||||
}
|
||||
[inputStream readInt32]; //type
|
||||
[inputStream readInt32]; //mtime
|
||||
|
||||
return [[ParsedFile alloc] initWithData:[inputStream readBytes]];
|
||||
}];
|
||||
|
||||
request.dependsOnPasswordEntry = false;
|
||||
request.shouldContinueExecutionWithErrorContext = ^bool (__unused MTRequestErrorContext *errorContext) {
|
||||
return true;
|
||||
};
|
||||
|
||||
request.completed = ^(id boxedResponse, __unused NSTimeInterval completionTimestamp, MTRpcError *error) {
|
||||
if (error != nil) {
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
} else {
|
||||
if ([boxedResponse isKindOfClass:[ParsedFile class]]) {
|
||||
if (completion) {
|
||||
completion(((ParsedFile *)boxedResponse).data);
|
||||
}
|
||||
} else {
|
||||
if (completion) {
|
||||
completion(nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[requestService addRequest:request];
|
||||
[mtProto resume];
|
||||
|
||||
id internalId = request.internalId;
|
||||
return ^{
|
||||
[requestService removeRequestByInternalId:internalId];
|
||||
[context performBatchUpdates:^{
|
||||
}];
|
||||
[mtProto stop];
|
||||
};
|
||||
}
|
||||
@ -25,7 +25,7 @@
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.usernotifications.service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||
<string>NotificationService</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
10
NotificationService/NotificationService.h
Normal file
10
NotificationService/NotificationService.h
Normal file
@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NotificationService : UNNotificationServiceExtension
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
359
NotificationService/NotificationService.m
Normal file
359
NotificationService/NotificationService.m
Normal file
@ -0,0 +1,359 @@
|
||||
#import "NotificationService.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <BuildConfig/BuildConfig.h>
|
||||
|
||||
#import "StoredAccountInfos.h"
|
||||
#import "Attachments.h"
|
||||
#import "Api.h"
|
||||
#import "FetchImage.h"
|
||||
|
||||
static NSData * _Nullable parseBase64(NSString *string) {
|
||||
string = [string stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
|
||||
while (string.length % 4 != 0) {
|
||||
string = [string stringByAppendingString:@"="];
|
||||
}
|
||||
return [[NSData alloc] initWithBase64EncodedString:string options:0];
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
PeerNamespaceCloudUser = 0,
|
||||
PeerNamespaceCloudGroup = 1,
|
||||
PeerNamespaceCloudChannel = 2,
|
||||
PeerNamespaceSecretChat = 3
|
||||
} PeerNamespace;
|
||||
|
||||
static int64_t makePeerId(int32_t namespace, int32_t value) {
|
||||
return (((int64_t)(namespace)) << 32) | ((int64_t)((uint64_t)((uint32_t)value)));
|
||||
}
|
||||
|
||||
@interface NotificationService () {
|
||||
NSString * _Nullable _rootPath;
|
||||
NSString * _Nullable _baseAppBundleId;
|
||||
void (^_contentHandler)(UNNotificationContent *);
|
||||
UNMutableNotificationContent * _Nullable _bestAttemptContent;
|
||||
void (^_cancelFetch)(void);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NotificationService
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
NSString *appBundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
|
||||
NSRange lastDotRange = [appBundleIdentifier rangeOfString:@"." options:NSBackwardsSearch];
|
||||
if (lastDotRange.location != NSNotFound) {
|
||||
_baseAppBundleId = [appBundleIdentifier substringToIndex:lastDotRange.location];
|
||||
NSString *appGroupName = [@"group." stringByAppendingString:_baseAppBundleId];
|
||||
NSURL *appGroupUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupName];
|
||||
|
||||
if (appGroupUrl != nil) {
|
||||
NSString *rootPath = [[appGroupUrl path] stringByAppendingPathComponent:@"telegram-data"];
|
||||
_rootPath = rootPath;
|
||||
} else {
|
||||
NSAssert(false, @"appGroupUrl == nil");
|
||||
}
|
||||
} else {
|
||||
NSAssert(false, @"Invalid bundle id");
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
|
||||
if (_rootPath == nil) {
|
||||
contentHandler(request.content);
|
||||
return;
|
||||
}
|
||||
|
||||
_contentHandler = [contentHandler copy];
|
||||
_bestAttemptContent = (UNMutableNotificationContent *)[request.content mutableCopy];
|
||||
|
||||
NSString * _Nullable encryptedPayload = request.content.userInfo[@"p"];
|
||||
NSData * _Nullable encryptedData = nil;
|
||||
if (encryptedPayload != nil && [encryptedPayload isKindOfClass:[NSString class]]) {
|
||||
encryptedData = parseBase64(encryptedPayload);
|
||||
}
|
||||
|
||||
StoredAccountInfos * _Nullable accountInfos = [StoredAccountInfos loadFromPath:[_rootPath stringByAppendingPathComponent:@"accounts-shared-data"]];
|
||||
|
||||
int selectedAccountIndex = -1;
|
||||
NSDictionary *decryptedPayload = decryptedNotificationPayload(accountInfos.accounts, encryptedData, &selectedAccountIndex);
|
||||
|
||||
if (decryptedPayload != nil && selectedAccountIndex != -1) {
|
||||
StoredAccountInfo *account = accountInfos.accounts[selectedAccountIndex];
|
||||
|
||||
NSMutableDictionary *userInfo = nil;
|
||||
if (_bestAttemptContent.userInfo != nil) {
|
||||
userInfo = [[NSMutableDictionary alloc] initWithDictionary:_bestAttemptContent.userInfo];
|
||||
} else {
|
||||
userInfo = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
userInfo[@"accountId"] = @(account.accountId);
|
||||
|
||||
int64_t peerId = 0;
|
||||
int32_t messageId = 0;
|
||||
bool silent = false;
|
||||
|
||||
NSString *messageIdString = decryptedPayload[@"msg_id"];
|
||||
if ([messageIdString isKindOfClass:[NSString class]]) {
|
||||
userInfo[@"msg_id"] = messageIdString;
|
||||
messageId = [messageIdString intValue];
|
||||
}
|
||||
|
||||
NSString *fromIdString = decryptedPayload[@"from_id"];
|
||||
if ([fromIdString isKindOfClass:[NSString class]]) {
|
||||
userInfo[@"from_id"] = fromIdString;
|
||||
peerId = makePeerId(PeerNamespaceCloudUser, [fromIdString intValue]);
|
||||
}
|
||||
|
||||
NSString *chatIdString = decryptedPayload[@"chat_id"];
|
||||
if ([chatIdString isKindOfClass:[NSString class]]) {
|
||||
userInfo[@"chat_id"] = chatIdString;
|
||||
peerId = makePeerId(PeerNamespaceCloudGroup, [chatIdString intValue]);
|
||||
}
|
||||
|
||||
NSString *channelIdString = decryptedPayload[@"channel_id"];
|
||||
if ([channelIdString isKindOfClass:[NSString class]]) {
|
||||
userInfo[@"channel_id"] = channelIdString;
|
||||
peerId = makePeerId(PeerNamespaceCloudChannel, [channelIdString intValue]);
|
||||
}
|
||||
|
||||
NSString *silentString = decryptedPayload[@"silent"];
|
||||
if ([silentString isKindOfClass:[NSString class]]) {
|
||||
silent = [silentString intValue] != 0;
|
||||
}
|
||||
|
||||
NSString *attachmentDataString = decryptedPayload[@"attachb64"];
|
||||
NSData *attachmentData = nil;
|
||||
id parsedAttachment = nil;
|
||||
if ([attachmentDataString isKindOfClass:[NSString class]]) {
|
||||
attachmentData = parseBase64(attachmentDataString);
|
||||
if (attachmentData != nil) {
|
||||
parsedAttachment = parseAttachment(attachmentData);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *imagesPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"aps-data"];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:imagesPath withIntermediateDirectories:true attributes:nil error:nil];
|
||||
NSString *accountBasePath = [_rootPath stringByAppendingPathComponent:[NSString stringWithFormat:@"account-%llud", account.accountId]];
|
||||
|
||||
NSString *mediaBoxPath = [accountBasePath stringByAppendingPathComponent:@"/postbox/media"];
|
||||
|
||||
NSString *tempImagePath = nil;
|
||||
NSString *mediaBoxThumbnailImagePath = nil;
|
||||
|
||||
int32_t fileDatacenterId = 0;
|
||||
Api1_InputFileLocation *inputFileLocation = nil;
|
||||
|
||||
NSString *fetchResourceId = nil;
|
||||
bool isPng = false;
|
||||
bool isExpandableMedia = false;
|
||||
|
||||
if (parsedAttachment != nil) {
|
||||
if ([parsedAttachment isKindOfClass:[Api1_Photo_photo class]]) {
|
||||
Api1_Photo_photo *photo = parsedAttachment;
|
||||
isExpandableMedia = true;
|
||||
for (id size in photo.sizes) {
|
||||
if ([size isKindOfClass:[Api1_PhotoSize_photoSize class]]) {
|
||||
Api1_PhotoSize_photoSize *sizeValue = size;
|
||||
if ([sizeValue.type isEqualToString:@"m"]) {
|
||||
inputFileLocation = [Api1_InputFileLocation inputPhotoFileLocationWithPid:photo.pid accessHash:photo.accessHash fileReference:photo.fileReference thumbSize:sizeValue.type];
|
||||
fileDatacenterId = [photo.dcId intValue];
|
||||
fetchResourceId = [NSString stringWithFormat:@"telegram-cloud-photo-size-%@-%@-%@", photo.dcId, photo.pid, sizeValue.type];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ([parsedAttachment isKindOfClass:[Api1_Document_document class]]) {
|
||||
Api1_Document_document *document = parsedAttachment;
|
||||
|
||||
bool isSticker = false;
|
||||
for (id attribute in document.attributes) {
|
||||
if ([attribute isKindOfClass:[Api1_DocumentAttribute_documentAttributeSticker class]]) {
|
||||
isSticker = true;
|
||||
}
|
||||
}
|
||||
bool isAnimatedSticker = [document.mimeType isEqualToString:@"application/x-tgsticker"];
|
||||
if (isSticker || isAnimatedSticker) {
|
||||
isExpandableMedia = true;
|
||||
}
|
||||
for (id size in document.thumbs) {
|
||||
if ([size isKindOfClass:[Api1_PhotoSize_photoSize class]]) {
|
||||
Api1_PhotoSize_photoSize *photoSize = size;
|
||||
if ((isSticker && [photoSize.type isEqualToString:@"s"]) || [photoSize.type isEqualToString:@"m"]) {
|
||||
if (isSticker) {
|
||||
isPng = true;
|
||||
}
|
||||
inputFileLocation = [Api1_InputFileLocation inputDocumentFileLocationWithPid:document.pid accessHash:document.accessHash fileReference:document.fileReference thumbSize:photoSize.type];
|
||||
fileDatacenterId = [document.dcId intValue];
|
||||
fetchResourceId = [NSString stringWithFormat:@"telegram-cloud-document-size-%@-%@-%@", document.dcId, document.pid, photoSize.type];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchResourceId != nil) {
|
||||
tempImagePath = [imagesPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", fetchResourceId, isPng ? @"png" : @"jpg"]];
|
||||
mediaBoxThumbnailImagePath = [mediaBoxPath stringByAppendingPathComponent:fetchResourceId];
|
||||
}
|
||||
|
||||
NSDictionary *aps = decryptedPayload[@"aps"];
|
||||
if ([aps isKindOfClass:[NSDictionary class]]) {
|
||||
id alert = aps[@"alert"];
|
||||
if ([alert isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *alertDict = alert;
|
||||
NSString *title = alertDict[@"title"];
|
||||
NSString *subtitle = alertDict[@"subtitle"];
|
||||
NSString *body = alertDict[@"body"];
|
||||
if (![title isKindOfClass:[NSString class]]) {
|
||||
title = @"";
|
||||
}
|
||||
if (![subtitle isKindOfClass:[NSString class]]) {
|
||||
subtitle = @"";
|
||||
}
|
||||
if (![body isKindOfClass:[NSString class]]) {
|
||||
body = nil;
|
||||
}
|
||||
if (title.length != 0 && silent) {
|
||||
title = [title stringByAppendingString:@" 🔕"];
|
||||
}
|
||||
_bestAttemptContent.title = title;
|
||||
_bestAttemptContent.subtitle = subtitle;
|
||||
_bestAttemptContent.body = body;
|
||||
} else if ([alert isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.title = @"";
|
||||
_bestAttemptContent.subtitle = @"";
|
||||
_bestAttemptContent.body = alert;
|
||||
}
|
||||
|
||||
NSString *threadIdString = aps[@"thread-id"];
|
||||
if ([threadIdString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.threadIdentifier = threadIdString;
|
||||
}
|
||||
NSString *soundString = aps[@"sound"];
|
||||
if ([soundString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.sound = [UNNotificationSound soundNamed:soundString];
|
||||
}
|
||||
NSString *categoryString = aps[@"category"];
|
||||
if ([categoryString isKindOfClass:[NSString class]]) {
|
||||
_bestAttemptContent.categoryIdentifier = categoryString;
|
||||
if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) {
|
||||
userInfo[@"peerId"] = @(peerId);
|
||||
userInfo[@"messageId.namespace"] = @(0);
|
||||
userInfo[@"messageId.id"] = @(messageId);
|
||||
|
||||
userInfo[@"media"] = [attachmentData base64EncodedStringWithOptions:0];
|
||||
|
||||
if (isExpandableMedia) {
|
||||
if ([categoryString isEqualToString:@"r"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withReplyMedia";
|
||||
} else if ([categoryString isEqualToString:@"m"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withMuteMedia";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accountInfos.accounts.count > 1) {
|
||||
if (_bestAttemptContent.title.length != 0 && account.peerName.length != 0) {
|
||||
_bestAttemptContent.title = [NSString stringWithFormat:@"%@ → %@", _bestAttemptContent.title, account.peerName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_bestAttemptContent.userInfo = userInfo;
|
||||
|
||||
if (_cancelFetch) {
|
||||
_cancelFetch();
|
||||
_cancelFetch = nil;
|
||||
}
|
||||
|
||||
if (mediaBoxThumbnailImagePath != nil && tempImagePath != nil && inputFileLocation != nil) {
|
||||
NSData *data = [NSData dataWithContentsOfFile:mediaBoxThumbnailImagePath];
|
||||
if (data != nil) {
|
||||
NSData *tempData = data;
|
||||
if (isPng) {
|
||||
/*if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() {
|
||||
tempData = imageData
|
||||
}*/
|
||||
}
|
||||
if ([tempData writeToFile:tempImagePath atomically:true]) {
|
||||
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:tempImagePath] options:nil error:nil];
|
||||
if (attachment != nil) {
|
||||
_bestAttemptContent.attachments = @[attachment];
|
||||
}
|
||||
}
|
||||
if (_contentHandler && _bestAttemptContent != nil) {
|
||||
_contentHandler(_bestAttemptContent);
|
||||
}
|
||||
} else {
|
||||
BuildConfig *buildConfig = [[BuildConfig alloc] initWithBaseAppBundleId:_baseAppBundleId];
|
||||
|
||||
__weak NotificationService *weakSelf = self;
|
||||
_cancelFetch = fetchImage(buildConfig, accountInfos.proxy, account, inputFileLocation, fileDatacenterId, ^(NSData * _Nullable data) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__strong NotificationService *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
if (strongSelf->_cancelFetch) {
|
||||
strongSelf->_cancelFetch();
|
||||
strongSelf->_cancelFetch = nil;
|
||||
|
||||
if (data != nil) {
|
||||
[data writeToFile:mediaBoxThumbnailImagePath atomically:true];
|
||||
NSData *tempData = data;
|
||||
if (isPng) {
|
||||
/*if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() {
|
||||
tempData = imageData
|
||||
}*/
|
||||
}
|
||||
if ([tempData writeToFile:tempImagePath atomically:true]) {
|
||||
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:tempImagePath] options:nil error:nil];
|
||||
if (attachment != nil) {
|
||||
strongSelf->_bestAttemptContent.attachments = @[attachment];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strongSelf->_contentHandler && strongSelf->_bestAttemptContent != nil) {
|
||||
strongSelf->_contentHandler(strongSelf->_bestAttemptContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (_contentHandler && _bestAttemptContent != nil) {
|
||||
_contentHandler(_bestAttemptContent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_contentHandler && _bestAttemptContent != nil) {
|
||||
_contentHandler(_bestAttemptContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)serviceExtensionTimeWillExpire {
|
||||
if (_cancelFetch) {
|
||||
_cancelFetch();
|
||||
_cancelFetch = nil;
|
||||
}
|
||||
|
||||
if (_contentHandler) {
|
||||
if(_bestAttemptContent) {
|
||||
_contentHandler(_bestAttemptContent);
|
||||
_bestAttemptContent = nil;
|
||||
}
|
||||
_contentHandler = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
10
NotificationService/Serialization.h
Normal file
10
NotificationService/Serialization.h
Normal file
@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MTProtoKitDynamic/MTProtoKitDynamic.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Serialization : NSObject <MTSerialization>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
35
NotificationService/Serialization.m
Normal file
35
NotificationService/Serialization.m
Normal file
@ -0,0 +1,35 @@
|
||||
#import "Serialization.h"
|
||||
|
||||
@implementation Serialization
|
||||
|
||||
- (NSUInteger)currentLayer {
|
||||
return 105;
|
||||
}
|
||||
|
||||
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (MTExportAuthorizationResponseParser _Nonnull)exportAuthorization:(int32_t)datacenterId data:(__autoreleasing NSData **)data {
|
||||
return ^MTExportedAuthorizationData *(NSData *resultData) {
|
||||
return nil;
|
||||
};
|
||||
}
|
||||
|
||||
- (NSData * _Nonnull)importAuthorization:(int32_t)authId bytes:(NSData * _Nonnull)bytes {
|
||||
return [NSData data];
|
||||
}
|
||||
|
||||
- (MTRequestDatacenterAddressListParser)requestDatacenterAddressWithData:(__autoreleasing NSData **)data {
|
||||
return ^MTDatacenterAddressListData *(NSData *resultData) {
|
||||
return nil;
|
||||
};
|
||||
}
|
||||
|
||||
- (MTRequestNoopParser)requestNoop:(__autoreleasing NSData **)data {
|
||||
return ^id(NSData *resultData) {
|
||||
return nil;
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
67
NotificationService/StoredAccountInfos.h
Normal file
67
NotificationService/StoredAccountInfos.h
Normal file
@ -0,0 +1,67 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AccountNotificationKey: NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSData *keyId;
|
||||
@property (nonatomic, strong, readonly) NSData *data;
|
||||
|
||||
@end
|
||||
|
||||
@interface AccountDatacenterKey: NSObject
|
||||
|
||||
@property (nonatomic, readonly) int64_t keyId;
|
||||
@property (nonatomic, strong, readonly) NSData *data;
|
||||
|
||||
@end
|
||||
|
||||
@interface AccountDatacenterAddress: NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString *host;
|
||||
@property (nonatomic, readonly) int32_t port;
|
||||
@property (nonatomic, readonly) bool isMedia;
|
||||
@property (nonatomic, strong, readonly) NSData * _Nullable secret;
|
||||
|
||||
@end
|
||||
|
||||
@interface AccountDatacenterInfo: NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) AccountDatacenterKey *masterKey;
|
||||
@property (nonatomic, strong, readonly) NSArray<AccountDatacenterAddress *> *addressList;
|
||||
|
||||
@end
|
||||
|
||||
@interface AccountProxyConnection: NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString *host;
|
||||
@property (nonatomic, readonly) int32_t port;
|
||||
@property (nonatomic, strong) NSString * _Nullable username;
|
||||
@property (nonatomic, strong) NSString * _Nullable password;
|
||||
@property (nonatomic, strong) NSData * _Nullable secret;
|
||||
|
||||
@end
|
||||
|
||||
@interface StoredAccountInfo : NSObject
|
||||
|
||||
@property (nonatomic, readonly) int64_t accountId;
|
||||
@property (nonatomic, readonly) int32_t primaryId;
|
||||
@property (nonatomic, readonly) bool isTestingEnvironment;
|
||||
@property (nonatomic, strong, readonly) NSString *peerName;
|
||||
@property (nonatomic, strong, readonly) NSDictionary<NSNumber *, AccountDatacenterInfo *> *datacenters;
|
||||
@property (nonatomic, strong, readonly) AccountNotificationKey *notificationKey;
|
||||
|
||||
@end
|
||||
|
||||
@interface StoredAccountInfos : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) AccountProxyConnection * _Nullable proxy;
|
||||
@property (nonatomic, strong, readonly) NSArray<StoredAccountInfo *> *accounts;
|
||||
|
||||
+ (StoredAccountInfos * _Nullable)loadFromPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
|
||||
NSDictionary * _Nullable decryptedNotificationPayload(NSArray<StoredAccountInfo *> *accounts, NSData *data, int *selectedAccountIndex);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
412
NotificationService/StoredAccountInfos.m
Normal file
412
NotificationService/StoredAccountInfos.m
Normal file
@ -0,0 +1,412 @@
|
||||
#import "StoredAccountInfos.h"
|
||||
|
||||
#import <MtProtoKitDynamic/MtProtoKitDynamic.h>
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
@implementation AccountNotificationKey
|
||||
|
||||
- (instancetype)initWithKeyId:(NSData *)keyId data:(NSData *)data {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_keyId = keyId;
|
||||
_data = data;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
|
||||
NSString *keyIdString = dict[@"id"];
|
||||
NSData *keyId = nil;
|
||||
if ([keyIdString isKindOfClass:[NSString class]]) {
|
||||
keyId = [[NSData alloc] initWithBase64EncodedString:keyIdString options:0];
|
||||
}
|
||||
if (keyId == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *dataString = dict[@"data"];
|
||||
NSData *data = nil;
|
||||
if ([dataString isKindOfClass:[NSString class]]) {
|
||||
data = [[NSData alloc] initWithBase64EncodedString:dataString options:0];
|
||||
}
|
||||
if (data == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [[AccountNotificationKey alloc] initWithKeyId:keyId data:data];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AccountDatacenterKey
|
||||
|
||||
- (instancetype)initWithKeyId:(int64_t)keyId data:(NSData *)data {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_keyId = keyId;
|
||||
_data = data;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
|
||||
NSNumber *keyIdNumber = dict[@"id"];
|
||||
if (![keyIdNumber isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
int64_t keyId = [keyIdNumber longLongValue];
|
||||
|
||||
NSString *dataString = dict[@"data"];
|
||||
NSData *data = nil;
|
||||
if ([dataString isKindOfClass:[NSString class]]) {
|
||||
data = [[NSData alloc] initWithBase64EncodedString:dataString options:0];
|
||||
}
|
||||
if (data == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [[AccountDatacenterKey alloc] initWithKeyId:keyId data:data];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AccountDatacenterAddress
|
||||
|
||||
- (instancetype)initWithHost:(NSString *)host port:(int32_t)port isMedia:(bool)isMedia secret:(NSData * _Nullable)secret {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_host = host;
|
||||
_port = port;
|
||||
_isMedia = isMedia;
|
||||
_secret = secret;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
|
||||
NSString *hostString = dict[@"host"];
|
||||
if (![hostString isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
NSString *host = hostString;
|
||||
|
||||
NSNumber *portNumber = dict[@"port"];
|
||||
if (![portNumber isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
int32_t port = [portNumber intValue];
|
||||
|
||||
NSNumber *isMediaNumber = dict[@"isMedia"];
|
||||
if (![isMediaNumber isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
bool isMedia = [isMediaNumber intValue] != 0;
|
||||
|
||||
NSString *secretString = dict[@"secret"];
|
||||
NSData *secret = nil;
|
||||
if ([secretString isKindOfClass:[NSString class]]) {
|
||||
secret = [[NSData alloc] initWithBase64EncodedString:secretString options:0];
|
||||
}
|
||||
|
||||
return [[AccountDatacenterAddress alloc] initWithHost:host port:port isMedia:isMedia secret:secret];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AccountDatacenterInfo
|
||||
|
||||
- (instancetype)initWithMasterKey:(AccountDatacenterKey *)masterKey addressList:(NSArray<AccountDatacenterAddress *> *)addressList {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_masterKey = masterKey;
|
||||
_addressList = addressList;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
|
||||
NSDictionary *masterKeyDict = dict[@"masterKey"];
|
||||
if (![masterKeyDict isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
AccountDatacenterKey *masterKey = [AccountDatacenterKey parse:masterKeyDict];
|
||||
if (masterKey == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *addressListArray = dict[@"addressList"];
|
||||
if (![addressListArray isKindOfClass:[NSArray class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<AccountDatacenterAddress *> *addressList = [[NSMutableArray alloc] init];
|
||||
for (NSDictionary *addressListItem in addressListArray) {
|
||||
if (![addressListItem isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
AccountDatacenterAddress *address = [AccountDatacenterAddress parse:addressListItem];
|
||||
if (address == nil) {
|
||||
return nil;
|
||||
}
|
||||
[addressList addObject:address];
|
||||
}
|
||||
|
||||
return [[AccountDatacenterInfo alloc] initWithMasterKey:masterKey addressList:addressList];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AccountProxyConnection
|
||||
|
||||
- (instancetype)initWithHost:(NSString *)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password secret:(NSData * _Nullable)secret {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_host = host;
|
||||
_port = port;
|
||||
username = _username;
|
||||
password = _password;
|
||||
secret = _secret;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
|
||||
NSString *hostString = dict[@"host"];
|
||||
if (![hostString isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
NSString *host = hostString;
|
||||
|
||||
NSNumber *portNumber = dict[@"port"];
|
||||
if (![portNumber isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
int32_t port = [portNumber intValue];
|
||||
|
||||
NSString *usernameString = dict[@"username"];
|
||||
NSString *username = nil;
|
||||
if ([usernameString isKindOfClass:[NSString class]]) {
|
||||
username = usernameString;
|
||||
}
|
||||
|
||||
NSString *passwordString = dict[@"password"];
|
||||
NSString *password = nil;
|
||||
if ([passwordString isKindOfClass:[NSString class]]) {
|
||||
password = passwordString;
|
||||
}
|
||||
|
||||
NSString *secretString = dict[@"secret"];
|
||||
NSData *secret = nil;
|
||||
if ([secretString isKindOfClass:[NSString class]]) {
|
||||
secret = [[NSData alloc] initWithBase64EncodedString:secretString options:0];
|
||||
}
|
||||
|
||||
return [[AccountProxyConnection alloc] initWithHost:host port:port username:username password:password secret:secret];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation StoredAccountInfo
|
||||
|
||||
- (instancetype)initWithAccountId:(int64_t)accountId primaryId:(int32_t)primaryId isTestingEnvironment:(bool)isTestingEnvironment peerName:(NSString *)peerName datacenters:(NSDictionary<NSNumber *, AccountDatacenterInfo *> *)datacenters notificationKey:(AccountNotificationKey *)notificationKey {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_accountId = accountId;
|
||||
_primaryId = primaryId;
|
||||
_isTestingEnvironment = isTestingEnvironment;
|
||||
_peerName = peerName;
|
||||
_datacenters = datacenters;
|
||||
_notificationKey = notificationKey;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
|
||||
NSNumber *idNumber = dict[@"id"];
|
||||
if (![idNumber isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
int64_t accountId = [idNumber longLongValue];
|
||||
|
||||
NSNumber *primaryIdNumber = dict[@"primaryId"];
|
||||
if (![primaryIdNumber isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
int32_t primaryId = [primaryIdNumber intValue];
|
||||
|
||||
NSNumber *isTestingEnvironmentNumber = dict[@"isTestingEnvironment"];
|
||||
if (![isTestingEnvironmentNumber isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
bool isTestingEnvironment = [isTestingEnvironmentNumber intValue] != 0;
|
||||
|
||||
NSString *peerNameString = dict[@"peerName"];
|
||||
if (![peerNameString isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *peerName = peerNameString;
|
||||
|
||||
NSArray *datacentersArray = dict[@"datacenters"];
|
||||
if (![datacentersArray isKindOfClass:[NSArray class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSNumber *, AccountDatacenterInfo *> *datacenters = [[NSMutableDictionary alloc] init];
|
||||
|
||||
for (NSInteger i = 0; i < datacentersArray.count; i += 2) {
|
||||
NSNumber *datacenterKey = datacentersArray[i];
|
||||
NSDictionary *datacenterData = datacentersArray[i + 1];
|
||||
|
||||
if (![datacenterKey isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
if (![datacenterData isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
AccountDatacenterInfo *parsedDatacenter = [AccountDatacenterInfo parse:datacenterData];
|
||||
if (parsedDatacenter != nil) {
|
||||
datacenters[datacenterKey] = parsedDatacenter;
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *notificationKeyDict = dict[@"notificationKey"];
|
||||
if (![notificationKeyDict isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
AccountNotificationKey *notificationKey = [AccountNotificationKey parse:notificationKeyDict];
|
||||
if (notificationKey == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [[StoredAccountInfo alloc] initWithAccountId:accountId primaryId:primaryId isTestingEnvironment:isTestingEnvironment peerName:peerName datacenters:datacenters notificationKey:notificationKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation StoredAccountInfos
|
||||
|
||||
- (instancetype)initWithProxy:(AccountProxyConnection * _Nullable)proxy accounts:(NSArray<StoredAccountInfo *> *)accounts {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_proxy = proxy;
|
||||
_accounts = accounts;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (StoredAccountInfos * _Nullable)loadFromPath:(NSString *)path {
|
||||
NSData *data = [NSData dataWithContentsOfFile:path];
|
||||
if (data == nil) {
|
||||
return nil;
|
||||
}
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
if (![dict isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
AccountProxyConnection * _Nullable proxy = nil;
|
||||
NSDictionary *proxyDict = dict[@"proxy"];
|
||||
if ([proxyDict isKindOfClass:[NSDictionary class]]) {
|
||||
proxy = [AccountProxyConnection parse:proxyDict];
|
||||
}
|
||||
|
||||
NSMutableArray<StoredAccountInfo *> *accounts = [[NSMutableArray alloc] init];
|
||||
|
||||
NSArray *accountsObject = dict[@"accounts"];
|
||||
if ([accountsObject isKindOfClass:[NSArray class]]) {
|
||||
for (NSDictionary *object in accountsObject) {
|
||||
if ([object isKindOfClass:[NSDictionary class]]) {
|
||||
StoredAccountInfo *account = [StoredAccountInfo parse:object];
|
||||
if (account != nil) {
|
||||
[accounts addObject:account];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [[StoredAccountInfos alloc] initWithProxy:proxy accounts:accounts];;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static NSData *sha256Digest(NSData *data) {
|
||||
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(data.bytes, (CC_LONG)data.length, digest);
|
||||
|
||||
return [[NSData alloc] initWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
static NSData *concatData(NSData *data1, NSData *data2) {
|
||||
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:data1.length + data2.length];
|
||||
[data appendData:data1];
|
||||
[data appendData:data2];
|
||||
return data;
|
||||
}
|
||||
|
||||
static NSData *concatData3(NSData *data1, NSData *data2, NSData *data3) {
|
||||
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:data1.length + data2.length + data3.length];
|
||||
[data appendData:data1];
|
||||
[data appendData:data2];
|
||||
[data appendData:data3];
|
||||
return data;
|
||||
}
|
||||
|
||||
NSDictionary * _Nullable decryptedNotificationPayload(NSArray<StoredAccountInfo *> *accounts, NSData *data, int *selectedAccountIndex) {
|
||||
if (data.length < 8 + 16) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
int accountIndex = -1;
|
||||
for (StoredAccountInfo *account in accounts) {
|
||||
accountIndex += 1;
|
||||
|
||||
AccountNotificationKey *notificationKey = account.notificationKey;
|
||||
if (![[data subdataWithRange:NSMakeRange(0, 8)] isEqualToData:notificationKey.keyId]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = 8;
|
||||
NSData *msgKey = [data subdataWithRange:NSMakeRange(8, 16)];
|
||||
NSData *rawData = [data subdataWithRange:NSMakeRange(8 + 16, data.length - (8 + 16))];
|
||||
|
||||
NSData *sha256_a = sha256Digest(concatData(msgKey, [notificationKey.data subdataWithRange:NSMakeRange(x, 36)]));
|
||||
NSData *sha256_b = sha256Digest(concatData([notificationKey.data subdataWithRange:NSMakeRange(40 + x, 36)], msgKey));
|
||||
NSData *aesKey = concatData3([sha256_a subdataWithRange:NSMakeRange(0, 8)], [sha256_b subdataWithRange:NSMakeRange(8, 16)], [sha256_a subdataWithRange:NSMakeRange(24, 8)]);
|
||||
NSData *aesIv = concatData3([sha256_b subdataWithRange:NSMakeRange(0, 8)], [sha256_a subdataWithRange:NSMakeRange(8, 16)], [sha256_b subdataWithRange:NSMakeRange(24, 8)]);
|
||||
|
||||
NSData *decryptedData = MTAesDecrypt(rawData, aesKey, aesIv);
|
||||
if (decryptedData.length <= 4) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
int32_t dataLength = 0;
|
||||
[decryptedData getBytes:&dataLength range:NSMakeRange(0, 4)];
|
||||
|
||||
if (dataLength < 0 || dataLength > decryptedData.length - 4) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *checkMsgKeyLarge = sha256Digest(concatData([notificationKey.data subdataWithRange:NSMakeRange(88 + x, 32)], decryptedData));
|
||||
NSData *checkMsgKey = [checkMsgKeyLarge subdataWithRange:NSMakeRange(8, 16)];
|
||||
|
||||
if (![checkMsgKey isEqualToData:msgKey]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *contentData = [decryptedData subdataWithRange:NSMakeRange(4, dataLength)];
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:contentData options:0 error:nil];
|
||||
if (![dict isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
if (selectedAccountIndex != nil) {
|
||||
*selectedAccountIndex = accountIndex;
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@ -226,13 +226,10 @@
|
||||
D008185622B579A1008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008185522B579A1008A895F /* BuildConfig.framework */; };
|
||||
D008185822B579AD008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008185722B579AD008A895F /* BuildConfig.framework */; };
|
||||
D00818A522B58CCB008A895F /* WatchCommonWatch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D00818A422B58CCB008A895F /* WatchCommonWatch.framework */; };
|
||||
D00818CF22B595DB008A895F /* LightweightAccountData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D00818CE22B595DB008A895F /* LightweightAccountData.framework */; };
|
||||
D00859A91B28189D00EAF753 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D00859A81B28189D00EAF753 /* Images.xcassets */; };
|
||||
D00859AC1B28189D00EAF753 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D00859AA1B28189D00EAF753 /* LaunchScreen.xib */; };
|
||||
D00ED75A1FE94630001F38BD /* AppIntentVocabulary.plist in Resources */ = {isa = PBXBuildFile; fileRef = D00ED7581FE94630001F38BD /* AppIntentVocabulary.plist */; };
|
||||
D00ED75D1FE95287001F38BD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D00ED75B1FE95287001F38BD /* InfoPlist.strings */; };
|
||||
D015E011225CCEB300CB9E8A /* ReadBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015E010225CCEB300CB9E8A /* ReadBuffer.swift */; };
|
||||
D015E01F225CDF5100CB9E8A /* Api0.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015E01E225CDF5000CB9E8A /* Api0.swift */; };
|
||||
D015E04D225D2D8F00CB9E8A /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
D015E050225D303F00CB9E8A /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; };
|
||||
D015E051225D303F00CB9E8A /* WebP.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@ -302,7 +299,6 @@
|
||||
D06706611D51185400DED3E3 /* TelegramCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D06706601D51185400DED3E3 /* TelegramCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D06706621D5118F500DED3E3 /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D06706601D51185400DED3E3 /* TelegramCore.framework */; };
|
||||
D073E52021FF7CE900742DDD /* Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = D073E51F21FF7CE900742DDD /* Crypto.m */; };
|
||||
D073E52222003E1E00742DDD /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073E52122003E1E00742DDD /* Data.swift */; };
|
||||
D08410501FABDD54008FFE92 /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08410511FABDD54008FFE92 /* MtProtoKitDynamic.framework */; };
|
||||
D08611B21F5711080047111E /* HockeySDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D01A47541F4DBED700383CC1 /* HockeySDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D08984FE2118B3F100918162 /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08984FD2118B3F100918162 /* MtProtoKitDynamic.framework */; };
|
||||
@ -354,6 +350,12 @@
|
||||
D0B4AF8F1EC122A700D51FF6 /* TelegramUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0400ED81D5B8F97007931CE /* TelegramUI.framework */; };
|
||||
D0B4AF901EC122A700D51FF6 /* TelegramUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0400ED81D5B8F97007931CE /* TelegramUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D0B844601DACF561005F29E1 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B8445F1DACF561005F29E1 /* libc++.tbd */; };
|
||||
D0BAAA1823100B7A00AFC473 /* Api.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA1623100B7A00AFC473 /* Api.m */; };
|
||||
D0BAAA1B23100C2700AFC473 /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA1A23100C2700AFC473 /* NotificationService.m */; };
|
||||
D0BAAA1E2310117200AFC473 /* StoredAccountInfos.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA1D2310117200AFC473 /* StoredAccountInfos.m */; };
|
||||
D0BAAA21231026BC00AFC473 /* Attachments.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA20231026BC00AFC473 /* Attachments.m */; };
|
||||
D0BAAA242310302300AFC473 /* FetchImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA232310302300AFC473 /* FetchImage.m */; };
|
||||
D0BAAA272310326200AFC473 /* Serialization.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA262310326200AFC473 /* Serialization.m */; };
|
||||
D0C2DFF81CC4D1BA0044FF83 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C2DFF71CC4D1BA0044FF83 /* MobileCoreServices.framework */; };
|
||||
D0CAD6A421C03BEB001E3055 /* FFMpeg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0CAD6A321C03BEB001E3055 /* FFMpeg.framework */; };
|
||||
D0CAD6A521C03BEB001E3055 /* FFMpeg.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0CAD6A321C03BEB001E3055 /* FFMpeg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@ -389,7 +391,6 @@
|
||||
D0D17E8A1CAAD66600C4750B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D17E891CAAD66600C4750B /* Accelerate.framework */; };
|
||||
D0D268791D79A70A00C422DA /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D268781D79A70A00C422DA /* IntentHandler.swift */; };
|
||||
D0D2688E1D79A70B00C422DA /* SiriIntents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0D268761D79A70A00C422DA /* SiriIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D0E2CE642227F0680084E3DD /* ManagedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E2CE632227F0680084E3DD /* ManagedFile.swift */; };
|
||||
D0E8B8AD2044496C00605593 /* voip_connecting.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = D0E8B8A82044496B00605593 /* voip_connecting.mp3 */; };
|
||||
D0E8B8AE2044496C00605593 /* voip_end.caf in Resources */ = {isa = PBXBuildFile; fileRef = D0E8B8A92044496C00605593 /* voip_end.caf */; };
|
||||
D0E8B8AF2044496C00605593 /* voip_fail.caf in Resources */ = {isa = PBXBuildFile; fileRef = D0E8B8AA2044496C00605593 /* voip_fail.caf */; };
|
||||
@ -399,10 +400,6 @@
|
||||
D0E8C2E02285EA6A009F26E8 /* BlackIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0E8C2DF2285EA6A009F26E8 /* BlackIcon@3x.png */; };
|
||||
D0ECCB7F1FE9C38500609802 /* Telegram_iOS_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ECCB7E1FE9C38500609802 /* Telegram_iOS_UITests.swift */; };
|
||||
D0ECCB8A1FE9C4AC00609802 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ECCB891FE9C4AC00609802 /* SnapshotHelper.swift */; };
|
||||
D0ED633A21FF3EDF001D4648 /* AccountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED633921FF3EDF001D4648 /* AccountData.swift */; };
|
||||
D0ED633D21FF4580001D4648 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0400EE41D5B912E007931CE /* NotificationService.swift */; };
|
||||
D0ED633F21FF46E4001D4648 /* ImageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED633E21FF46E4001D4648 /* ImageData.swift */; };
|
||||
D0ED634121FF4786001D4648 /* Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED634021FF4786001D4648 /* Serialization.swift */; };
|
||||
D0F575132083B96B00F1C1E1 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F575122083B96B00F1C1E1 /* CloudKit.framework */; };
|
||||
D0FC1948201D2DA800FEDBB2 /* SFCompactRounded-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = D0FC1947201D2DA700FEDBB2 /* SFCompactRounded-Semibold.otf */; };
|
||||
/* End PBXBuildFile section */
|
||||
@ -1060,6 +1057,18 @@
|
||||
D0B844591DACF507005F29E1 /* HockeySDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HockeySDK.framework; path = "third-party/HockeySDK.framework"; sourceTree = "<group>"; };
|
||||
D0B8445A1DACF507005F29E1 /* HockeySDKResources.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = HockeySDKResources.bundle; path = "third-party/HockeySDKResources.bundle"; sourceTree = "<group>"; };
|
||||
D0B8445F1DACF561005F29E1 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
|
||||
D0BAAA1623100B7A00AFC473 /* Api.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Api.m; sourceTree = "<group>"; };
|
||||
D0BAAA1723100B7A00AFC473 /* Api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Api.h; sourceTree = "<group>"; };
|
||||
D0BAAA1923100C2700AFC473 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = "<group>"; };
|
||||
D0BAAA1A23100C2700AFC473 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = "<group>"; };
|
||||
D0BAAA1C2310117200AFC473 /* StoredAccountInfos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StoredAccountInfos.h; sourceTree = "<group>"; };
|
||||
D0BAAA1D2310117200AFC473 /* StoredAccountInfos.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StoredAccountInfos.m; sourceTree = "<group>"; };
|
||||
D0BAAA1F231026BC00AFC473 /* Attachments.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Attachments.h; sourceTree = "<group>"; };
|
||||
D0BAAA20231026BC00AFC473 /* Attachments.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Attachments.m; sourceTree = "<group>"; };
|
||||
D0BAAA222310302300AFC473 /* FetchImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FetchImage.h; sourceTree = "<group>"; };
|
||||
D0BAAA232310302300AFC473 /* FetchImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FetchImage.m; sourceTree = "<group>"; };
|
||||
D0BAAA252310326200AFC473 /* Serialization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Serialization.h; sourceTree = "<group>"; };
|
||||
D0BAAA262310326200AFC473 /* Serialization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Serialization.m; sourceTree = "<group>"; };
|
||||
D0C2DFF51CC4D1B20044FF83 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; };
|
||||
D0C2DFF71CC4D1BA0044FF83 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
|
||||
D0C2DFF91CC4D1C90044FF83 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; };
|
||||
@ -1148,7 +1157,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D00818CF22B595DB008A895F /* LightweightAccountData.framework in Frameworks */,
|
||||
D008185822B579AD008A895F /* BuildConfig.framework in Frameworks */,
|
||||
D015E04D225D2D8F00CB9E8A /* WebP.framework in Frameworks */,
|
||||
D0CCD61D222EFFB000EE1E08 /* MtProtoKitDynamic.framework in Frameworks */,
|
||||
@ -2157,6 +2165,18 @@
|
||||
D0400EE31D5B912E007931CE /* NotificationService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0BAAA1723100B7A00AFC473 /* Api.h */,
|
||||
D0BAAA1623100B7A00AFC473 /* Api.m */,
|
||||
D0BAAA1923100C2700AFC473 /* NotificationService.h */,
|
||||
D0BAAA1A23100C2700AFC473 /* NotificationService.m */,
|
||||
D0BAAA1C2310117200AFC473 /* StoredAccountInfos.h */,
|
||||
D0BAAA1D2310117200AFC473 /* StoredAccountInfos.m */,
|
||||
D0BAAA1F231026BC00AFC473 /* Attachments.h */,
|
||||
D0BAAA20231026BC00AFC473 /* Attachments.m */,
|
||||
D0BAAA222310302300AFC473 /* FetchImage.h */,
|
||||
D0BAAA232310302300AFC473 /* FetchImage.m */,
|
||||
D0BAAA252310326200AFC473 /* Serialization.h */,
|
||||
D0BAAA262310326200AFC473 /* Serialization.m */,
|
||||
D015E01E225CDF5000CB9E8A /* Api0.swift */,
|
||||
D000CAC221FB6E170011B15D /* NotificationService-AppStore.entitlements */,
|
||||
D000CAC321FB6E170011B15D /* NotificationService-AppStoreLLC.entitlements */,
|
||||
@ -3175,15 +3195,13 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D073E52222003E1E00742DDD /* Data.swift in Sources */,
|
||||
D0E2CE642227F0680084E3DD /* ManagedFile.swift in Sources */,
|
||||
D0ED633D21FF4580001D4648 /* NotificationService.swift in Sources */,
|
||||
D015E01F225CDF5100CB9E8A /* Api0.swift in Sources */,
|
||||
D0ED633A21FF3EDF001D4648 /* AccountData.swift in Sources */,
|
||||
D0ED634121FF4786001D4648 /* Serialization.swift in Sources */,
|
||||
D0BAAA242310302300AFC473 /* FetchImage.m in Sources */,
|
||||
D0BAAA1E2310117200AFC473 /* StoredAccountInfos.m in Sources */,
|
||||
D0BAAA1823100B7A00AFC473 /* Api.m in Sources */,
|
||||
D073E52021FF7CE900742DDD /* Crypto.m in Sources */,
|
||||
D0ED633F21FF46E4001D4648 /* ImageData.swift in Sources */,
|
||||
D015E011225CCEB300CB9E8A /* ReadBuffer.swift in Sources */,
|
||||
D0BAAA1B23100C2700AFC473 /* NotificationService.m in Sources */,
|
||||
D0BAAA21231026BC00AFC473 /* Attachments.m in Sources */,
|
||||
D0BAAA272310326200AFC473 /* Serialization.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "DebugAppStoreLLC"
|
||||
buildConfiguration = "DebugHockeyapp"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
|
||||
@ -408,32 +408,50 @@ final class MessageHistoryTable: Table {
|
||||
return globallyUniqueIdToMessageId
|
||||
}
|
||||
|
||||
func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
|
||||
func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
|
||||
for (peerId, messageIds) in self.messageIdsByPeerId(messageIds) {
|
||||
var operations: [MessageHistoryIndexOperation] = []
|
||||
|
||||
for id in messageIds {
|
||||
self.messageHistoryIndexTable.removeMessage(id, operations: &operations)
|
||||
}
|
||||
for operation in operations {
|
||||
if case let .Remove(index) = operation {
|
||||
if let message = self.getMessage(index) {
|
||||
for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) {
|
||||
forEachMedia(media)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
|
||||
}
|
||||
}
|
||||
|
||||
func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
|
||||
func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
|
||||
var operations: [MessageHistoryIndexOperation] = []
|
||||
self.messageHistoryIndexTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operations: &operations)
|
||||
for operation in operations {
|
||||
if case let .Remove(index) = operation {
|
||||
if let message = self.getMessage(index) {
|
||||
for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) {
|
||||
forEachMedia(media)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
|
||||
}
|
||||
|
||||
func clearHistory(peerId: PeerId, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
|
||||
func clearHistory(peerId: PeerId, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
|
||||
let indices = self.allMessageIndices(peerId: peerId).filter { namespaces.contains($0.id.namespace) }
|
||||
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
|
||||
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
|
||||
func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
|
||||
let indices = self.allIndicesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace)
|
||||
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
|
||||
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
func updateMessage(_ id: MessageId, message: StoreMessage, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
|
||||
|
||||
@ -72,28 +72,28 @@ public final class Transaction {
|
||||
self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole)
|
||||
}
|
||||
|
||||
public func deleteMessages(_ messageIds: [MessageId]) {
|
||||
public func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) {
|
||||
assert(!self.disposed)
|
||||
self.postbox?.deleteMessages(messageIds)
|
||||
self.postbox?.deleteMessages(messageIds, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id) {
|
||||
public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) {
|
||||
assert(!self.disposed)
|
||||
self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId)
|
||||
self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
public func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace? = nil, _ f: (Message) -> Bool) {
|
||||
self.postbox?.withAllMessages(peerId: peerId, namespace: namespace, f)
|
||||
}
|
||||
|
||||
public func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces) {
|
||||
public func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces, forEachMedia: (Media) -> Void) {
|
||||
assert(!self.disposed)
|
||||
self.postbox?.clearHistory(peerId, namespaces: namespaces)
|
||||
self.postbox?.clearHistory(peerId, namespaces: namespaces, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) {
|
||||
public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
|
||||
assert(!self.disposed)
|
||||
self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace)
|
||||
self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
public func messageIdsForGlobalIds(_ ids: [Int32]) -> [MessageId] {
|
||||
@ -105,11 +105,11 @@ public final class Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteMessagesWithGlobalIds(_ ids: [Int32]) {
|
||||
public func deleteMessagesWithGlobalIds(_ ids: [Int32], forEachMedia: (Media) -> Void) {
|
||||
assert(!self.disposed)
|
||||
if let postbox = self.postbox {
|
||||
let messageIds = postbox.messageIdsForGlobalIds(ids)
|
||||
postbox.deleteMessages(messageIds)
|
||||
postbox.deleteMessages(messageIds, forEachMedia: forEachMedia)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1499,12 +1499,12 @@ public final class Postbox {
|
||||
self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations)
|
||||
}
|
||||
|
||||
fileprivate func deleteMessages(_ messageIds: [MessageId]) {
|
||||
self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
|
||||
fileprivate func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) {
|
||||
self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id) {
|
||||
self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
|
||||
fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) {
|
||||
self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
fileprivate func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace?, _ f: (Message) -> Bool) {
|
||||
@ -1519,15 +1519,15 @@ public final class Postbox {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces) {
|
||||
self.messageHistoryTable.clearHistory(peerId: peerId, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
|
||||
fileprivate func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces, forEachMedia: (Media) -> Void) {
|
||||
self.messageHistoryTable.clearHistory(peerId: peerId, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
|
||||
for namespace in self.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: .everywhere) where namespaces.contains(namespace) {
|
||||
self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... Int32.max - 1, operations: &self.currentPeerHoleOperations)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) {
|
||||
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
|
||||
fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
|
||||
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
|
||||
}
|
||||
|
||||
fileprivate func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) {
|
||||
|
||||
@ -86,7 +86,9 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
private let smallCircleNode: ASImageNode
|
||||
private let smallCircleShadowNode: ASImageNode
|
||||
|
||||
private let contentContainer: ASDisplayNode
|
||||
private var itemNodes: [ReactionNode] = []
|
||||
private let disclosureButton: HighlightTrackingButtonNode
|
||||
|
||||
private var isExpanded: Bool = false
|
||||
private var highlightedReaction: String?
|
||||
@ -139,6 +141,28 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
self.largeCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: largeCircleSize, shadowBlur: shadowBlur)
|
||||
self.smallCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: smallCircleSize, shadowBlur: shadowBlur)
|
||||
|
||||
self.contentContainer = ASDisplayNode()
|
||||
self.contentContainer.clipsToBounds = true
|
||||
|
||||
self.disclosureButton = HighlightTrackingButtonNode()
|
||||
self.disclosureButton.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -6.0)
|
||||
let buttonImage = generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.contextMenu.dimColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
context.setStrokeColor(UIColor.clear.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.beginPath()
|
||||
context.move(to: CGPoint(x: 8.0, y: size.height / 2.0 + 3.0))
|
||||
context.addLine(to: CGPoint(x: size.width / 2.0, y: 11.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 8.0, y: size.height / 2.0 + 3.0))
|
||||
context.strokePath()
|
||||
})
|
||||
self.disclosureButton.setImage(buttonImage, for: [])
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.smallCircleShadowNode)
|
||||
@ -150,10 +174,23 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
self.backgroundContainerNode.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.backgroundContainerNode)
|
||||
|
||||
self.contentContainer.addSubnode(self.disclosureButton)
|
||||
|
||||
self.itemNodes = self.items.map { item in
|
||||
return ReactionNode(account: account, theme: theme, reaction: .reaction(value: item.value, text: item.text, path: item.path), maximizedReactionSize: 30.0 - 18.0, loadFirstFrame: true)
|
||||
}
|
||||
self.itemNodes.forEach(self.addSubnode)
|
||||
self.itemNodes.forEach(self.contentContainer.addSubnode)
|
||||
|
||||
self.addSubnode(self.contentContainer)
|
||||
|
||||
self.disclosureButton.addTarget(self, action: #selector(self.disclosurePressed), forControlEvents: .touchUpInside)
|
||||
self.disclosureButton.highligthedChanged = { [weak self] highlighted in
|
||||
if highlighted {
|
||||
self?.disclosureButton.layer.animateScale(from: 1.0, to: 0.8, duration: 0.15, removeOnCompletion: false)
|
||||
} else {
|
||||
self?.disclosureButton.layer.animateScale(from: 0.8, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
@ -193,18 +230,27 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
let minimizedItemSize: CGFloat = 30.0
|
||||
let maximizedItemSize: CGFloat = 30.0 - 18.0
|
||||
let shadowBlur: CGFloat = 5.0
|
||||
let rowHeight: CGFloat = 52.0
|
||||
let verticalInset: CGFloat = 11.0
|
||||
let rowHeight: CGFloat = 30.0
|
||||
let rowSpacing: CGFloat = itemSpacing
|
||||
|
||||
let columnCount = min(7, self.items.count)
|
||||
let columnCount = min(6, self.items.count)
|
||||
let contentWidth = CGFloat(columnCount) * minimizedItemSize + (CGFloat(columnCount) - 1.0) * itemSpacing + sideInset * 2.0
|
||||
let rowCount = self.items.count / columnCount + (self.items.count % columnCount == 0 ? 0 : 1)
|
||||
let contentHeight = rowHeight * CGFloat(rowCount)
|
||||
|
||||
let expandedRowCount = self.isExpanded ? rowCount : 1
|
||||
|
||||
let contentHeight = verticalInset * 2.0 + rowHeight * CGFloat(expandedRowCount) + CGFloat(expandedRowCount - 1) * rowSpacing
|
||||
|
||||
let (backgroundFrame, isLeftAligned) = self.calculateBackgroundFrame(containerSize: size, insets: insets, anchorRect: anchorRect, contentSize: CGSize(width: contentWidth, height: contentHeight))
|
||||
|
||||
transition.updateFrame(node: self.contentContainer, frame: backgroundFrame)
|
||||
|
||||
for i in 0 ..< self.items.count {
|
||||
let row = CGFloat(i / columnCount)
|
||||
let column = CGFloat(i % columnCount)
|
||||
let rowIndex = i / columnCount
|
||||
let columnIndex = i % columnCount
|
||||
let row = CGFloat(rowIndex)
|
||||
let column = CGFloat(columnIndex)
|
||||
|
||||
var reactionValue: String?
|
||||
switch self.itemNodes[i].reaction {
|
||||
@ -224,18 +270,43 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
itemSize = updatedSize
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.itemNodes[i], frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset + column * (minimizedItemSize + itemSpacing) - itemOffset, y: backgroundFrame.minY + row * rowHeight + floor((rowHeight - minimizedItemSize) / 2.0) - itemOffset), size: CGSize(width: itemSize, height: itemSize)), beginWithCurrentState: true)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: sideInset + column * (minimizedItemSize + itemSpacing) - itemOffset, y: verticalInset + row * (rowHeight + rowSpacing) + floor((rowHeight - minimizedItemSize) / 2.0) - itemOffset), size: CGSize(width: itemSize, height: itemSize))
|
||||
transition.updateFrame(node: self.itemNodes[i], frame: itemFrame, beginWithCurrentState: true)
|
||||
self.itemNodes[i].updateLayout(size: CGSize(width: itemSize, height: itemSize), scale: itemSize / (maximizedItemSize + 18.0), transition: transition, displayText: false)
|
||||
self.itemNodes[i].updateIsAnimating(false, animated: false)
|
||||
if row != 0 {
|
||||
if rowIndex != 0 || columnIndex == columnCount - 1 {
|
||||
if self.isExpanded {
|
||||
self.itemNodes[i].alpha = 1.0
|
||||
if self.itemNodes[i].alpha.isZero {
|
||||
self.itemNodes[i].alpha = 1.0
|
||||
if transition.isAnimated {
|
||||
let delayOffset: Double = 1.0 - Double(columnIndex) / Double(columnCount - 1)
|
||||
self.itemNodes[i].layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4 + delayOffset * 0.32, initialVelocity: 0.0, damping: 95.0)
|
||||
self.itemNodes[i].layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.05)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.itemNodes[i].alpha = 0.0
|
||||
}
|
||||
} else {
|
||||
self.itemNodes[i].alpha = 1.0
|
||||
}
|
||||
|
||||
if rowIndex == 0 && columnIndex == columnCount - 1 {
|
||||
transition.updateFrame(node: self.disclosureButton, frame: itemFrame)
|
||||
if self.isExpanded {
|
||||
if self.disclosureButton.alpha.isEqual(to: 1.0) {
|
||||
self.disclosureButton.alpha = 0.0
|
||||
if transition.isAnimated {
|
||||
self.disclosureButton.layer.animateScale(from: 0.8, to: 0.1, duration: 0.2, removeOnCompletion: false)
|
||||
self.disclosureButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.disclosureButton.layer.removeAnimation(forKey: "scale")
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.disclosureButton.alpha = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isInOverflow = backgroundFrame.maxY > anchorRect.minY
|
||||
@ -254,10 +325,10 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
let largeCircleFrame: CGRect
|
||||
let smallCircleFrame: CGRect
|
||||
if isLeftAligned {
|
||||
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.maxX + 22.0 - rowHeight + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
|
||||
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.maxX + 16.0 - rowHeight + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
|
||||
smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.maxX - 3.0, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize))
|
||||
} else {
|
||||
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.minX - 24.0 + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
|
||||
largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.minX - 18.0 + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize))
|
||||
smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.minX + 3.0 - smallCircleSize, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize))
|
||||
}
|
||||
|
||||
@ -289,15 +360,16 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func animateOut(to targetAnchorRect: CGRect?, animatingOutToReaction: Bool) {
|
||||
self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
for itemNode in self.itemNodes {
|
||||
self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
self.disclosureButton.layer.animateAlpha(from: self.disclosureButton.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
|
||||
if let targetAnchorRect = targetAnchorRect, let (size, insets, anchorRect) = self.validLayout {
|
||||
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: nil, animateOutToAnchorRect: targetAnchorRect)
|
||||
@ -383,8 +455,14 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let contentPoint = self.contentContainer.view.convert(point, from: self.view)
|
||||
if !self.disclosureButton.alpha.isZero {
|
||||
if let result = self.disclosureButton.hitTest(self.disclosureButton.view.convert(point, from: self.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.frame.contains(point) {
|
||||
if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) {
|
||||
return self.view
|
||||
}
|
||||
}
|
||||
@ -401,13 +479,14 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func reaction(at point: CGPoint) -> ReactionGestureItem? {
|
||||
let contentPoint = self.contentContainer.view.convert(point, from: self.view)
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.frame.contains(point) {
|
||||
if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) {
|
||||
return itemNode.reaction
|
||||
}
|
||||
}
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.frame.insetBy(dx: -8.0, dy: -8.0).contains(point) {
|
||||
if !itemNode.alpha.isZero && itemNode.frame.insetBy(dx: -8.0, dy: -8.0).contains(contentPoint) {
|
||||
return itemNode.reaction
|
||||
}
|
||||
}
|
||||
@ -420,4 +499,11 @@ public final class ReactionContextNode: ASDisplayNode {
|
||||
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func disclosurePressed() {
|
||||
self.isExpanded = true
|
||||
if let (size, insets, anchorRect) = self.validLayout {
|
||||
self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.3, curve: .spring), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ final class ReactionNode: ASDisplayNode {
|
||||
switch reaction {
|
||||
case let .reaction(value, _, path):
|
||||
switch value {
|
||||
case "😒":
|
||||
case "😔":
|
||||
intrinsicSize.width *= 1.7
|
||||
intrinsicSize.height *= 1.7
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
@ -96,6 +96,34 @@ final class ReactionNode: ASDisplayNode {
|
||||
intrinsicSize.width *= 1.256
|
||||
intrinsicSize.height *= 1.256
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.05 * intrinsicSize.width)
|
||||
case "🥳":
|
||||
intrinsicSize.width *= 1.39
|
||||
intrinsicSize.height *= 1.39
|
||||
self.intrinsicOffset = CGPoint(x: 0.1 * intrinsicSize.width, y: -0.05 * intrinsicSize.width)
|
||||
case "😭":
|
||||
intrinsicSize.width *= 1.15
|
||||
intrinsicSize.height *= 1.15
|
||||
self.intrinsicOffset = CGPoint(x: 0.1 * intrinsicSize.width, y: 0.03 * intrinsicSize.width)
|
||||
case "😒":
|
||||
intrinsicSize.width *= 1.05
|
||||
intrinsicSize.height *= 1.05
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
case "👌":
|
||||
intrinsicSize.width *= 1.08
|
||||
intrinsicSize.height *= 1.08
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
case "😐":
|
||||
intrinsicSize.width *= 1.75
|
||||
intrinsicSize.height *= 1.75
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.01 * intrinsicSize.width)
|
||||
case "💩":
|
||||
intrinsicSize.width *= 1.1
|
||||
intrinsicSize.height *= 1.1
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
case "😊":
|
||||
intrinsicSize.width *= 1.2
|
||||
intrinsicSize.height *= 1.2
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: -0.01 * intrinsicSize.width)
|
||||
default:
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
}
|
||||
@ -116,6 +144,8 @@ final class ReactionNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
//self.backgroundColor = .gray
|
||||
|
||||
self.textBackgroundNode.addSubnode(self.textNode)
|
||||
self.addSubnode(self.textBackgroundNode)
|
||||
|
||||
|
||||
@ -477,7 +477,7 @@ public final class ShareController: ViewController {
|
||||
if let error = error {
|
||||
Queue.mainQueue().async {
|
||||
let _ = (account.postbox.transaction { transaction -> Peer? in
|
||||
transaction.deleteMessages([id])
|
||||
deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: [id])
|
||||
return transaction.getPeer(id.peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
|
||||
@ -2283,14 +2283,18 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
}
|
||||
}
|
||||
case let .DeleteMessagesWithGlobalIds(ids):
|
||||
transaction.deleteMessagesWithGlobalIds(ids)
|
||||
transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in
|
||||
processRemovedMedia(mediaBox, media)
|
||||
})
|
||||
case let .DeleteMessages(ids):
|
||||
deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids)
|
||||
case let .UpdateMinAvailableMessage(id):
|
||||
if let message = transaction.getMessage(id) {
|
||||
updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: id.peerId, minTimestamp: message.timestamp, forceRootGroupIfNotExists: false)
|
||||
}
|
||||
transaction.deleteMessagesInRange(peerId: id.peerId, namespace: id.namespace, minId: 1, maxId: id.id)
|
||||
transaction.deleteMessagesInRange(peerId: id.peerId, namespace: id.namespace, minId: 1, maxId: id.id, forEachMedia: { media in
|
||||
processRemovedMedia(mediaBox, media)
|
||||
})
|
||||
case let .UpdatePeerChatInclusion(peerId, groupId, changedGroup):
|
||||
let currentInclusion = transaction.getPeerChatListInclusion(peerId)
|
||||
var currentPinningIndex: UInt16?
|
||||
|
||||
@ -26,7 +26,15 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.deleteMessages(ids)
|
||||
transaction.deleteMessages(ids, forEachMedia: { media in
|
||||
processRemovedMedia(mediaBox, media)
|
||||
})
|
||||
}
|
||||
|
||||
public func deleteAllMessagesWithAuthor(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) {
|
||||
transaction.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: { media in
|
||||
processRemovedMedia(mediaBox, media)
|
||||
})
|
||||
}
|
||||
|
||||
public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, namespaces: MessageIdNamespaces) {
|
||||
@ -36,5 +44,7 @@ public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: P
|
||||
return true
|
||||
})
|
||||
}
|
||||
transaction.clearHistory(peerId, namespaces: namespaces)
|
||||
transaction.clearHistory(peerId, namespaces: namespaces, forEachMedia: { media in
|
||||
processRemovedMedia(mediaBox, media)
|
||||
})
|
||||
}
|
||||
|
||||
@ -167,7 +167,9 @@ public func clearAuthorHistory(account: Account, peerId: PeerId, memberId: PeerI
|
||||
|> `catch` { success -> Signal<Void, NoError> in
|
||||
if success {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.removeAllMessagesWithAuthor(peerId, authorId: memberId, namespace: Namespaces.Message.Cloud)
|
||||
transaction.removeAllMessagesWithAuthor(peerId, authorId: memberId, namespace: Namespaces.Message.Cloud, forEachMedia: { media in
|
||||
processRemovedMedia(account.postbox.mediaBox, media)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import TelegramApiMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import UIKit
|
||||
import TelegramApi
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
|
||||
public func smallestImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? {
|
||||
@ -74,7 +76,18 @@ public func imageRepresentationLargerThan(_ representations: [TelegramMediaImage
|
||||
}
|
||||
|
||||
public func parseMediaData(data: Data) -> Media? {
|
||||
if let object = Api.parse(Buffer(data: data)) {
|
||||
let buffer = BufferReader(Buffer(data: data))
|
||||
var parseBuffer: Buffer?
|
||||
guard let signature = buffer.readInt32() else {
|
||||
return nil
|
||||
}
|
||||
if signature == 0x3072cfa1 {
|
||||
parseBuffer = parseBytes(buffer).flatMap({ $0.makeData() }).flatMap(MTGzip.decompress).flatMap(Buffer.init(data:))
|
||||
} else {
|
||||
parseBuffer = Buffer(data: data)
|
||||
}
|
||||
|
||||
if let parseBuffer = parseBuffer, let object = Api.parse(parseBuffer) {
|
||||
if let photo = object as? Api.Photo {
|
||||
return telegramMediaImageFromApiPhoto(photo)
|
||||
} else if let document = object as? Api.Document {
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
func processRemovedMedia(_ mediaBox: MediaBox, _ media: Media) {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
let _ = mediaBox.removeCachedResources(Set(image.representations.map({ WrappedMediaResourceId($0.resource.id) }))).start()
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let _ = mediaBox.removeCachedResources(Set(file.previewRepresentations.map({ WrappedMediaResourceId($0.resource.id) }))).start()
|
||||
let _ = mediaBox.removeCachedResources(Set([WrappedMediaResourceId(file.resource.id)])).start()
|
||||
}
|
||||
}
|
||||
@ -127,7 +127,9 @@ func managedApplyPendingScheduledMessagesActions(postbox: Postbox, network: Netw
|
||||
})
|
||||
|> then(
|
||||
postbox.transaction { transaction -> Void in
|
||||
transaction.deleteMessages([entry.id])
|
||||
transaction.deleteMessages([entry.id], forEachMedia: { media in
|
||||
processRemovedMedia(postbox.mediaBox, media)
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
)
|
||||
|
||||
@ -425,7 +425,9 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
|
||||
})
|
||||
|
||||
if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated {
|
||||
transaction.deleteMessagesInRange(peerId: peerId, namespace: minAvailableMessageId.namespace, minId: 1, maxId: minAvailableMessageId.id)
|
||||
transaction.deleteMessagesInRange(peerId: peerId, namespace: minAvailableMessageId.namespace, minId: 1, maxId: minAvailableMessageId.id, forEachMedia: { media in
|
||||
processRemovedMedia(postbox.mediaBox, media)
|
||||
})
|
||||
}
|
||||
case .chatFull:
|
||||
break
|
||||
|
||||
@ -604,6 +604,8 @@
|
||||
D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */; };
|
||||
D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */; };
|
||||
D0B85AC61F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */; };
|
||||
D0BAAA14230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */; };
|
||||
D0BAAA15230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */; };
|
||||
D0BB7C5A1E5C8074001527C3 /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; };
|
||||
D0BC386E1E3FDAB70044D6FE /* CreateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */; };
|
||||
D0BC38701E40853E0044D6FE /* UpdatePeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */; };
|
||||
@ -1122,6 +1124,7 @@
|
||||
D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePeerNotificationSettings.swift; sourceTree = "<group>"; };
|
||||
D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUserPresence.swift; sourceTree = "<group>"; };
|
||||
D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyUsedHashtags.swift; sourceTree = "<group>"; };
|
||||
D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessRemovedMedia.swift; sourceTree = "<group>"; };
|
||||
D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelParticipants.swift; sourceTree = "<group>"; };
|
||||
D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroup.swift; sourceTree = "<group>"; };
|
||||
D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeers.swift; sourceTree = "<group>"; };
|
||||
@ -1518,6 +1521,7 @@
|
||||
D03B0D121D62257600955575 /* Resources */,
|
||||
D0DFD5DE1FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift */,
|
||||
D0879BC722F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift */,
|
||||
D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */,
|
||||
);
|
||||
name = Media;
|
||||
sourceTree = "<group>";
|
||||
@ -2492,6 +2496,7 @@
|
||||
D041E3F81E535A88008C24B4 /* RemovePeerMember.swift in Sources */,
|
||||
D076F8892296D8E9004F895A /* ManageChannelDiscussionGroup.swift in Sources */,
|
||||
D0B417C11D7DCEEF004562A4 /* ApiGroupOrChannel.swift in Sources */,
|
||||
D0BAAA14230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */,
|
||||
D041E3F51E535464008C24B4 /* AddPeerMember.swift in Sources */,
|
||||
D0AF32311FACEDEC0097362B /* CoreSettings.swift in Sources */,
|
||||
D054649A20738760002ECC1E /* SecureIdRentalAgreementValue.swift in Sources */,
|
||||
@ -2883,6 +2888,7 @@
|
||||
D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */,
|
||||
D01843A92190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */,
|
||||
D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */,
|
||||
D0BAAA15230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */,
|
||||
D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */,
|
||||
D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */,
|
||||
D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */,
|
||||
|
||||
@ -1229,6 +1229,9 @@ final class SharedApplicationContext {
|
||||
extendNow = true
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
extendNow = false
|
||||
#endif
|
||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0, extendNow: extendNow)
|
||||
})
|
||||
|
||||
@ -1300,7 +1303,20 @@ final class SharedApplicationContext {
|
||||
}
|
||||
|
||||
Logger.shared.log("App \(self.episodeId)", "remoteNotification: \(redactedPayload)")
|
||||
completionHandler(UIBackgroundFetchResult.noData)
|
||||
|
||||
if userInfo["p"] == nil {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
|
||||
sharedApplicationContext.wakeupManager.replaceCurrentExtensionWithExternalTime(completion: {
|
||||
completionHandler(.newData)
|
||||
}, timeout: 29.0)
|
||||
sharedApplicationContext.notificationManager.addNotification(userInfo)
|
||||
})
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
|
||||
@ -1325,261 +1341,11 @@ final class SharedApplicationContext {
|
||||
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||
sharedApplicationContext.notificationManager.addEncryptedNotification(payload.dictionaryPayload)
|
||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*private func processPushPayload(_ payload: [AnyHashable: Any], account: Account) {
|
||||
let decryptedPayload: Signal<[AnyHashable: Any]?, NoError>
|
||||
if let _ = payload["aps"] as? [AnyHashable: Any] {
|
||||
decryptedPayload = .single(payload)
|
||||
} else if var encryptedPayload = payload["p"] as? String {
|
||||
encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+")
|
||||
encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/")
|
||||
while encryptedPayload.count % 4 != 0 {
|
||||
encryptedPayload.append("=")
|
||||
}
|
||||
if let data = Data(base64Encoded: encryptedPayload) {
|
||||
decryptedPayload = decryptedNotificationPayload(account: account, data: data)
|
||||
|> map { value -> [AnyHashable: Any]? in
|
||||
if let value = value, let object = try? JSONSerialization.jsonObject(with: value, options: []) {
|
||||
return object as? [AnyHashable: Any]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
decryptedPayload = .single(nil)
|
||||
}
|
||||
} else {
|
||||
decryptedPayload = .single(nil)
|
||||
}
|
||||
|
||||
let _ = (decryptedPayload
|
||||
|> deliverOnMainQueue).start(next: { payload in
|
||||
guard let payload = payload else {
|
||||
return
|
||||
}
|
||||
|
||||
var redactedPayload = payload
|
||||
if var aps = redactedPayload["aps"] as? [AnyHashable: Any] {
|
||||
if Logger.shared.redactSensitiveData {
|
||||
if aps["alert"] != nil {
|
||||
aps["alert"] = "[[redacted]]"
|
||||
}
|
||||
if aps["body"] != nil {
|
||||
aps["body"] = "[[redacted]]"
|
||||
}
|
||||
}
|
||||
redactedPayload["aps"] = aps
|
||||
}
|
||||
Logger.shared.log("Apns \(self.episodeId)", "\(redactedPayload)")
|
||||
|
||||
let aps = payload["aps"] as? [AnyHashable: Any]
|
||||
|
||||
if UIApplication.shared.applicationState == .background {
|
||||
var readMessageId: MessageId?
|
||||
var isCall = false
|
||||
var isAnnouncement = false
|
||||
var isLocationPolling = false
|
||||
var isMutePolling = false
|
||||
var title: String = ""
|
||||
var body: String?
|
||||
var apnsSound: String?
|
||||
var configurationUpdate: (Int32, String, Int32, Data?)?
|
||||
if let aps = aps, let alert = aps["alert"] as? String {
|
||||
if let range = alert.range(of: ": ") {
|
||||
title = String(alert[..<range.lowerBound])
|
||||
body = String(alert[range.upperBound...])
|
||||
} else {
|
||||
body = alert
|
||||
}
|
||||
} else if let aps = aps, let alert = aps["alert"] as? [AnyHashable: AnyObject] {
|
||||
if let alertBody = alert["body"] as? String {
|
||||
body = alertBody
|
||||
if let alertTitle = alert["title"] as? String {
|
||||
title = alertTitle
|
||||
}
|
||||
}
|
||||
if let locKey = alert["loc-key"] as? String {
|
||||
if locKey == "PHONE_CALL_REQUEST" {
|
||||
isCall = true
|
||||
} else if locKey == "GEO_LIVE_PENDING" {
|
||||
isLocationPolling = true
|
||||
} else if locKey == "MESSAGE_MUTED" {
|
||||
isMutePolling = true
|
||||
}
|
||||
let string = NSLocalizedString(locKey, comment: "")
|
||||
if !string.isEmpty {
|
||||
if let locArgs = alert["loc-args"] as? [AnyObject] {
|
||||
var args: [CVarArg] = []
|
||||
var failed = false
|
||||
for arg in locArgs {
|
||||
if let arg = arg as? CVarArg {
|
||||
args.append(arg)
|
||||
} else {
|
||||
failed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
body = "\(string)"
|
||||
} else {
|
||||
body = String(format: string, arguments: args)
|
||||
}
|
||||
} else {
|
||||
body = "\(string)"
|
||||
}
|
||||
} else {
|
||||
body = nil
|
||||
}
|
||||
} else {
|
||||
body = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let aps = aps, let address = aps["addr"] as? String, let datacenterId = aps["dc"] as? Int {
|
||||
var host = address
|
||||
var port: Int32 = 443
|
||||
if let range = address.range(of: ":") {
|
||||
host = String(address[address.startIndex ..< range.lowerBound])
|
||||
if let portValue = Int(String(address[range.upperBound...])) {
|
||||
port = Int32(portValue)
|
||||
}
|
||||
}
|
||||
var secret: Data?
|
||||
if let secretString = aps["sec"] as? String {
|
||||
let data = dataWithHexString(secretString)
|
||||
if data.count == 16 || data.count == 32 {
|
||||
secret = data
|
||||
}
|
||||
}
|
||||
configurationUpdate = (Int32(datacenterId), host, port, secret)
|
||||
}
|
||||
|
||||
if let aps = aps, let sound = aps["sound"] as? String {
|
||||
apnsSound = sound
|
||||
}
|
||||
|
||||
if payload["call_id"] != nil {
|
||||
isCall = true
|
||||
}
|
||||
|
||||
if payload["announcement"] != nil {
|
||||
isAnnouncement = true
|
||||
}
|
||||
|
||||
if let body = body {
|
||||
if isAnnouncement {
|
||||
self.queuedAnnouncements.append(body)
|
||||
self.maybeDequeueAnnouncements()
|
||||
} else {
|
||||
var peerId: PeerId?
|
||||
var notificationRequestId: NotificationManagedNotificationRequestId?
|
||||
|
||||
if let fromId = payload["from_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["chat_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["channel_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
|
||||
}
|
||||
|
||||
if let msgId = payload["msg_id"] {
|
||||
let msgIdValue = msgId as! NSString
|
||||
if let peerId = peerId {
|
||||
notificationRequestId = .messageId(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(msgIdValue.intValue)))
|
||||
}
|
||||
} else if let randomId = payload["random_id"] {
|
||||
let randomIdValue = randomId as! NSString
|
||||
var peerId: PeerId?
|
||||
if let encryptionIdString = payload["encryption_id"] as? String, let encryptionId = Int32(encryptionIdString) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.SecretChat, id: encryptionId)
|
||||
}
|
||||
notificationRequestId = .globallyUniqueId(randomIdValue.longLongValue, peerId)
|
||||
} else {
|
||||
isMutePolling = true
|
||||
}
|
||||
|
||||
if let notificationRequestId = notificationRequestId {
|
||||
self.queuedNotificationRequests.append((title, body, apnsSound, notificationRequestId))
|
||||
self.maybeDequeueNotificationRequests()
|
||||
} else if isMutePolling {
|
||||
self.queuedMutePolling = true
|
||||
self.maybeDequeueNotificationRequests()
|
||||
}
|
||||
}
|
||||
} else if let _ = payload["max_id"] {
|
||||
var peerId: PeerId?
|
||||
|
||||
if let fromId = payload["from_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["chat_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["channel_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
|
||||
}
|
||||
|
||||
if let peerId = peerId {
|
||||
if let msgId = payload["max_id"] {
|
||||
let msgIdValue = msgId as! NSString
|
||||
if msgIdValue.intValue != 0 {
|
||||
readMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(msgIdValue.intValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var addedWakeups = Set<QueuedWakeup>()
|
||||
if isCall {
|
||||
addedWakeups.insert(.call)
|
||||
}
|
||||
if isLocationPolling {
|
||||
addedWakeups.insert(.backgroundLocation)
|
||||
}
|
||||
if !addedWakeups.isEmpty {
|
||||
self.queuedWakeups.formUnion(addedWakeups)
|
||||
self.maybeDequeueWakeups()
|
||||
}
|
||||
if let readMessageId = readMessageId {
|
||||
self.clearNotificationsManager?.append(readMessageId)
|
||||
self.clearNotificationsManager?.commitNow()
|
||||
|
||||
let signal = self.context.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { context -> Signal<Void, NoError> in
|
||||
if let context = context {
|
||||
return context.context.account.postbox.transaction (ignoreDisabled: true, { transaction -> Void in
|
||||
transaction.applyIncomingReadMaxId(readMessageId)
|
||||
})
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
let _ = signal.start()
|
||||
}
|
||||
|
||||
if let (datacenterId, host, port, secret) = configurationUpdate {
|
||||
let signal = self.context.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { context -> Signal<Void, NoError> in
|
||||
if let context = context {
|
||||
context.context.account.network.mergeBackupDatacenterAddress(datacenterId: datacenterId, host: host, port: port, secret: secret)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
let _ = signal.start()
|
||||
}
|
||||
}
|
||||
})
|
||||
}*/
|
||||
|
||||
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
|
||||
Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)")
|
||||
}
|
||||
|
||||
@ -530,11 +530,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
let reactions: [(String, String, String)] = [
|
||||
("😒", "Sad", "sad"),
|
||||
("😔", "Sad", "sad"),
|
||||
("😳", "Surprised", "surprised"),
|
||||
("😂", "Fun", "lol"),
|
||||
("👍", "Like", "thumbsup"),
|
||||
("❤", "Love", "heart"),
|
||||
("🥳", "Celebrate", "celebrate"),
|
||||
("😭", "Cry", "cry"),
|
||||
("😒", "Meh", "meh"),
|
||||
("👌", "OK", "ok"),
|
||||
("😐", "Poker", "poker"),
|
||||
("💩", "Poop", "poop"),
|
||||
("😊", "Smile", "smile")
|
||||
]
|
||||
|
||||
var reactionItems: [ReactionContextItem] = []
|
||||
@ -7045,8 +7052,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
if actions.contains(3) {
|
||||
let mediaBox = strongSelf.context.account.postbox.mediaBox
|
||||
let _ = strongSelf.context.account.postbox.transaction({ transaction -> Void in
|
||||
transaction.removeAllMessagesWithAuthor(peerId, authorId: author.id, namespace: Namespaces.Message.Cloud)
|
||||
deleteAllMessagesWithAuthor(transaction: transaction, mediaBox: mediaBox, peerId: peerId, authorId: author.id, namespace: Namespaces.Message.Cloud)
|
||||
}).start()
|
||||
let _ = clearAuthorHistory(account: strongSelf.context.account, peerId: peerId, memberId: author.id).start()
|
||||
} else if actions.contains(0) {
|
||||
|
||||
@ -9,6 +9,7 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
|
||||
private let dateFont = UIFont.italicSystemFont(ofSize: 11.0)
|
||||
private let reactionCountFont = Font.semiboldItalic(11.0)
|
||||
|
||||
private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
|
||||
if let _ = layer.animation(forKey: "clockFrameAnimation") {
|
||||
@ -62,50 +63,10 @@ enum ChatMessageDateAndStatusType: Equatable {
|
||||
case ImageOutgoing(ChatMessageDateAndStatusOutgoingType)
|
||||
case FreeIncoming
|
||||
case FreeOutgoing(ChatMessageDateAndStatusOutgoingType)
|
||||
|
||||
static func ==(lhs: ChatMessageDateAndStatusType, rhs: ChatMessageDateAndStatusType) -> Bool {
|
||||
switch lhs {
|
||||
case .BubbleIncoming:
|
||||
if case .BubbleIncoming = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .BubbleOutgoing(type):
|
||||
if case .BubbleOutgoing(type) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .ImageIncoming:
|
||||
if case .ImageIncoming = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .ImageOutgoing(type):
|
||||
if case .ImageOutgoing(type) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .FreeIncoming:
|
||||
if case .FreeIncoming = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .FreeOutgoing(type):
|
||||
if case .FreeOutgoing(type) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let reactionSize: CGFloat = 18.0
|
||||
private let reactionSize: CGFloat = 19.0
|
||||
private let reactionFont = Font.regular(12.0)
|
||||
|
||||
private final class StatusReactionNode: ASImageNode {
|
||||
let value: String
|
||||
@ -120,7 +81,7 @@ private final class StatusReactionNode: ASImageNode {
|
||||
self.image = generateImage(CGSize(width: reactionSize, height: reactionSize), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
let string = NSAttributedString(string: value, font: Font.regular(11.0), textColor: .black)
|
||||
let string = NSAttributedString(string: value, font: reactionFont, textColor: .black)
|
||||
string.draw(at: CGPoint(x: 1.0, y: 3.0))
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
@ -136,6 +97,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
private let dateNode: TextNode
|
||||
private var impressionIcon: ASImageNode?
|
||||
private var reactionNodes: [StatusReactionNode] = []
|
||||
private var reactionCountNode: TextNode?
|
||||
|
||||
private var type: ChatMessageDateAndStatusType?
|
||||
private var theme: ChatPresentationThemeData?
|
||||
@ -166,6 +128,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
let currentType = self.type
|
||||
let currentTheme = self.theme
|
||||
|
||||
let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode)
|
||||
|
||||
return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in
|
||||
let dateColor: UIColor
|
||||
var backgroundImage: UIImage?
|
||||
@ -396,9 +360,20 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
backgroundInsets = UIEdgeInsets(top: 2.0, left: 7.0, bottom: 2.0, right: 7.0)
|
||||
}
|
||||
|
||||
var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
|
||||
var reactionInset: CGFloat = 0.0
|
||||
if !reactions.isEmpty {
|
||||
reactionInset = 1.0 + CGFloat(reactions.count) * reactionSize
|
||||
reactionInset = 5.0 + CGFloat(reactions.count) * reactionSize
|
||||
|
||||
var count = 0
|
||||
for reaction in reactions {
|
||||
count += Int(reaction.count)
|
||||
}
|
||||
|
||||
let layoutAndApply = makeReactionCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(count)", font: reactionCountFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
|
||||
reactionInset += layoutAndApply.0.size.width + 2.0
|
||||
reactionCountLayoutAndApply = layoutAndApply
|
||||
}
|
||||
leftInset += reactionInset
|
||||
|
||||
@ -543,7 +518,14 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
} else {
|
||||
node = StatusReactionNode(value: reactions[i].value, count: Int(reactions[i].count))
|
||||
if strongSelf.reactionNodes.count > i {
|
||||
strongSelf.reactionNodes[i].removeFromSupernode()
|
||||
let previousNode = strongSelf.reactionNodes[i]
|
||||
if animated {
|
||||
previousNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousNode] _ in
|
||||
previousNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
previousNode.removeFromSupernode()
|
||||
}
|
||||
strongSelf.reactionNodes[i] = node
|
||||
} else {
|
||||
strongSelf.reactionNodes.append(node)
|
||||
@ -551,12 +533,44 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
if node.supernode == nil {
|
||||
strongSelf.addSubnode(node)
|
||||
if animated {
|
||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
}
|
||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + 1.0 + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize))
|
||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize))
|
||||
reactionOffset += reactionSize
|
||||
}
|
||||
for _ in reactions.count ..< strongSelf.reactionNodes.count {
|
||||
strongSelf.reactionNodes.removeLast().removeFromSupernode()
|
||||
let node = strongSelf.reactionNodes.removeLast()
|
||||
if animated {
|
||||
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
||||
node?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
node.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let (layout, apply) = reactionCountLayoutAndApply {
|
||||
let node = apply()
|
||||
if strongSelf.reactionCountNode !== node {
|
||||
strongSelf.reactionCountNode?.removeFromSupernode()
|
||||
strongSelf.addSubnode(node)
|
||||
strongSelf.reactionCountNode = node
|
||||
if animated {
|
||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
}
|
||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1, y: backgroundInsets.top + 1.0 + offset), size: layout.size)
|
||||
} else if let reactionCountNode = strongSelf.reactionCountNode {
|
||||
strongSelf.reactionCountNode = nil
|
||||
if animated {
|
||||
reactionCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountNode] _ in
|
||||
reactionCountNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
reactionCountNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -151,161 +151,187 @@ public final class NotificationViewControllerImpl {
|
||||
return
|
||||
}
|
||||
|
||||
if let accountIdValue = notification.request.content.userInfo["accountId"] as? Int64, let peerIdValue = notification.request.content.userInfo["peerId"] as? Int64, let messageIdNamespace = notification.request.content.userInfo["messageId.namespace"] as? Int32, let messageIdId = notification.request.content.userInfo["messageId.id"] as? Int32, let mediaDataString = notification.request.content.userInfo["media"] as? String, let mediaData = Data(base64Encoded: mediaDataString), let media = parseMediaData(data: mediaData) {
|
||||
let messageId = MessageId(peerId: PeerId(peerIdValue), namespace: messageIdNamespace, id: messageIdId)
|
||||
guard let accountIdValue = notification.request.content.userInfo["accountId"] as? Int64 else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let peerIdValue = notification.request.content.userInfo["peerId"] as? Int64 else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let messageIdNamespace = notification.request.content.userInfo["messageId.namespace"] as? Int32 else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let messageIdId = notification.request.content.userInfo["messageId.id"] as? Int32 else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let mediaDataString = notification.request.content.userInfo["media"] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let mediaData = Data(base64Encoded: mediaDataString) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let media = parseMediaData(data: mediaData) else {
|
||||
return
|
||||
}
|
||||
|
||||
let messageId = MessageId(peerId: PeerId(peerIdValue), namespace: messageIdNamespace, id: messageIdId)
|
||||
|
||||
if let image = media as? TelegramMediaImage, let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 120.0, height: 120.0)), let largestRepresentation = largestImageRepresentation(image.representations) {
|
||||
let dimensions = largestRepresentation.dimensions
|
||||
let fittedSize = dimensions.fitted(CGSize(width: view.bounds.width, height: 1000.0))
|
||||
view.frame = CGRect(origin: view.frame.origin, size: fittedSize)
|
||||
self.setPreferredContentSize(fittedSize)
|
||||
|
||||
if let image = media as? TelegramMediaImage, let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 120.0, height: 120.0)), let largestRepresentation = largestImageRepresentation(image.representations) {
|
||||
let dimensions = largestRepresentation.dimensions
|
||||
let fittedSize = dimensions.fitted(CGSize(width: view.bounds.width, height: 1000.0))
|
||||
view.frame = CGRect(origin: view.frame.origin, size: fittedSize)
|
||||
self.setPreferredContentSize(fittedSize)
|
||||
|
||||
self.imageInfo = (false, dimensions)
|
||||
self.updateImageLayout(boundingSize: view.bounds.size)
|
||||
|
||||
let mediaBoxPath = accountsPath + "/" + accountRecordIdPathName(AccountRecordId(rawValue: accountIdValue)) + "/postbox/media"
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(largestRepresentation.resource.id.uniqueId)"), options: .mappedRead) {
|
||||
self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(nil, data, true)))
|
||||
|> map { $0.1 })
|
||||
return
|
||||
self.imageInfo = (false, dimensions)
|
||||
self.updateImageLayout(boundingSize: view.bounds.size)
|
||||
|
||||
let mediaBoxPath = accountsPath + "/" + accountRecordIdPathName(AccountRecordId(rawValue: accountIdValue)) + "/postbox/media"
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(largestRepresentation.resource.id.uniqueId)"), options: .mappedRead) {
|
||||
self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(nil, data, true)))
|
||||
|> map { $0.1 })
|
||||
return
|
||||
}
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(thumbnailRepresentation.resource.id.uniqueId)"), options: .mappedRead) {
|
||||
self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(data, nil, false)))
|
||||
|> map { $0.1 })
|
||||
}
|
||||
|
||||
guard let sharedAccountContext = sharedAccountContext else {
|
||||
return
|
||||
}
|
||||
|
||||
self.applyDisposable.set((sharedAccountContext.activeAccounts
|
||||
|> map { _, accounts, _ -> Account? in
|
||||
return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1
|
||||
}
|
||||
|> filter { account in
|
||||
return account != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> mapToSignal { account -> Signal<(Account, ImageMediaReference?), NoError> in
|
||||
guard let account = account else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(thumbnailRepresentation.resource.id.uniqueId)"), options: .mappedRead) {
|
||||
self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(data, nil, false)))
|
||||
|> map { $0.1 })
|
||||
}
|
||||
|
||||
guard let sharedAccountContext = sharedAccountContext else {
|
||||
return
|
||||
}
|
||||
|
||||
self.applyDisposable.set((sharedAccountContext.activeAccounts
|
||||
|> map { _, accounts, _ -> Account? in
|
||||
return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1
|
||||
}
|
||||
|> filter { account in
|
||||
return account != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> mapToSignal { account -> Signal<(Account, ImageMediaReference?), NoError> in
|
||||
guard let account = account else {
|
||||
return .complete()
|
||||
}
|
||||
return account.postbox.messageAtId(messageId)
|
||||
|> take(1)
|
||||
|> map { message in
|
||||
var imageReference: ImageMediaReference?
|
||||
if let message = message {
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
imageReference = .message(message: MessageReference(message), media: image)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imageReference = .standalone(media: image)
|
||||
}
|
||||
return (account, imageReference)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] accountAndImage in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let imageReference = accountAndImage.1 {
|
||||
strongSelf.imageNode.setSignal(chatMessagePhoto(postbox: accountAndImage.0.postbox, photoReference: imageReference))
|
||||
|
||||
accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true))
|
||||
strongSelf.fetchedDisposable.set(standaloneChatMessagePhotoInteractiveFetched(account: accountAndImage.0, photoReference: imageReference).start())
|
||||
}
|
||||
}))
|
||||
} else if let file = media as? TelegramMediaFile, let dimensions = file.dimensions {
|
||||
guard let sharedAccountContext = sharedAccountContext else {
|
||||
return
|
||||
}
|
||||
|
||||
let fittedSize = dimensions.fitted(CGSize(width: min(256.0, view.bounds.width), height: 256.0))
|
||||
view.frame = CGRect(origin: view.frame.origin, size: fittedSize)
|
||||
self.setPreferredContentSize(fittedSize)
|
||||
|
||||
self.imageInfo = (true, dimensions)
|
||||
self.updateImageLayout(boundingSize: view.bounds.size)
|
||||
|
||||
self.applyDisposable.set((sharedAccountContext.activeAccounts
|
||||
|> map { _, accounts, _ -> Account? in
|
||||
return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1
|
||||
}
|
||||
|> filter { account in
|
||||
return account != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> mapToSignal { account -> Signal<(Account, FileMediaReference?), NoError> in
|
||||
guard let account = account else {
|
||||
return .complete()
|
||||
}
|
||||
return account.postbox.messageAtId(messageId)
|
||||
return account.postbox.messageAtId(messageId)
|
||||
|> take(1)
|
||||
|> map { message in
|
||||
var fileReference: FileMediaReference?
|
||||
var imageReference: ImageMediaReference?
|
||||
if let message = message {
|
||||
for media in message.media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
fileReference = .message(message: MessageReference(message), media: file)
|
||||
if let image = media as? TelegramMediaImage {
|
||||
imageReference = .message(message: MessageReference(message), media: image)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fileReference = .standalone(media: file)
|
||||
imageReference = .standalone(media: image)
|
||||
}
|
||||
return (account, fileReference)
|
||||
return (account, imageReference)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] accountAndImage in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let imageReference = accountAndImage.1 {
|
||||
strongSelf.imageNode.setSignal(chatMessagePhoto(postbox: accountAndImage.0.postbox, photoReference: imageReference))
|
||||
|
||||
accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true))
|
||||
strongSelf.fetchedDisposable.set(standaloneChatMessagePhotoInteractiveFetched(account: accountAndImage.0, photoReference: imageReference).start())
|
||||
}
|
||||
}))
|
||||
} else if let file = media as? TelegramMediaFile, let dimensions = file.dimensions {
|
||||
guard let sharedAccountContext = sharedAccountContext else {
|
||||
return
|
||||
}
|
||||
|
||||
let fittedSize = dimensions.fitted(CGSize(width: min(256.0, view.bounds.width), height: 256.0))
|
||||
view.frame = CGRect(origin: view.frame.origin, size: fittedSize)
|
||||
self.setPreferredContentSize(fittedSize)
|
||||
|
||||
self.imageInfo = (true, dimensions)
|
||||
self.updateImageLayout(boundingSize: view.bounds.size)
|
||||
|
||||
self.applyDisposable.set((sharedAccountContext.activeAccounts
|
||||
|> map { _, accounts, _ -> Account? in
|
||||
return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1
|
||||
}
|
||||
|> filter { account in
|
||||
return account != nil
|
||||
}
|
||||
|> take(1)
|
||||
|> mapToSignal { account -> Signal<(Account, FileMediaReference?), NoError> in
|
||||
guard let account = account else {
|
||||
return .complete()
|
||||
}
|
||||
return account.postbox.messageAtId(messageId)
|
||||
|> take(1)
|
||||
|> map { message in
|
||||
var fileReference: FileMediaReference?
|
||||
if let message = message {
|
||||
for media in message.media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
fileReference = .message(message: MessageReference(message), media: file)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fileReference = .standalone(media: file)
|
||||
}
|
||||
return (account, fileReference)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak view] accountAndImage in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let fileReference = accountAndImage.1 {
|
||||
if file.isAnimatedSticker {
|
||||
let animatedStickerNode: AnimatedStickerNode
|
||||
if let current = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode = current
|
||||
} else {
|
||||
animatedStickerNode = AnimatedStickerNode()
|
||||
strongSelf.animatedStickerNode = animatedStickerNode
|
||||
animatedStickerNode.started = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.imageNode.isHidden = true
|
||||
}
|
||||
if !strongSelf.imageNode.frame.width.isZero {
|
||||
animatedStickerNode.frame = strongSelf.imageNode.frame
|
||||
animatedStickerNode.updateLayout(size: strongSelf.imageNode.frame.size)
|
||||
}
|
||||
view?.addSubnode(animatedStickerNode)
|
||||
}
|
||||
let dimensions = fileReference.media.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
||||
let fittedDimensions = dimensions.aspectFitted(CGSize(width: 512.0, height: 512.0))
|
||||
strongSelf.imageNode.setSignal(chatMessageAnimatedSticker(postbox: accountAndImage.0.postbox, file: fileReference.media, small: false, size: fittedDimensions))
|
||||
animatedStickerNode.setup(account: accountAndImage.0, resource: .resource(fileReference.media.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct)
|
||||
animatedStickerNode.visibility = true
|
||||
|
||||
accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true))
|
||||
strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start())
|
||||
} else if file.isSticker {
|
||||
if let animatedStickerNode = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode.removeFromSupernode()
|
||||
strongSelf.animatedStickerNode = nil
|
||||
}
|
||||
strongSelf.imageNode.isHidden = false
|
||||
|
||||
strongSelf.imageNode.setSignal(chatMessageSticker(account: accountAndImage.0, file: file, small: false))
|
||||
|
||||
accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true))
|
||||
strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start())
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak view] accountAndImage in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let fileReference = accountAndImage.1 {
|
||||
if file.isAnimatedSticker {
|
||||
let animatedStickerNode: AnimatedStickerNode
|
||||
if let current = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode = current
|
||||
} else {
|
||||
animatedStickerNode = AnimatedStickerNode()
|
||||
strongSelf.animatedStickerNode = animatedStickerNode
|
||||
animatedStickerNode.started = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.imageNode.isHidden = true
|
||||
}
|
||||
if !strongSelf.imageNode.frame.width.isZero {
|
||||
animatedStickerNode.frame = strongSelf.imageNode.frame
|
||||
animatedStickerNode.updateLayout(size: strongSelf.imageNode.frame.size)
|
||||
}
|
||||
view?.addSubnode(animatedStickerNode)
|
||||
}
|
||||
let dimensions = fileReference.media.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
||||
let fittedDimensions = dimensions.aspectFitted(CGSize(width: 512.0, height: 512.0))
|
||||
strongSelf.imageNode.setSignal(chatMessageAnimatedSticker(postbox: accountAndImage.0.postbox, file: fileReference.media, small: false, size: fittedDimensions))
|
||||
animatedStickerNode.setup(account: accountAndImage.0, resource: .resource(fileReference.media.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct)
|
||||
animatedStickerNode.visibility = true
|
||||
|
||||
accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true))
|
||||
strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start())
|
||||
} else if file.isSticker {
|
||||
if let animatedStickerNode = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode.removeFromSupernode()
|
||||
strongSelf.animatedStickerNode = nil
|
||||
}
|
||||
strongSelf.imageNode.isHidden = false
|
||||
|
||||
strongSelf.imageNode.setSignal(chatMessageSticker(account: accountAndImage.0, file: file, small: false))
|
||||
|
||||
accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true))
|
||||
strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start())
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,14 @@ private final class PollStateContext {
|
||||
}
|
||||
}
|
||||
|
||||
private final class NotificationInfo {
|
||||
let dict: [AnyHashable: Any]
|
||||
|
||||
init(dict: [AnyHashable: Any]) {
|
||||
self.dict = dict
|
||||
}
|
||||
}
|
||||
|
||||
public final class SharedNotificationManager {
|
||||
private let episodeId: UInt32
|
||||
private let application: UIApplication
|
||||
@ -35,7 +43,7 @@ public final class SharedNotificationManager {
|
||||
private var accountsAndKeys: [(Account, Bool, MasterNotificationKey)]?
|
||||
private var accountsAndKeysDisposable: Disposable?
|
||||
|
||||
private var encryptedNotifications: [[AnyHashable: Any]] = []
|
||||
private var notifications: [NotificationInfo] = []
|
||||
|
||||
private var pollStateContexts: [AccountRecordId: PollStateContext] = [:]
|
||||
|
||||
@ -149,8 +157,8 @@ public final class SharedNotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
func addEncryptedNotification(_ dict: [AnyHashable: Any]) {
|
||||
self.encryptedNotifications.append(dict)
|
||||
func addNotification(_ dict: [AnyHashable: Any]) {
|
||||
self.notifications.append(NotificationInfo(dict: dict))
|
||||
|
||||
if self.accountsAndKeys != nil {
|
||||
self.process()
|
||||
@ -162,8 +170,8 @@ public final class SharedNotificationManager {
|
||||
return
|
||||
}
|
||||
var decryptedNotifications: [(Account, Bool, [AnyHashable: Any])] = []
|
||||
for dict in self.encryptedNotifications {
|
||||
if var encryptedPayload = dict["p"] as? String {
|
||||
for notification in self.notifications {
|
||||
if var encryptedPayload = notification.dict["p"] as? String {
|
||||
encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+")
|
||||
encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/")
|
||||
while encryptedPayload.count % 4 != 0 {
|
||||
@ -181,7 +189,7 @@ public final class SharedNotificationManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.encryptedNotifications.removeAll()
|
||||
self.notifications.removeAll()
|
||||
|
||||
for (account, isCurrent, payload) in decryptedNotifications {
|
||||
var redactedPayload = payload
|
||||
@ -206,7 +214,7 @@ public final class SharedNotificationManager {
|
||||
var isAnnouncement = false
|
||||
var isLocationPolling = false
|
||||
var notificationRequestId: NotificationManagedNotificationRequestId?
|
||||
var isMutePolling = false
|
||||
var shouldPollState = false
|
||||
var title: String = ""
|
||||
var body: String?
|
||||
var apnsSound: String?
|
||||
@ -226,63 +234,37 @@ public final class SharedNotificationManager {
|
||||
title = alertTitle
|
||||
}
|
||||
}
|
||||
if let locKey = alert["loc-key"] as? String {
|
||||
if locKey == "SESSION_REVOKE" {
|
||||
isForcedLogOut = true
|
||||
} else if locKey == "PHONE_CALL_REQUEST" {
|
||||
isCall = true
|
||||
} else if locKey == "GEO_LIVE_PENDING" {
|
||||
isLocationPolling = true
|
||||
} else if locKey == "MESSAGE_MUTED" {
|
||||
isMutePolling = true
|
||||
} else if locKey == "MESSAGE_DELETED" {
|
||||
var peerId: PeerId?
|
||||
if let fromId = payload["from_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["chat_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["channel_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
|
||||
}
|
||||
if let peerId = peerId {
|
||||
if let messageIds = payload["messages"] as? String {
|
||||
for messageId in messageIds.split(separator: ",") {
|
||||
if let messageIdValue = Int32(messageId) {
|
||||
messagesDeleted.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue))
|
||||
}
|
||||
}
|
||||
if let locKey = payload["loc-key"] as? String {
|
||||
if locKey == "SESSION_REVOKE" {
|
||||
isForcedLogOut = true
|
||||
} else if locKey == "PHONE_CALL_REQUEST" {
|
||||
isCall = true
|
||||
} else if locKey == "GEO_LIVE_PENDING" {
|
||||
isLocationPolling = true
|
||||
} else if locKey == "MESSAGE_MUTED" {
|
||||
shouldPollState = true
|
||||
} else if locKey == "MESSAGE_DELETED" {
|
||||
var peerId: PeerId?
|
||||
if let fromId = payload["from_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["chat_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue))
|
||||
} else if let fromId = payload["channel_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue))
|
||||
}
|
||||
if let peerId = peerId {
|
||||
if let messageIds = payload["messages"] as? String {
|
||||
for messageId in messageIds.split(separator: ",") {
|
||||
if let messageIdValue = Int32(messageId) {
|
||||
messagesDeleted.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let string = NSLocalizedString(locKey, comment: "")
|
||||
if !string.isEmpty {
|
||||
if let locArgs = alert["loc-args"] as? [AnyObject] {
|
||||
var args: [CVarArg] = []
|
||||
var failed = false
|
||||
for arg in locArgs {
|
||||
if let arg = arg as? CVarArg {
|
||||
args.append(arg)
|
||||
} else {
|
||||
failed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
body = "\(string)"
|
||||
} else {
|
||||
body = String(format: string, arguments: args)
|
||||
}
|
||||
} else {
|
||||
body = "\(string)"
|
||||
}
|
||||
} else {
|
||||
body = nil
|
||||
}
|
||||
} else {
|
||||
body = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,6 +308,8 @@ public final class SharedNotificationManager {
|
||||
} else {
|
||||
var peerId: PeerId?
|
||||
|
||||
shouldPollState = true
|
||||
|
||||
if let fromId = payload["from_id"] {
|
||||
let fromIdValue = fromId as! NSString
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue))
|
||||
@ -350,7 +334,7 @@ public final class SharedNotificationManager {
|
||||
}
|
||||
notificationRequestId = .globallyUniqueId(randomIdValue.longLongValue, peerId)
|
||||
} else {
|
||||
isMutePolling = true
|
||||
shouldPollState = true
|
||||
}
|
||||
}
|
||||
} else if let _ = payload["max_id"] {
|
||||
@ -386,7 +370,7 @@ public final class SharedNotificationManager {
|
||||
return
|
||||
}
|
||||
|
||||
if notificationRequestId != nil || isMutePolling || isCall {
|
||||
if notificationRequestId != nil || shouldPollState || isCall {
|
||||
if !self.inForeground || !isCurrent {
|
||||
self.beginPollingState(account: account)
|
||||
}
|
||||
@ -411,7 +395,7 @@ public final class SharedNotificationManager {
|
||||
|
||||
if !messagesDeleted.isEmpty {
|
||||
let _ = account.postbox.transaction(ignoreDisabled: true, { transaction -> Void in
|
||||
transaction.deleteMessages(messagesDeleted)
|
||||
deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: messagesDeleted)
|
||||
}).start()
|
||||
}
|
||||
|
||||
|
||||
@ -60,6 +60,8 @@ public final class SharedWakeupManager {
|
||||
private var hasActiveAudioSessionDisposable: Disposable?
|
||||
private var tasksDisposable: Disposable?
|
||||
private var currentTask: (UIBackgroundTaskIdentifier, Double, SwiftSignalKit.Timer)?
|
||||
private var currentExternalCompletion: (() -> Void, SwiftSignalKit.Timer)?
|
||||
private var currentExternalCompletionValidationTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var managedPausedInBackgroundPlayer: Disposable?
|
||||
|
||||
@ -201,8 +203,51 @@ public final class SharedWakeupManager {
|
||||
}
|
||||
}
|
||||
|
||||
func replaceCurrentExtensionWithExternalTime(completion: @escaping () -> Void, timeout: Double) {
|
||||
if let (currentCompletion, timer) = self.currentExternalCompletion {
|
||||
currentCompletion()
|
||||
timer.invalidate()
|
||||
self.currentExternalCompletion = nil
|
||||
}
|
||||
let timer = SwiftSignalKit.Timer(timeout: timeout - 5.0, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.currentExternalCompletionValidationTimer?.invalidate()
|
||||
strongSelf.currentExternalCompletionValidationTimer = nil
|
||||
if let (completion, timer) = strongSelf.currentExternalCompletion {
|
||||
strongSelf.currentExternalCompletion = nil
|
||||
timer.invalidate()
|
||||
completion()
|
||||
}
|
||||
strongSelf.checkTasks()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.currentExternalCompletion = (completion, timer)
|
||||
timer.start()
|
||||
|
||||
self.currentExternalCompletionValidationTimer?.invalidate()
|
||||
let validationTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.currentExternalCompletionValidationTimer?.invalidate()
|
||||
strongSelf.currentExternalCompletionValidationTimer = nil
|
||||
strongSelf.checkTasks()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.currentExternalCompletionValidationTimer = validationTimer
|
||||
validationTimer.start()
|
||||
self.checkTasks()
|
||||
}
|
||||
|
||||
func checkTasks() {
|
||||
var hasTasksForBackgroundExtension = false
|
||||
if self.inForeground || self.hasActiveAudioSession {
|
||||
if let (completion, timer) = self.currentExternalCompletion {
|
||||
self.currentExternalCompletion = nil
|
||||
completion()
|
||||
timer.invalidate()
|
||||
}
|
||||
|
||||
if let (taskId, _, timer) = self.currentTask {
|
||||
self.currentTask = nil
|
||||
timer.invalidate()
|
||||
@ -210,13 +255,21 @@ public final class SharedWakeupManager {
|
||||
self.isInBackgroundExtension = false
|
||||
}
|
||||
} else {
|
||||
var hasTasksForBackgroundExtension = false
|
||||
for (_, _, tasks) in self.accountsAndTasks {
|
||||
if !tasks.isEmpty {
|
||||
hasTasksForBackgroundExtension = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasTasksForBackgroundExtension && self.currentExternalCompletionValidationTimer == nil {
|
||||
if let (completion, timer) = self.currentExternalCompletion {
|
||||
self.currentExternalCompletion = nil
|
||||
completion()
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
if self.activeExplicitExtensionTimer != nil {
|
||||
hasTasksForBackgroundExtension = true
|
||||
}
|
||||
@ -265,11 +318,11 @@ public final class SharedWakeupManager {
|
||||
self.isInBackgroundExtension = false
|
||||
}
|
||||
}
|
||||
self.updateAccounts()
|
||||
self.updateAccounts(hasTasks: hasTasksForBackgroundExtension)
|
||||
}
|
||||
|
||||
private func updateAccounts() {
|
||||
if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension || self.activeExplicitExtensionTimer != nil {
|
||||
private func updateAccounts(hasTasks: Bool) {
|
||||
if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension || (hasTasks && self.currentExternalCompletion != nil) || self.activeExplicitExtensionTimer != nil {
|
||||
for (account, primary, tasks) in self.accountsAndTasks {
|
||||
if (self.inForeground && primary) || !tasks.isEmpty || (self.activeExplicitExtensionTimer != nil && primary) {
|
||||
account.shouldBeServiceTaskMaster.set(.single(.always))
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
public func doesUrlMatchText(url: String, text: String) -> Bool {
|
||||
for c in url {
|
||||
if !c.isASCII {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if url == text {
|
||||
return true
|
||||
}
|
||||
@ -32,7 +37,6 @@ public extension CharacterSet {
|
||||
}
|
||||
|
||||
public func isValidUrl(_ url: String) -> Bool {
|
||||
|
||||
if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), ["http", "https"].contains(url.scheme), let host = url.host, host.contains(".") && url.user == nil {
|
||||
let components = host.components(separatedBy: ".")
|
||||
let domain = (components.first ?? "")
|
||||
|
||||
@ -791,7 +791,7 @@ typedef struct ID3v2EMFunc {
|
||||
|
||||
static const ID3v2EMFunc id3v2_extra_meta_funcs[] = {
|
||||
{ "GEO", "GEOB", read_geobtag, free_geobtag },
|
||||
{ "PIC", "APIC", read_apic, free_apic },
|
||||
//{ "PIC", "APIC", read_apic, free_apic },
|
||||
{ "CHAP","CHAP", read_chapter, free_chapter },
|
||||
{ "PRIV","PRIV", read_priv, free_priv },
|
||||
{ NULL }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user