mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' into buck-test
This commit is contained in:
commit
2d56c99495
660 changed files with 20469 additions and 11858 deletions
|
|
@ -21,7 +21,6 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
|
|||
|
||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||
|
||||
let apiId: Int32 = buildConfig.apiId
|
||||
let languagesCategory = "ios"
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
import Foundation
|
||||
import CommonCrypto
|
||||
import LightweightAccountData
|
||||
|
||||
private func sha256Digest(_ data: Data) -> Data {
|
||||
let length = data.count
|
||||
return data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||
var result = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
|
||||
result.withUnsafeMutableBytes { (destBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
CC_SHA256(bytes, UInt32(length), destBytes)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func decryptedNotificationPayload(accounts: [StoredAccountInfo], data: Data) -> (StoredAccountInfo, [AnyHashable: Any])? {
|
||||
if data.count < 8 + 16 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for account in accounts {
|
||||
let notificationKey = account.notificationKey
|
||||
|
||||
if data.subdata(in: 0 ..< 8) != notificationKey.id {
|
||||
continue
|
||||
}
|
||||
|
||||
let x = 8
|
||||
let msgKey = data.subdata(in: 8 ..< (8 + 16))
|
||||
let rawData = data.subdata(in: (8 + 16) ..< data.count)
|
||||
let sha256_a = sha256Digest(msgKey + notificationKey.data.subdata(in: x ..< (x + 36)))
|
||||
let sha256_b = sha256Digest(notificationKey.data.subdata(in: (40 + x) ..< (40 + x + 36)) + msgKey)
|
||||
let aesKey = sha256_a.subdata(in: 0 ..< 8) + sha256_b.subdata(in: 8 ..< (8 + 16)) + sha256_a.subdata(in: 24 ..< (24 + 8))
|
||||
let aesIv = sha256_b.subdata(in: 0 ..< 8) + sha256_a.subdata(in: 8 ..< (8 + 16)) + sha256_b.subdata(in: 24 ..< (24 + 8))
|
||||
|
||||
guard let data = MTAesDecrypt(rawData, aesKey, aesIv), data.count > 4 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var dataLength: Int32 = 0
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
|
||||
memcpy(&dataLength, bytes, 4)
|
||||
}
|
||||
|
||||
if dataLength < 0 || dataLength > data.count - 4 {
|
||||
return nil
|
||||
}
|
||||
|
||||
let checkMsgKeyLarge = sha256Digest(notificationKey.data.subdata(in: (88 + x) ..< (88 + x + 32)) + data)
|
||||
let checkMsgKey = checkMsgKeyLarge.subdata(in: 8 ..< (8 + 16))
|
||||
|
||||
if checkMsgKey != msgKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
let contentData = data.subdata(in: 4 ..< (4 + Int(dataLength)))
|
||||
guard let result = try? JSONSerialization.jsonObject(with: contentData, options: []) else {
|
||||
return nil
|
||||
}
|
||||
guard let dict = result as? [AnyHashable: Any] else {
|
||||
return nil
|
||||
}
|
||||
return (account, dict)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
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
|
|
@ -1,834 +0,0 @@
|
|||
|
||||
fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
var dict: [Int32 : (BufferReader) -> Any?] = [:]
|
||||
dict[-1471112230] = { return $0.readInt32() }
|
||||
dict[570911930] = { return $0.readInt64() }
|
||||
dict[571523412] = { return $0.readDouble() }
|
||||
dict[-1255641564] = { return parseString($0) }
|
||||
dict[590459437] = { return Api.Photo.parse_photoEmpty($0) }
|
||||
dict[-797637467] = { return Api.Photo.parse_photo($0) }
|
||||
dict[236446268] = { return Api.PhotoSize.parse_photoSizeEmpty($0) }
|
||||
dict[2009052699] = { return Api.PhotoSize.parse_photoSize($0) }
|
||||
dict[-374917894] = { return Api.PhotoSize.parse_photoCachedSize($0) }
|
||||
dict[-525288402] = { return Api.PhotoSize.parse_photoStrippedSize($0) }
|
||||
dict[-1132476723] = { return Api.FileLocation.parse_fileLocationToBeDeprecated($0) }
|
||||
dict[1815593308] = { return Api.DocumentAttribute.parse_documentAttributeImageSize($0) }
|
||||
dict[297109817] = { return Api.DocumentAttribute.parse_documentAttributeAnimated($0) }
|
||||
dict[1662637586] = { return Api.DocumentAttribute.parse_documentAttributeSticker($0) }
|
||||
dict[250621158] = { return Api.DocumentAttribute.parse_documentAttributeVideo($0) }
|
||||
dict[-1739392570] = { return Api.DocumentAttribute.parse_documentAttributeAudio($0) }
|
||||
dict[358154344] = { return Api.DocumentAttribute.parse_documentAttributeFilename($0) }
|
||||
dict[-1744710921] = { return Api.DocumentAttribute.parse_documentAttributeHasStickers($0) }
|
||||
dict[-4838507] = { return Api.InputStickerSet.parse_inputStickerSetEmpty($0) }
|
||||
dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) }
|
||||
dict[-2044933984] = { return Api.InputStickerSet.parse_inputStickerSetShortName($0) }
|
||||
dict[1075322878] = { return Api.InputFileLocation.parse_inputPhotoFileLocation($0) }
|
||||
dict[-1160743548] = { return Api.InputFileLocation.parse_inputDocumentFileLocation($0) }
|
||||
dict[-1361650766] = { return Api.MaskCoords.parse_maskCoords($0) }
|
||||
dict[-1683841855] = { return Api.Document.parse_document($0) }
|
||||
return dict
|
||||
}()
|
||||
|
||||
struct Api {
|
||||
static func parse(_ buffer: Buffer) -> Any? {
|
||||
let reader = BufferReader(buffer)
|
||||
if let signature = reader.readInt32() {
|
||||
return parse(reader, signature: signature)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func parseVector<T>(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? {
|
||||
if let count = reader.readInt32() {
|
||||
var array = [T]()
|
||||
var i: Int32 = 0
|
||||
while i < count {
|
||||
var signature = elementSignature
|
||||
if elementSignature == 0 {
|
||||
if let unboxedSignature = reader.readInt32() {
|
||||
signature = unboxedSignature
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if elementType == Buffer.self {
|
||||
if let item = parseBytes(reader) as? T {
|
||||
array.append(item)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if let item = Api.parse(reader, signature: signature) as? T {
|
||||
array.append(item)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return array
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) {
|
||||
switch object {
|
||||
case let _1 as Api.Photo:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PhotoSize:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.FileLocation:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.DocumentAttribute:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputStickerSet:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputFileLocation:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.MaskCoords:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.Document:
|
||||
_1.serialize(buffer, boxed)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
extension Api {
|
||||
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) {
|
||||
switch self {
|
||||
case .photoEmpty(let id):
|
||||
if boxed {
|
||||
buffer.appendInt32(590459437)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let dcId):
|
||||
if boxed {
|
||||
buffer.appendInt32(-797637467)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeBytes(fileReference, buffer: buffer, boxed: false)
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sizes.count))
|
||||
for item in sizes {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
serializeInt32(dcId, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photoEmpty(let id):
|
||||
return ("photoEmpty", [("id", id)])
|
||||
case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes, let dcId):
|
||||
return ("photo", [("flags", flags), ("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("date", date), ("sizes", sizes), ("dcId", dcId)])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_photoEmpty(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.Photo.photoEmpty(id: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photo(_ reader: BufferReader) -> Photo? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Int64?
|
||||
_3 = reader.readInt64()
|
||||
var _4: Buffer?
|
||||
_4 = parseBytes(reader)
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: [Api.PhotoSize]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self)
|
||||
}
|
||||
var _7: Int32?
|
||||
_7 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, dcId: _7!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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) {
|
||||
switch self {
|
||||
case .photoSizeEmpty(let type):
|
||||
if boxed {
|
||||
buffer.appendInt32(236446268)
|
||||
}
|
||||
serializeString(type, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .photoSize(let type, let location, let w, let h, let size):
|
||||
if boxed {
|
||||
buffer.appendInt32(2009052699)
|
||||
}
|
||||
serializeString(type, buffer: buffer, boxed: false)
|
||||
location.serialize(buffer, true)
|
||||
serializeInt32(w, buffer: buffer, boxed: false)
|
||||
serializeInt32(h, buffer: buffer, boxed: false)
|
||||
serializeInt32(size, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .photoCachedSize(let type, let location, let w, let h, let bytes):
|
||||
if boxed {
|
||||
buffer.appendInt32(-374917894)
|
||||
}
|
||||
serializeString(type, buffer: buffer, boxed: false)
|
||||
location.serialize(buffer, true)
|
||||
serializeInt32(w, buffer: buffer, boxed: false)
|
||||
serializeInt32(h, buffer: buffer, boxed: false)
|
||||
serializeBytes(bytes, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .photoStrippedSize(let type, let bytes):
|
||||
if boxed {
|
||||
buffer.appendInt32(-525288402)
|
||||
}
|
||||
serializeString(type, buffer: buffer, boxed: false)
|
||||
serializeBytes(bytes, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .photoSizeEmpty(let type):
|
||||
return ("photoSizeEmpty", [("type", type)])
|
||||
case .photoSize(let type, let location, let w, let h, let size):
|
||||
return ("photoSize", [("type", type), ("location", location), ("w", w), ("h", h), ("size", size)])
|
||||
case .photoCachedSize(let type, let location, let w, let h, let bytes):
|
||||
return ("photoCachedSize", [("type", type), ("location", location), ("w", w), ("h", h), ("bytes", bytes)])
|
||||
case .photoStrippedSize(let type, let bytes):
|
||||
return ("photoStrippedSize", [("type", type), ("bytes", bytes)])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.PhotoSize.photoSizeEmpty(type: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Api.FileLocation?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.FileLocation
|
||||
}
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Api.FileLocation?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.FileLocation
|
||||
}
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Buffer?
|
||||
_5 = parseBytes(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Buffer?
|
||||
_2 = parseBytes(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.PhotoSize.photoStrippedSize(type: _1!, bytes: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
enum FileLocation: TypeConstructorDescription {
|
||||
case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .fileLocationToBeDeprecated(let volumeId, let localId):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1132476723)
|
||||
}
|
||||
serializeInt64(volumeId, buffer: buffer, boxed: false)
|
||||
serializeInt32(localId, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.FileLocation.fileLocationToBeDeprecated(volumeId: _1!, localId: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
enum DocumentAttribute: TypeConstructorDescription {
|
||||
case documentAttributeImageSize(w: Int32, h: Int32)
|
||||
case documentAttributeAnimated
|
||||
case documentAttributeSticker(flags: Int32, alt: String, stickerset: Api.InputStickerSet, maskCoords: Api.MaskCoords?)
|
||||
case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32)
|
||||
case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?)
|
||||
case documentAttributeFilename(fileName: String)
|
||||
case documentAttributeHasStickers
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .documentAttributeImageSize(let w, let h):
|
||||
if boxed {
|
||||
buffer.appendInt32(1815593308)
|
||||
}
|
||||
serializeInt32(w, buffer: buffer, boxed: false)
|
||||
serializeInt32(h, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .documentAttributeAnimated:
|
||||
if boxed {
|
||||
buffer.appendInt32(297109817)
|
||||
}
|
||||
|
||||
break
|
||||
case .documentAttributeSticker(let flags, let alt, let stickerset, let maskCoords):
|
||||
if boxed {
|
||||
buffer.appendInt32(1662637586)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(alt, buffer: buffer, boxed: false)
|
||||
stickerset.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 0) != 0 {maskCoords!.serialize(buffer, true)}
|
||||
break
|
||||
case .documentAttributeVideo(let flags, let duration, let w, let h):
|
||||
if boxed {
|
||||
buffer.appendInt32(250621158)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(duration, buffer: buffer, boxed: false)
|
||||
serializeInt32(w, buffer: buffer, boxed: false)
|
||||
serializeInt32(h, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1739392570)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(duration, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(performer!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeBytes(waveform!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .documentAttributeFilename(let fileName):
|
||||
if boxed {
|
||||
buffer.appendInt32(358154344)
|
||||
}
|
||||
serializeString(fileName, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .documentAttributeHasStickers:
|
||||
if boxed {
|
||||
buffer.appendInt32(-1744710921)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .documentAttributeImageSize(let w, let h):
|
||||
return ("documentAttributeImageSize", [("w", w), ("h", h)])
|
||||
case .documentAttributeAnimated:
|
||||
return ("documentAttributeAnimated", [])
|
||||
case .documentAttributeSticker(let flags, let alt, let stickerset, let maskCoords):
|
||||
return ("documentAttributeSticker", [("flags", flags), ("alt", alt), ("stickerset", stickerset), ("maskCoords", maskCoords)])
|
||||
case .documentAttributeVideo(let flags, let duration, let w, let h):
|
||||
return ("documentAttributeVideo", [("flags", flags), ("duration", duration), ("w", w), ("h", h)])
|
||||
case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform):
|
||||
return ("documentAttributeAudio", [("flags", flags), ("duration", duration), ("title", title), ("performer", performer), ("waveform", waveform)])
|
||||
case .documentAttributeFilename(let fileName):
|
||||
return ("documentAttributeFilename", [("fileName", fileName)])
|
||||
case .documentAttributeHasStickers:
|
||||
return ("documentAttributeHasStickers", [])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
return Api.DocumentAttribute.documentAttributeAnimated
|
||||
}
|
||||
static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: Api.InputStickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.InputStickerSet
|
||||
}
|
||||
var _4: Api.MaskCoords?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.MaskCoords
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.DocumentAttribute.documentAttributeSticker(flags: _1!, alt: _2!, stickerset: _3!, maskCoords: _4)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) }
|
||||
var _5: Buffer?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {_5 = parseBytes(reader) }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.DocumentAttribute.documentAttributeFilename(fileName: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_documentAttributeHasStickers(_ reader: BufferReader) -> DocumentAttribute? {
|
||||
return Api.DocumentAttribute.documentAttributeHasStickers
|
||||
}
|
||||
|
||||
}
|
||||
enum InputStickerSet: TypeConstructorDescription {
|
||||
case inputStickerSetEmpty
|
||||
case inputStickerSetID(id: Int64, accessHash: Int64)
|
||||
case inputStickerSetShortName(shortName: String)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .inputStickerSetEmpty:
|
||||
if boxed {
|
||||
buffer.appendInt32(-4838507)
|
||||
}
|
||||
|
||||
break
|
||||
case .inputStickerSetID(let id, let accessHash):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1645763991)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .inputStickerSetShortName(let shortName):
|
||||
if boxed {
|
||||
buffer.appendInt32(-2044933984)
|
||||
}
|
||||
serializeString(shortName, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .inputStickerSetEmpty:
|
||||
return ("inputStickerSetEmpty", [])
|
||||
case .inputStickerSetID(let id, let accessHash):
|
||||
return ("inputStickerSetID", [("id", id), ("accessHash", accessHash)])
|
||||
case .inputStickerSetShortName(let shortName):
|
||||
return ("inputStickerSetShortName", [("shortName", shortName)])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? {
|
||||
return Api.InputStickerSet.inputStickerSetEmpty
|
||||
}
|
||||
static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.InputStickerSet.inputStickerSetID(id: _1!, accessHash: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.InputStickerSet.inputStickerSetShortName(shortName: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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) {
|
||||
switch self {
|
||||
case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize):
|
||||
if boxed {
|
||||
buffer.appendInt32(1075322878)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeBytes(fileReference, buffer: buffer, boxed: false)
|
||||
serializeString(thumbSize, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .inputDocumentFileLocation(let id, let accessHash, let fileReference, let thumbSize):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1160743548)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeBytes(fileReference, buffer: buffer, boxed: false)
|
||||
serializeString(thumbSize, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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)])
|
||||
case .inputDocumentFileLocation(let id, let accessHash, let fileReference, let thumbSize):
|
||||
return ("inputDocumentFileLocation", [("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("thumbSize", thumbSize)])
|
||||
}
|
||||
}
|
||||
|
||||
static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Buffer?
|
||||
_3 = parseBytes(reader)
|
||||
var _4: String?
|
||||
_4 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.InputFileLocation.inputPhotoFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Buffer?
|
||||
_3 = parseBytes(reader)
|
||||
var _4: String?
|
||||
_4 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.InputFileLocation.inputDocumentFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
enum MaskCoords: TypeConstructorDescription {
|
||||
case maskCoords(n: Int32, x: Double, y: Double, zoom: Double)
|
||||
|
||||
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .maskCoords(let n, let x, let y, let zoom):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1361650766)
|
||||
}
|
||||
serializeInt32(n, buffer: buffer, boxed: false)
|
||||
serializeDouble(x, buffer: buffer, boxed: false)
|
||||
serializeDouble(y, buffer: buffer, boxed: false)
|
||||
serializeDouble(zoom, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Double?
|
||||
_2 = reader.readDouble()
|
||||
var _3: Double?
|
||||
_3 = reader.readDouble()
|
||||
var _4: Double?
|
||||
_4 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.MaskCoords.maskCoords(n: _1!, x: _2!, y: _3!, zoom: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
buffer.appendInt32(-1683841855)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt64(accessHash, buffer: buffer, boxed: false)
|
||||
serializeBytes(fileReference, buffer: buffer, boxed: false)
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
serializeString(mimeType, buffer: buffer, boxed: false)
|
||||
serializeInt32(size, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(thumbs!.count))
|
||||
for item in thumbs! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
serializeInt32(dcId, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(attributes.count))
|
||||
for item in attributes {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Int64?
|
||||
_3 = reader.readInt64()
|
||||
var _4: Buffer?
|
||||
_4 = parseBytes(reader)
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: String?
|
||||
_6 = parseString(reader)
|
||||
var _7: Int32?
|
||||
_7 = reader.readInt32()
|
||||
var _8: [Api.PhotoSize]?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self)
|
||||
} }
|
||||
var _9: Int32?
|
||||
_9 = reader.readInt32()
|
||||
var _10: [Api.DocumentAttribute]?
|
||||
if let _ = reader.readInt32() {
|
||||
_10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
let _c10 = _10 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
|
||||
return Api.Document.document(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, mimeType: _6!, size: _7!, thumbs: _8, dcId: _9!, attributes: _10!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
extension Api {
|
||||
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];
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
enum Namespaces {
|
||||
struct Peer {
|
||||
static let CloudUser: PeerId.Namespace = 0
|
||||
static let CloudGroup: PeerId.Namespace = 1
|
||||
static let CloudChannel: PeerId.Namespace = 2
|
||||
}
|
||||
}
|
||||
|
||||
struct PeerId {
|
||||
typealias Namespace = Int32
|
||||
typealias Id = Int32
|
||||
|
||||
public let namespace: Namespace
|
||||
public let id: Id
|
||||
|
||||
public init(namespace: Namespace, id: Id) {
|
||||
self.namespace = namespace
|
||||
self.id = id
|
||||
}
|
||||
|
||||
public init(_ n: Int64) {
|
||||
self.namespace = Int32((n >> 32) & 0x7fffffff)
|
||||
self.id = Int32(bitPattern: UInt32(n & 0xffffffff))
|
||||
}
|
||||
|
||||
public func toInt64() -> Int64 {
|
||||
return (Int64(self.namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: self.id)))
|
||||
}
|
||||
}
|
||||
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];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
import Foundation
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
|
||||
import BuildConfig
|
||||
import LightweightAccountData
|
||||
|
||||
struct ImageResource {
|
||||
let datacenterId: Int
|
||||
let volumeId: Int64
|
||||
let localId: Int32
|
||||
let secret: Int64
|
||||
let fileReference: Data?
|
||||
|
||||
var resourceId: String {
|
||||
return "telegram-cloud-file-\(self.datacenterId)-\(self.volumeId)-\(self.localId)-\(self.secret)"
|
||||
}
|
||||
}
|
||||
|
||||
private class Keychain: NSObject, MTKeychain {
|
||||
var dict: [String: Data] = [:]
|
||||
|
||||
func setObject(_ object: Any!, forKey aKey: String!, group: String!) {
|
||||
let data = NSKeyedArchiver.archivedData(withRootObject: object)
|
||||
self.dict[group + ":" + aKey] = data
|
||||
}
|
||||
|
||||
func object(forKey aKey: String!, group: String!) -> Any! {
|
||||
if let data = self.dict[group + ":" + aKey] {
|
||||
return NSKeyedUnarchiver.unarchiveObject(with: data as Data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeObject(forKey aKey: String!, group: String!) {
|
||||
self.dict.removeValue(forKey: group + ":" + aKey)
|
||||
}
|
||||
|
||||
func dropGroup(_ group: String!) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ParsedFile: NSObject {
|
||||
let data: Data?
|
||||
|
||||
init(data: Data?) {
|
||||
self.data = data
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
func fetchImageWithAccount(buildConfig: BuildConfig, proxyConnection: AccountProxyConnection?, account: StoredAccountInfo, inputFileLocation: Api.InputFileLocation, datacenterId: Int32, completion: @escaping (Data?) -> Void) -> () -> Void {
|
||||
MTLogSetEnabled(true)
|
||||
MTLogSetLoggingFunction({ str, args in
|
||||
//let string = NSString(format: str! as NSString, args!)
|
||||
print("MT: \(str!)")
|
||||
})
|
||||
|
||||
let serialization = Serialization()
|
||||
|
||||
var apiEnvironment = MTApiEnvironment()
|
||||
|
||||
apiEnvironment.apiId = buildConfig.apiId
|
||||
apiEnvironment.langPack = "ios"
|
||||
apiEnvironment.layer = NSNumber(value: Int(serialization.currentLayer()))
|
||||
apiEnvironment.disableUpdates = true
|
||||
apiEnvironment = apiEnvironment.withUpdatedLangPackCode("en")
|
||||
|
||||
if let proxy = proxyConnection {
|
||||
apiEnvironment = apiEnvironment.withUpdatedSocksProxySettings(MTSocksProxySettings(ip: proxy.host, port: UInt16(clamping: proxy.port), username: proxy.username, password: proxy.password, secret: proxy.secret))
|
||||
}
|
||||
|
||||
let context = MTContext(serialization: serialization, apiEnvironment: apiEnvironment, isTestingEnvironment: account.isTestingEnvironment, useTempAuthKeys: false)!
|
||||
|
||||
let seedAddressList: [Int: [String]]
|
||||
|
||||
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 (id, ips) in seedAddressList {
|
||||
context.setSeedAddressSetForDatacenterWithId(id, seedAddressSet: MTDatacenterAddressSet(addressList: ips.map { MTDatacenterAddress(ip: $0, port: 443, preferForMedia: false, restrictToTcp: false, cdn: false, preferForProxy: false, secret: nil) }))
|
||||
}
|
||||
|
||||
let keychain = Keychain()
|
||||
context.keychain = keychain
|
||||
|
||||
context.performBatchUpdates({
|
||||
for (id, info) in account.datacenters {
|
||||
if !info.addressList.isEmpty {
|
||||
var addressList: [MTDatacenterAddress] = []
|
||||
for address in info.addressList {
|
||||
addressList.append(MTDatacenterAddress(ip: address.host, port: UInt16(clamping: address.port), preferForMedia: address.isMedia, restrictToTcp: false, cdn: false, preferForProxy: false, secret: address.secret))
|
||||
}
|
||||
context.updateAddressSetForDatacenter(withId: Int(id), addressSet: MTDatacenterAddressSet(addressList: addressList), forceUpdateSchemes: true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for (id, info) in account.datacenters {
|
||||
context.updateAuthInfoForDatacenter(withId: Int(id), authInfo: MTDatacenterAuthInfo(authKey: info.masterKey.data, authKeyId: info.masterKey.id, saltSet: [], authKeyAttributes: [:], mainTempAuthKey: nil, mediaTempAuthKey: nil))
|
||||
}
|
||||
|
||||
let mtProto = MTProto(context: context, datacenterId: Int(datacenterId), usageCalculationInfo: nil)!
|
||||
mtProto.useTempAuthKeys = context.useTempAuthKeys
|
||||
mtProto.checkForProxyConnectionIssues = false
|
||||
|
||||
let requestService = MTRequestMessageService(context: context)!
|
||||
mtProto.add(requestService)
|
||||
|
||||
let request = MTRequest()
|
||||
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-475607115) //upload.getFile
|
||||
Api.serializeObject(inputFileLocation, buffer: buffer, boxed: true)
|
||||
|
||||
buffer.appendInt32(0)
|
||||
buffer.appendInt32(32 * 1024)
|
||||
|
||||
request.setPayload(buffer.makeData(), metadata: "getFile", shortMetadata: "getFile", responseParser: { response in
|
||||
let reader = BufferReader(Buffer(data: response))
|
||||
guard let signature = reader.readInt32() else {
|
||||
return ParsedFile(data: nil)
|
||||
}
|
||||
guard signature == 157948117 else {
|
||||
return ParsedFile(data: nil)
|
||||
}
|
||||
reader.skip(4) //type
|
||||
reader.skip(4) //mtime
|
||||
guard let bytes = parseBytes(reader) else {
|
||||
return ParsedFile(data: nil)
|
||||
}
|
||||
return ParsedFile(data: bytes.makeData())
|
||||
})
|
||||
|
||||
request.dependsOnPasswordEntry = false
|
||||
request.shouldContinueExecutionWithErrorContext = { errorContext in
|
||||
guard let _ = errorContext else {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
request.completed = { (boxedResponse, timestamp, error) -> () in
|
||||
if let _ = error {
|
||||
completion(nil)
|
||||
} else {
|
||||
if let result = boxedResponse as? ParsedFile {
|
||||
completion(result.data)
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestService.add(request)
|
||||
mtProto.resume()
|
||||
|
||||
let internalId = request.internalId
|
||||
return {
|
||||
requestService.removeRequest(byInternalId: 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>
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
enum ManagedFileMode {
|
||||
case read
|
||||
case readwrite
|
||||
case append
|
||||
}
|
||||
|
||||
private func wrappedWrite(_ fd: Int32, _ data: UnsafeRawPointer, _ count: Int) -> Int {
|
||||
return write(fd, data, count)
|
||||
}
|
||||
|
||||
private func wrappedRead(_ fd: Int32, _ data: UnsafeMutableRawPointer, _ count: Int) -> Int {
|
||||
return read(fd, data, count)
|
||||
}
|
||||
|
||||
final class ManagedFile {
|
||||
private let fd: Int32
|
||||
private let mode: ManagedFileMode
|
||||
|
||||
init?(path: String, mode: ManagedFileMode) {
|
||||
self.mode = mode
|
||||
let fileMode: Int32
|
||||
let accessMode: UInt16
|
||||
switch mode {
|
||||
case .read:
|
||||
fileMode = O_RDONLY
|
||||
accessMode = S_IRUSR
|
||||
case .readwrite:
|
||||
fileMode = O_RDWR | O_CREAT
|
||||
accessMode = S_IRUSR | S_IWUSR
|
||||
case .append:
|
||||
fileMode = O_WRONLY | O_CREAT | O_APPEND
|
||||
accessMode = S_IRUSR | S_IWUSR
|
||||
}
|
||||
let fd = open(path, fileMode, accessMode)
|
||||
if fd >= 0 {
|
||||
self.fd = fd
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
close(self.fd)
|
||||
}
|
||||
|
||||
func write(_ data: UnsafeRawPointer, count: Int) -> Int {
|
||||
return wrappedWrite(self.fd, data, count)
|
||||
}
|
||||
|
||||
func read(_ data: UnsafeMutableRawPointer, _ count: Int) -> Int {
|
||||
return wrappedRead(self.fd, data, count)
|
||||
}
|
||||
|
||||
func readData(count: Int) -> Data {
|
||||
var result = Data(count: count)
|
||||
result.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
|
||||
let readCount = self.read(bytes, count)
|
||||
assert(readCount == count)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func seek(position: Int64) {
|
||||
lseek(self.fd, position, SEEK_SET)
|
||||
}
|
||||
|
||||
func truncate(count: Int64) {
|
||||
ftruncate(self.fd, count)
|
||||
}
|
||||
|
||||
func getSize() -> Int? {
|
||||
var value = stat()
|
||||
if fstat(self.fd, &value) == 0 {
|
||||
return Int(value.st_size)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func sync() {
|
||||
fsync(self.fd)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -1,538 +0,0 @@
|
|||
import Foundation
|
||||
import UserNotifications
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
import WebP
|
||||
import BuildConfig
|
||||
import LightweightAccountData
|
||||
|
||||
private var sharedLogger: Logger?
|
||||
|
||||
private final class Logger {
|
||||
private let maxLength: Int = 2 * 1024 * 1024
|
||||
private let maxFiles: Int = 20
|
||||
|
||||
private let basePath: String
|
||||
private var file: (ManagedFile, Int)?
|
||||
|
||||
var logToFile: Bool = true
|
||||
var logToConsole: Bool = true
|
||||
|
||||
public static func setSharedLogger(_ logger: Logger) {
|
||||
sharedLogger = logger
|
||||
}
|
||||
|
||||
public static var shared: Logger {
|
||||
if let sharedLogger = sharedLogger {
|
||||
return sharedLogger
|
||||
} else {
|
||||
assertionFailure()
|
||||
let tempLogger = Logger(basePath: "")
|
||||
tempLogger.logToFile = false
|
||||
tempLogger.logToConsole = false
|
||||
return tempLogger
|
||||
}
|
||||
}
|
||||
|
||||
public init(basePath: String) {
|
||||
self.basePath = basePath
|
||||
//self.logToConsole = false
|
||||
}
|
||||
|
||||
public func log(_ tag: String, _ what: @autoclosure () -> String) {
|
||||
if !self.logToFile && !self.logToConsole {
|
||||
return
|
||||
}
|
||||
|
||||
let string = what()
|
||||
|
||||
var rawTime = time_t()
|
||||
time(&rawTime)
|
||||
var timeinfo = tm()
|
||||
localtime_r(&rawTime, &timeinfo)
|
||||
|
||||
var curTime = timeval()
|
||||
gettimeofday(&curTime, nil)
|
||||
let milliseconds = curTime.tv_usec / 1000
|
||||
|
||||
var consoleContent: String?
|
||||
if self.logToConsole {
|
||||
let content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string])
|
||||
consoleContent = content
|
||||
print(content)
|
||||
}
|
||||
|
||||
if self.logToFile {
|
||||
let content: String
|
||||
if let consoleContent = consoleContent {
|
||||
content = consoleContent
|
||||
} else {
|
||||
content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string])
|
||||
}
|
||||
|
||||
var currentFile: ManagedFile?
|
||||
var openNew = false
|
||||
if let (file, length) = self.file {
|
||||
if length >= self.maxLength {
|
||||
self.file = nil
|
||||
openNew = true
|
||||
} else {
|
||||
currentFile = file
|
||||
}
|
||||
} else {
|
||||
openNew = true
|
||||
}
|
||||
if openNew {
|
||||
let _ = try? FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
var createNew = false
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
|
||||
var minCreationDate: (Date, URL)?
|
||||
var maxCreationDate: (Date, URL)?
|
||||
var count = 0
|
||||
for url in files {
|
||||
if url.lastPathComponent.hasPrefix("log-") {
|
||||
if let values = try? url.resourceValues(forKeys: Set([URLResourceKey.creationDateKey])), let creationDate = values.creationDate {
|
||||
count += 1
|
||||
if minCreationDate == nil || minCreationDate!.0 > creationDate {
|
||||
minCreationDate = (creationDate, url)
|
||||
}
|
||||
if maxCreationDate == nil || maxCreationDate!.0 < creationDate {
|
||||
maxCreationDate = (creationDate, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (_, url) = minCreationDate, count >= self.maxFiles {
|
||||
let _ = try? FileManager.default.removeItem(at: url)
|
||||
}
|
||||
if let (_, url) = maxCreationDate {
|
||||
var value = stat()
|
||||
if stat(url.path, &value) == 0 && Int(value.st_size) < self.maxLength {
|
||||
if let file = ManagedFile(path: url.path, mode: .append) {
|
||||
self.file = (file, Int(value.st_size))
|
||||
currentFile = file
|
||||
}
|
||||
} else {
|
||||
createNew = true
|
||||
}
|
||||
} else {
|
||||
createNew = true
|
||||
}
|
||||
}
|
||||
|
||||
if createNew {
|
||||
let fileName = String(format: "log-%d-%d-%d_%02d-%02d-%02d.%03d.txt", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds)])
|
||||
|
||||
let path = self.basePath + "/" + fileName
|
||||
|
||||
if let file = ManagedFile(path: path, mode: .append) {
|
||||
self.file = (file, 0)
|
||||
currentFile = file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentFile = currentFile {
|
||||
if let data = content.data(using: .utf8) {
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
let _ = currentFile.write(bytes, count: data.count)
|
||||
}
|
||||
var newline: UInt8 = 0x0a
|
||||
let _ = currentFile.write(&newline, count: 1)
|
||||
if let file = self.file {
|
||||
self.file = (file.0, file.1 + data.count + 1)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseBase64(string: String) -> Data? {
|
||||
var string = string
|
||||
string = string.replacingOccurrences(of: "-", with: "+")
|
||||
string = string.replacingOccurrences(of: "_", with: "/")
|
||||
while string.count % 4 != 0 {
|
||||
string.append("=")
|
||||
}
|
||||
return Data(base64Encoded: string)
|
||||
}
|
||||
|
||||
enum ParsedMediaAttachment {
|
||||
case document(Api.Document)
|
||||
case photo(Api.Photo)
|
||||
}
|
||||
|
||||
private func parseAttachment(data: Data) -> (ParsedMediaAttachment, Data)? {
|
||||
let reader = BufferReader(Buffer(data: data))
|
||||
guard let initialSignature = reader.readInt32() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let buffer: Buffer
|
||||
if initialSignature == 0x3072cfa1 {
|
||||
guard let bytes = parseBytes(reader) else {
|
||||
return nil
|
||||
}
|
||||
guard let decompressedData = MTGzip.decompress(bytes.makeData()) else {
|
||||
return nil
|
||||
}
|
||||
buffer = Buffer(data: decompressedData)
|
||||
} else {
|
||||
buffer = Buffer(data: data)
|
||||
}
|
||||
|
||||
if let result = Api.parse(buffer) {
|
||||
if let photo = result as? Api.Photo {
|
||||
return (.photo(photo), buffer.makeData())
|
||||
} else if let document = result as? Api.Document {
|
||||
return (.document(document), buffer.makeData())
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func photoSizeDimensions(_ size: Api.PhotoSize) -> CGSize? {
|
||||
switch size {
|
||||
case let .photoSize(_, _, w, h, _):
|
||||
return CGSize(width: CGFloat(w), height: CGFloat(h))
|
||||
case let .photoCachedSize(_, _, w, h, _):
|
||||
return CGSize(width: CGFloat(w), height: CGFloat(h))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func photoDimensions(_ photo: Api.Photo) -> CGSize? {
|
||||
switch photo {
|
||||
case let .photo(_, _, _, _, _, sizes, _):
|
||||
for size in sizes.reversed() {
|
||||
if let dimensions = photoSizeDimensions(size) {
|
||||
return dimensions
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case .photoEmpty:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func photoSizes(_ photo: Api.Photo) -> [Api.PhotoSize] {
|
||||
switch photo {
|
||||
case let .photo(_, _, _, _, _, sizes, _):
|
||||
return sizes
|
||||
case .photoEmpty:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
private let rootPath: String?
|
||||
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
var cancelFetch: (() -> Void)?
|
||||
|
||||
override init() {
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
if let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) {
|
||||
let appGroupName = "group.\(appBundleIdentifier[..<lastDotRange.lowerBound])"
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
if let appGroupUrl = maybeAppGroupUrl {
|
||||
let rootPath = appGroupUrl.path + "/telegram-data"
|
||||
self.rootPath = rootPath
|
||||
|
||||
if sharedLogger == nil {
|
||||
let logsPath = rootPath + "/notificationServiceLogs"
|
||||
Logger.setSharedLogger(Logger(basePath: logsPath))
|
||||
}
|
||||
} else {
|
||||
self.rootPath = nil
|
||||
preconditionFailure()
|
||||
}
|
||||
} else {
|
||||
self.rootPath = nil
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
guard let rootPath = self.rootPath else {
|
||||
contentHandler(request.content)
|
||||
return
|
||||
}
|
||||
let accountInfos = self.rootPath.flatMap({ rootPath in
|
||||
loadAccountsData(rootPath: rootPath)
|
||||
}) ?? StoredAccountInfos(proxy: nil, accounts: [])
|
||||
|
||||
self.contentHandler = contentHandler
|
||||
self.bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
|
||||
|
||||
var encryptedData: Data?
|
||||
if let encryptedPayload = request.content.userInfo["p"] as? String {
|
||||
encryptedData = parseBase64(string: encryptedPayload)
|
||||
}
|
||||
|
||||
Logger.shared.log("NotificationService", "received notification \(request), parsed encryptedData \(String(describing: encryptedData))")
|
||||
|
||||
if let (account, dict) = encryptedData.flatMap({ decryptedNotificationPayload(accounts: accountInfos.accounts, data: $0) }) {
|
||||
Logger.shared.log("NotificationService", "decrypted notification")
|
||||
var userInfo = self.bestAttemptContent?.userInfo ?? [:]
|
||||
userInfo["accountId"] = account.id
|
||||
|
||||
var peerId: PeerId?
|
||||
var messageId: Int32?
|
||||
var silent = false
|
||||
|
||||
if let msgId = dict["msg_id"] as? String {
|
||||
userInfo["msg_id"] = msgId
|
||||
messageId = Int32(msgId)
|
||||
}
|
||||
if let fromId = dict["from_id"] as? String {
|
||||
userInfo["from_id"] = fromId
|
||||
if let id = Int32(fromId) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: id)
|
||||
}
|
||||
}
|
||||
if let chatId = dict["chat_id"] as? String {
|
||||
userInfo["chat_id"] = chatId
|
||||
if let id = Int32(chatId) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: id)
|
||||
}
|
||||
}
|
||||
if let channelId = dict["channel_id"] as? String {
|
||||
userInfo["channel_id"] = channelId
|
||||
if let id = Int32(channelId) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: id)
|
||||
}
|
||||
}
|
||||
if let silentValue = dict["silent"] as? String {
|
||||
silent = silentValue == "1"
|
||||
}
|
||||
|
||||
var attachment: ParsedMediaAttachment?
|
||||
var attachmentData: Data?
|
||||
if let attachmentDataString = dict["attachb64"] as? String, let attachmentDataValue = parseBase64(string: attachmentDataString) {
|
||||
if let value = parseAttachment(data: attachmentDataValue) {
|
||||
attachment = value.0
|
||||
attachmentData = value.1
|
||||
}
|
||||
}
|
||||
|
||||
let imagesPath = NSTemporaryDirectory() + "aps-data"
|
||||
let _ = try? FileManager.default.createDirectory(atPath: imagesPath, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
let accountBasePath = rootPath + "/account-\(UInt64(bitPattern: account.id))"
|
||||
|
||||
let mediaBoxPath = accountBasePath + "/postbox/media"
|
||||
|
||||
var tempImagePath: String?
|
||||
var mediaBoxThumbnailImagePath: String?
|
||||
|
||||
var inputFileLocation: (Int32, Api.InputFileLocation)?
|
||||
var fetchResourceId: String?
|
||||
var isPng = false
|
||||
var isExpandableMedia = false
|
||||
|
||||
if let attachment = attachment {
|
||||
switch attachment {
|
||||
case let .photo(photo):
|
||||
switch photo {
|
||||
case let .photo(_, id, accessHash, fileReference, _, sizes, dcId):
|
||||
isExpandableMedia = true
|
||||
loop: for size in sizes {
|
||||
switch size {
|
||||
case let .photoSize(type, _, _, _, _):
|
||||
if type == "m" {
|
||||
inputFileLocation = (dcId, .inputPhotoFileLocation(id: id, accessHash: accessHash, fileReference: fileReference, thumbSize: type))
|
||||
fetchResourceId = "telegram-cloud-photo-size-\(dcId)-\(id)-\(type)"
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
case .photoEmpty:
|
||||
break
|
||||
}
|
||||
case let .document(document):
|
||||
switch document {
|
||||
case let .document(_, id, accessHash, fileReference, _, mimeType, _, thumbs, dcId, attributes):
|
||||
var isSticker = false
|
||||
for attribute in attributes {
|
||||
switch attribute {
|
||||
case .documentAttributeSticker:
|
||||
isSticker = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
let isAnimatedSticker = mimeType == "application/x-tgsticker"
|
||||
if isSticker || isAnimatedSticker {
|
||||
isExpandableMedia = true
|
||||
}
|
||||
if let thumbs = thumbs {
|
||||
loop: for size in thumbs {
|
||||
switch size {
|
||||
case let .photoSize(type, _, _, _, _):
|
||||
if (isSticker && type == "s") || type == "m" {
|
||||
if isSticker {
|
||||
isPng = true
|
||||
}
|
||||
inputFileLocation = (dcId, .inputDocumentFileLocation(id: id, accessHash: accessHash, fileReference: fileReference, thumbSize: type))
|
||||
fetchResourceId = "telegram-cloud-document-size-\(dcId)-\(id)-\(type)"
|
||||
break loop
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let fetchResourceId = fetchResourceId {
|
||||
tempImagePath = imagesPath + "/\(fetchResourceId).\(isPng ? "png" : "jpg")"
|
||||
mediaBoxThumbnailImagePath = mediaBoxPath + "/\(fetchResourceId)"
|
||||
}
|
||||
|
||||
if let aps = dict["aps"] as? [AnyHashable: Any] {
|
||||
if let alert = aps["alert"] as? String {
|
||||
self.bestAttemptContent?.title = ""
|
||||
self.bestAttemptContent?.body = alert
|
||||
} else if let alert = aps["alert"] as? [AnyHashable: Any] {
|
||||
self.bestAttemptContent?.title = alert["title"] as? String ?? ""
|
||||
if let title = self.bestAttemptContent?.title, !title.isEmpty && silent {
|
||||
self.bestAttemptContent?.title = "\(title) 🔕"
|
||||
}
|
||||
self.bestAttemptContent?.subtitle = alert["subtitle"] as? String ?? ""
|
||||
self.bestAttemptContent?.body = alert["body"] as? String ?? ""
|
||||
}
|
||||
|
||||
if accountInfos.accounts.count > 1 {
|
||||
if let title = self.bestAttemptContent?.title, !title.isEmpty, !account.peerName.isEmpty {
|
||||
self.bestAttemptContent?.title = "\(title) → \(account.peerName)"
|
||||
}
|
||||
}
|
||||
|
||||
if let threadId = aps["thread-id"] as? String {
|
||||
self.bestAttemptContent?.threadIdentifier = threadId
|
||||
}
|
||||
if let sound = aps["sound"] as? String {
|
||||
self.bestAttemptContent?.sound = UNNotificationSound(named: UNNotificationSoundName(sound))
|
||||
}
|
||||
if let category = aps["category"] as? String {
|
||||
self.bestAttemptContent?.categoryIdentifier = category
|
||||
if let peerId = peerId, let messageId = messageId, let _ = attachment, let attachmentData = attachmentData {
|
||||
userInfo["peerId"] = peerId.toInt64()
|
||||
userInfo["messageId.namespace"] = 0 as Int32
|
||||
userInfo["messageId.id"] = messageId
|
||||
|
||||
userInfo["media"] = attachmentData.base64EncodedString()
|
||||
|
||||
if isExpandableMedia {
|
||||
if category == "r" {
|
||||
self.bestAttemptContent?.categoryIdentifier = "withReplyMedia"
|
||||
} else if category == "m" {
|
||||
self.bestAttemptContent?.categoryIdentifier = "withMuteMedia"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.bestAttemptContent?.userInfo = userInfo
|
||||
|
||||
self.cancelFetch?()
|
||||
if let mediaBoxThumbnailImagePath = mediaBoxThumbnailImagePath, let tempImagePath = tempImagePath, let (datacenterId, inputFileLocation) = inputFileLocation {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxThumbnailImagePath)) {
|
||||
var tempData = data
|
||||
if isPng {
|
||||
if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() {
|
||||
tempData = imageData
|
||||
}
|
||||
}
|
||||
if let _ = try? tempData.write(to: URL(fileURLWithPath: tempImagePath)) {
|
||||
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: tempImagePath)) {
|
||||
self.bestAttemptContent?.attachments = [attachment]
|
||||
}
|
||||
}
|
||||
if let bestAttemptContent = self.bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
} else {
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
return
|
||||
}
|
||||
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||
|
||||
self.cancelFetch = fetchImageWithAccount(buildConfig: buildConfig, proxyConnection: accountInfos.proxy, account: account, inputFileLocation: inputFileLocation, datacenterId: datacenterId, completion: { [weak self] data in
|
||||
DispatchQueue.main.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.cancelFetch?()
|
||||
strongSelf.cancelFetch = nil
|
||||
if let data = data {
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: mediaBoxThumbnailImagePath))
|
||||
var tempData = data
|
||||
if isPng {
|
||||
if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() {
|
||||
tempData = imageData
|
||||
}
|
||||
}
|
||||
if let _ = try? tempData.write(to: URL(fileURLWithPath: tempImagePath)) {
|
||||
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: tempImagePath)) {
|
||||
strongSelf.bestAttemptContent?.attachments = [attachment]
|
||||
}
|
||||
}
|
||||
}
|
||||
if let bestAttemptContent = strongSelf.bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if let bestAttemptContent = self.bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("NotificationService", "couldn't decrypt notification")
|
||||
|
||||
if let bestAttemptContent = self.bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
Logger.shared.log("NotificationService", "serviceExtensionTimeWillExpire")
|
||||
|
||||
self.cancelFetch?()
|
||||
self.cancelFetch = nil
|
||||
|
||||
if let contentHandler = self.contentHandler, let bestAttemptContent = self.bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,310 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
class Buffer: CustomStringConvertible {
|
||||
var data: UnsafeMutableRawPointer?
|
||||
var _size: UInt = 0
|
||||
private var capacity: UInt = 0
|
||||
private let freeWhenDone: Bool
|
||||
|
||||
var size: Int {
|
||||
return Int(self._size)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if self.freeWhenDone {
|
||||
free(self.data)
|
||||
}
|
||||
}
|
||||
|
||||
init(memory: UnsafeMutableRawPointer?, size: Int, capacity: Int, freeWhenDone: Bool) {
|
||||
self.data = memory
|
||||
self._size = UInt(size)
|
||||
self.capacity = UInt(capacity)
|
||||
self.freeWhenDone = freeWhenDone
|
||||
}
|
||||
|
||||
init() {
|
||||
self.data = nil
|
||||
self._size = 0
|
||||
self.capacity = 0
|
||||
self.freeWhenDone = true
|
||||
}
|
||||
|
||||
convenience init(data: Data?) {
|
||||
self.init()
|
||||
|
||||
if let data = data {
|
||||
data.withUnsafeBytes { bytes in
|
||||
self.appendBytes(bytes, length: UInt(data.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func makeData() -> Data {
|
||||
return self.withUnsafeMutablePointer { pointer, size -> Data in
|
||||
if let pointer = pointer {
|
||||
return Data(bytes: pointer.assumingMemoryBound(to: UInt8.self), count: Int(size))
|
||||
} else {
|
||||
return Data()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
get {
|
||||
var string = ""
|
||||
if let data = self.data {
|
||||
var i: UInt = 0
|
||||
let bytes = data.assumingMemoryBound(to: UInt8.self)
|
||||
while i < _size && i < 8 {
|
||||
string += String(format: "%02x", Int(bytes.advanced(by: Int(i)).pointee))
|
||||
i += 1
|
||||
}
|
||||
if i < _size {
|
||||
string += "...\(_size)b"
|
||||
}
|
||||
} else {
|
||||
string += "<null>"
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
func appendBytes(_ bytes: UnsafeRawPointer, length: UInt) {
|
||||
if self.capacity < self._size + length {
|
||||
self.capacity = self._size + length + 128
|
||||
if self.data == nil {
|
||||
self.data = malloc(Int(self.capacity))!
|
||||
}
|
||||
else {
|
||||
self.data = realloc(self.data, Int(self.capacity))!
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(self.data?.advanced(by: Int(self._size)), bytes, Int(length))
|
||||
self._size += length
|
||||
}
|
||||
|
||||
func appendBuffer(_ buffer: Buffer) {
|
||||
if self.capacity < self._size + buffer._size {
|
||||
self.capacity = self._size + buffer._size + 128
|
||||
if self.data == nil {
|
||||
self.data = malloc(Int(self.capacity))!
|
||||
}
|
||||
else {
|
||||
self.data = realloc(self.data, Int(self.capacity))!
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(self.data?.advanced(by: Int(self._size)), buffer.data, Int(buffer._size))
|
||||
}
|
||||
|
||||
func appendInt32(_ value: Int32) {
|
||||
var v = value
|
||||
self.appendBytes(&v, length: 4)
|
||||
}
|
||||
|
||||
func appendInt64(_ value: Int64) {
|
||||
var v = value
|
||||
self.appendBytes(&v, length: 8)
|
||||
}
|
||||
|
||||
func appendDouble(_ value: Double) {
|
||||
var v = value
|
||||
self.appendBytes(&v, length: 8)
|
||||
}
|
||||
|
||||
func withUnsafeMutablePointer<R>(_ f: (UnsafeMutableRawPointer?, UInt) -> R) -> R {
|
||||
return f(self.data, self._size)
|
||||
}
|
||||
}
|
||||
|
||||
class BufferReader {
|
||||
private let buffer: Buffer
|
||||
private(set) var offset: UInt = 0
|
||||
|
||||
init(_ buffer: Buffer) {
|
||||
self.buffer = buffer
|
||||
}
|
||||
|
||||
func reset() {
|
||||
self.offset = 0
|
||||
}
|
||||
|
||||
func skip(_ count: Int) {
|
||||
self.offset = min(self.buffer._size, self.offset + UInt(count))
|
||||
}
|
||||
|
||||
func readInt32() -> Int32? {
|
||||
if self.offset + 4 <= self.buffer._size {
|
||||
let value: Int32 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int32.self).pointee
|
||||
self.offset += 4
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readInt64() -> Int64? {
|
||||
if self.offset + 8 <= self.buffer._size {
|
||||
let value: Int64 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int64.self).pointee
|
||||
self.offset += 8
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readDouble() -> Double? {
|
||||
if self.offset + 8 <= self.buffer._size {
|
||||
let value: Double = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Double.self).pointee
|
||||
self.offset += 8
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBytesAsInt32(_ count: Int) -> Int32? {
|
||||
if count == 0 {
|
||||
return 0
|
||||
}
|
||||
else if count > 0 && count <= 4 || self.offset + UInt(count) <= self.buffer._size {
|
||||
var value: Int32 = 0
|
||||
memcpy(&value, self.buffer.data?.advanced(by: Int(self.offset)), count)
|
||||
self.offset += UInt(count)
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBuffer(_ count: Int) -> Buffer? {
|
||||
if count >= 0 && self.offset + UInt(count) <= self.buffer._size {
|
||||
let buffer = Buffer()
|
||||
buffer.appendBytes((self.buffer.data?.advanced(by: Int(self.offset)))!, length: UInt(count))
|
||||
self.offset += UInt(count)
|
||||
return buffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func withReadBufferNoCopy<T>(_ count: Int, _ f: (Buffer) -> T) -> T? {
|
||||
if count >= 0 && self.offset + UInt(count) <= self.buffer._size {
|
||||
return f(Buffer(memory: self.buffer.data!.advanced(by: Int(self.offset)), size: count, capacity: count, freeWhenDone: false))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func roundUp(_ numToRound: Int, multiple: Int) -> Int {
|
||||
if multiple == 0 {
|
||||
return numToRound
|
||||
}
|
||||
|
||||
let remainder = numToRound % multiple
|
||||
if remainder == 0 {
|
||||
return numToRound
|
||||
}
|
||||
|
||||
return numToRound + multiple - remainder
|
||||
}
|
||||
|
||||
func parseBytes(_ reader: BufferReader) -> Buffer? {
|
||||
if let tmp = reader.readBytesAsInt32(1) {
|
||||
var paddingBytes: Int = 0
|
||||
var length: Int = 0
|
||||
if tmp == 254 {
|
||||
if let len = reader.readBytesAsInt32(3) {
|
||||
length = Int(len)
|
||||
paddingBytes = roundUp(length, multiple: 4) - length
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
else {
|
||||
length = Int(tmp)
|
||||
paddingBytes = roundUp(length + 1, multiple: 4) - (length + 1)
|
||||
}
|
||||
|
||||
let buffer = reader.readBuffer(length)
|
||||
reader.skip(paddingBytes)
|
||||
return buffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseString(_ reader: BufferReader) -> String? {
|
||||
if let buffer = parseBytes(reader) {
|
||||
return (NSString(data: buffer.makeData() as Data, encoding: String.Encoding.utf8.rawValue) as? String) ?? ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
protocol TypeConstructorDescription {
|
||||
func descriptionFields() -> (String, [(String, Any)])
|
||||
}
|
||||
|
||||
func serializeInt32(_ value: Int32, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(-1471112230)
|
||||
}
|
||||
buffer.appendInt32(value)
|
||||
}
|
||||
|
||||
func serializeInt64(_ value: Int64, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(570911930)
|
||||
}
|
||||
buffer.appendInt64(value)
|
||||
}
|
||||
|
||||
func serializeDouble(_ value: Double, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(571523412)
|
||||
}
|
||||
buffer.appendDouble(value)
|
||||
}
|
||||
|
||||
func serializeString(_ value: String, buffer: Buffer, boxed: Bool) {
|
||||
let stringBuffer = Buffer()
|
||||
let data = value.data(using: .utf8, allowLossyConversion: true) ?? Data()
|
||||
data.withUnsafeBytes { bytes in
|
||||
stringBuffer.appendBytes(bytes, length: UInt(data.count))
|
||||
}
|
||||
serializeBytes(stringBuffer, buffer: buffer, boxed: boxed)
|
||||
}
|
||||
|
||||
func serializeBytes(_ value: Buffer, buffer: Buffer, boxed: Bool) {
|
||||
if boxed {
|
||||
buffer.appendInt32(-1255641564)
|
||||
}
|
||||
|
||||
var length: Int32 = Int32(value.size)
|
||||
var padding: Int32 = 0
|
||||
if (length >= 254)
|
||||
{
|
||||
var tmp: UInt8 = 254
|
||||
buffer.appendBytes(&tmp, length: 1)
|
||||
buffer.appendBytes(&length, length: 3)
|
||||
padding = (((length % 4) == 0 ? length : (length + 4 - (length % 4)))) - length;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.appendBytes(&length, length: 1)
|
||||
|
||||
let e1 = (((length + 1) % 4) == 0 ? (length + 1) : ((length + 1) + 4 - ((length + 1) % 4)))
|
||||
padding = (e1) - (length + 1)
|
||||
}
|
||||
|
||||
if value.size != 0 {
|
||||
buffer.appendBytes(value.data!, length: UInt(length))
|
||||
}
|
||||
|
||||
var i: Int32 = 0
|
||||
var tmp: UInt8 = 0
|
||||
while i < padding {
|
||||
buffer.appendBytes(&tmp, length: 1)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import Foundation
|
||||
#if BUCK
|
||||
import MtProtoKit
|
||||
#else
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
|
||||
public class BoxedMessage: NSObject {
|
||||
public let body: Any
|
||||
public init(_ body: Any) {
|
||||
self.body = body
|
||||
}
|
||||
}
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 105
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func exportAuthorization(_ datacenterId: Int32, data: AutoreleasingUnsafeMutablePointer<NSData?>) -> MTExportAuthorizationResponseParser!
|
||||
{
|
||||
return { data -> MTExportedAuthorizationData? in
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func importAuthorization(_ authId: Int32, bytes: Data!) -> Data! {
|
||||
return Data()
|
||||
}
|
||||
|
||||
public func requestDatacenterAddress(with data: AutoreleasingUnsafeMutablePointer<NSData?>) -> MTRequestDatacenterAddressListParser! {
|
||||
return { response -> MTDatacenterAddressListData? in
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func requestNoop(_ data: AutoreleasingUnsafeMutablePointer<NSData?>!) -> MTRequestNoopParser! {
|
||||
return { response -> AnyObject? in
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ class ShareRootController: UIViewController {
|
|||
|
||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||
|
||||
let apiId: Int32 = buildConfig.apiId
|
||||
let languagesCategory = "ios"
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -904,8 +901,6 @@
|
|||
D00859B71B28189D00EAF753 /* Telegram_iOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telegram_iOSTests.swift; sourceTree = "<group>"; };
|
||||
D00ED7591FE94630001F38BD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = en.lproj/AppIntentVocabulary.plist; sourceTree = "<group>"; };
|
||||
D00ED75C1FE95287001F38BD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D015E010225CCEB300CB9E8A /* ReadBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadBuffer.swift; sourceTree = "<group>"; };
|
||||
D015E01E225CDF5000CB9E8A /* Api0.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Api0.swift; sourceTree = "<group>"; };
|
||||
D015E04C225D2D8F00CB9E8A /* WebP.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WebP.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D01A47521F4DBEB100383CC1 /* libHockeySDK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libHockeySDK.a; path = "../../build/HockeySDK-iOS/Support/build/Debug-iphoneos/libHockeySDK.a"; sourceTree = "<group>"; };
|
||||
D01A47541F4DBED700383CC1 /* HockeySDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = HockeySDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
|
@ -940,7 +935,6 @@
|
|||
D03B0E951D637A0500955575 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D03BCCC91C6EBD670097A291 /* ListViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewTests.swift; sourceTree = "<group>"; };
|
||||
D0400ED81D5B8F97007931CE /* TelegramUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TelegramUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0400EE41D5B912E007931CE /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
D0400EE61D5B912E007931CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D04DCC0B1F71C80000B021D7 /* 0.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = 0.m4a; sourceTree = "<group>"; };
|
||||
D04DCC0C1F71C80000B021D7 /* 1.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = 1.m4a; sourceTree = "<group>"; };
|
||||
|
|
@ -1017,7 +1011,6 @@
|
|||
D06706601D51185400DED3E3 /* TelegramCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TelegramCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D073E51E21FF7CE900742DDD /* Crypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Crypto.h; sourceTree = "<group>"; };
|
||||
D073E51F21FF7CE900742DDD /* Crypto.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Crypto.m; sourceTree = "<group>"; };
|
||||
D073E52122003E1E00742DDD /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
|
||||
D079FD001F06BBD10038FADE /* Telegram-iOS-AppStore.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Telegram-iOS-AppStore.entitlements"; sourceTree = "<group>"; };
|
||||
D08410471FABDC7A008FFE92 /* SSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D08410491FABDCF2008FFE92 /* LegacyComponents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LegacyComponents.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
|
@ -1060,6 +1053,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; };
|
||||
|
|
@ -1105,7 +1110,6 @@
|
|||
D0D268881D79A70A00C422DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D0D268971D79AF1B00C422DA /* SiriIntents-AppStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SiriIntents-AppStore.entitlements"; sourceTree = "<group>"; };
|
||||
D0D268981D79AF3900C422DA /* SiriIntentsUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SiriIntentsUI.entitlements; sourceTree = "<group>"; };
|
||||
D0E2CE632227F0680084E3DD /* ManagedFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedFile.swift; sourceTree = "<group>"; };
|
||||
D0E3A7071B285B5000A402D9 /* Telegram-iOS-Hockeyapp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Telegram-iOS-Hockeyapp.entitlements"; sourceTree = "<group>"; };
|
||||
D0E41A381D65A69C00FBFC00 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; };
|
||||
D0E41A3B1D65A69C00FBFC00 /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1124,10 +1128,7 @@
|
|||
D0ECCB7E1FE9C38500609802 /* Telegram_iOS_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telegram_iOS_UITests.swift; sourceTree = "<group>"; };
|
||||
D0ECCB801FE9C38500609802 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D0ECCB891FE9C4AC00609802 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = "<group>"; };
|
||||
D0ED633921FF3EDF001D4648 /* AccountData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountData.swift; sourceTree = "<group>"; };
|
||||
D0ED633C21FF3F28001D4648 /* NotificationService-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NotificationService-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
D0ED633E21FF46E4001D4648 /* ImageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageData.swift; sourceTree = "<group>"; };
|
||||
D0ED634021FF4786001D4648 /* Serialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Serialization.swift; sourceTree = "<group>"; };
|
||||
D0F575122083B96B00F1C1E1 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
|
||||
D0FC1947201D2DA700FEDBB2 /* SFCompactRounded-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SFCompactRounded-Semibold.otf"; path = "Telegram-iOS/Resources/SFCompactRounded-Semibold.otf"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
|
@ -1148,7 +1149,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,22 +2157,26 @@
|
|||
D0400EE31D5B912E007931CE /* NotificationService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D015E01E225CDF5000CB9E8A /* Api0.swift */,
|
||||
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 */,
|
||||
D000CAC221FB6E170011B15D /* NotificationService-AppStore.entitlements */,
|
||||
D000CAC321FB6E170011B15D /* NotificationService-AppStoreLLC.entitlements */,
|
||||
D000CAC121FB6E160011B15D /* NotificationService-Fork.entitlements */,
|
||||
D000CAC021FB6E160011B15D /* NotificationService-HockeyApp.entitlements */,
|
||||
D0400EE41D5B912E007931CE /* NotificationService.swift */,
|
||||
D0ED633921FF3EDF001D4648 /* AccountData.swift */,
|
||||
D0ED633E21FF46E4001D4648 /* ImageData.swift */,
|
||||
D0400EE61D5B912E007931CE /* Info.plist */,
|
||||
D0ED633C21FF3F28001D4648 /* NotificationService-Bridging-Header.h */,
|
||||
D0ED634021FF4786001D4648 /* Serialization.swift */,
|
||||
D073E51E21FF7CE900742DDD /* Crypto.h */,
|
||||
D073E51F21FF7CE900742DDD /* Crypto.m */,
|
||||
D073E52122003E1E00742DDD /* Data.swift */,
|
||||
D0E2CE632227F0680084E3DD /* ManagedFile.swift */,
|
||||
D015E010225CCEB300CB9E8A /* ReadBuffer.swift */,
|
||||
);
|
||||
path = NotificationService;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -3175,15 +3179,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"
|
||||
|
|
|
|||
|
|
@ -551,6 +551,9 @@
|
|||
<FileRef
|
||||
location = "group:submodules/SettingsUI/SettingsUI_Xcode.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:submodules/MessageReactionListUI/MessageReactionListUI_Xcode.xcodeproj">
|
||||
</FileRef>
|
||||
</Group>
|
||||
<Group
|
||||
location = "container:"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME}</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict/>
|
||||
<dict/>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIcons</key>
|
||||
|
|
@ -332,5 +337,29 @@
|
|||
<false/>
|
||||
<key>UIViewGroupOpacity</key>
|
||||
<false/>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Telegram iOS Color Theme File</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array>
|
||||
<string>BlueIcon@3x.png</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.telegram.Telegram-iOS.theme</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>tgios-theme</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,5 @@
|
|||
<string>merchant.privatbank.test.telergramios</string>
|
||||
<string>merchant.privatbank.prod.telergram</string>
|
||||
</array>
|
||||
<key>com.apple.developer.carplay-messaging</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@
|
|||
"PUSH_MESSAGE" = "%1$@|sent you a message";
|
||||
"PUSH_MESSAGES_1" = "%1$@|sent you a message";
|
||||
"PUSH_MESSAGES_any" = "%1$@|sent you %2$d messages";
|
||||
"PUSH_MESSAGE_ALBUM" = "%1$@|sent you an album";
|
||||
"PUSH_ALBUM" = "%1$@|sent you an album";
|
||||
|
||||
"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@";
|
||||
"PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message";
|
||||
|
|
@ -217,6 +217,8 @@
|
|||
"PUSH_CHAT_MESSAGE_GAME_SCORE" = "%1$@ scored %4$@ in game %3$@ in the group %2$@";
|
||||
"PUSH_CHAT_MESSAGE_VIDEOS" = "%1$@ sent %3$@ videos to the group %2$@";
|
||||
|
||||
"PUSH_REMINDER_TITLE" = "🗓 Reminder";
|
||||
|
||||
"LOCAL_MESSAGE_FWDS" = "%1$@ forwarded you %2$d messages";
|
||||
"LOCAL_CHANNEL_MESSAGE_FWDS" = "%1$@ posted %2$d forwarded messages";
|
||||
"LOCAL_CHAT_MESSAGE_FWDS" = "%1$@ forwarded %2$d messages";
|
||||
|
|
@ -1001,6 +1003,7 @@
|
|||
"Notification.Mute1h" = "Mute for 1 hour";
|
||||
"Notification.Mute1hMin" = "Mute for 1h";
|
||||
"Conversation.ContextMenuShare" = "Share";
|
||||
"Conversation.ContextMenuLookUp" = "Look Up";
|
||||
|
||||
"SharedMedia.TitleAll" = "Shared Media";
|
||||
|
||||
|
|
@ -4280,6 +4283,10 @@ Sorry for the inconvenience.";
|
|||
"Privacy.AddNewPeer" = "Add Users or Groups";
|
||||
"PrivacyPhoneNumberSettings.WhoCanSeeMyPhoneNumber" = "WHO CAN SEE MY PHONE NUMBER";
|
||||
"PrivacyPhoneNumberSettings.CustomHelp" = "Users who already have your number saved in the contacts will also see it on Telegram.";
|
||||
"PrivacyPhoneNumberSettings.CustomDisabledHelp" = "Users who add your number to their contacts will see it on Telegram only if they are your contacts.";
|
||||
|
||||
"PrivacyPhoneNumberSettings.DiscoveryHeader" = "WHO CAN FIND ME BY MY NUMBER";
|
||||
|
||||
"Privacy.PhoneNumber" = "Phone Number";
|
||||
"PrivacySettings.PhoneNumber" = "Phone Number";
|
||||
"Contacts.SearchUsersAndGroupsLabel" = "Search for users and groups";
|
||||
|
|
@ -4525,7 +4532,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"VoiceOver.Chat.RecordModeVoiceMessage" = "Voice message";
|
||||
"VoiceOver.Chat.RecordModeVoiceMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to video.";
|
||||
"VoiceOver.Chat.RecordModeVideoMessage" = "Video message";
|
||||
"VoiceOver.Chat.RecordModeVideoMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio.";
|
||||
"VoiceOver.Chat.RecordModeVideoMessageInfo" = "Double tap and hold to record video message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio.";
|
||||
"VoiceOver.Chat.Message" = "Message";
|
||||
"VoiceOver.Chat.YourMessage" = "Your message";
|
||||
"VoiceOver.Chat.ReplyFrom" = "Reply to message from: %@";
|
||||
|
|
@ -4575,7 +4582,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"VoiceOver.Chat.Title" = "Title: %@";
|
||||
"VoiceOver.Chat.Caption" = "Caption: %@";
|
||||
"VoiceOver.Chat.Duration" = "Duration: %@";
|
||||
"VoiceOver.Chat.Size" = "Size %@";
|
||||
"VoiceOver.Chat.Size" = "Size: %@";
|
||||
"VoiceOver.Chat.MusicTitle" = "%1$@, by %2$@";
|
||||
"VoiceOver.Chat.PlayHint" = "Double tap to play";
|
||||
"VoiceOver.Chat.OpenHint" = "Double tap to open";
|
||||
|
|
@ -4606,11 +4613,17 @@ Any member of this group will be able to see messages in the channel.";
|
|||
"ScheduledMessages.Title" = "Scheduled Messages";
|
||||
"ScheduledMessages.RemindersTitle" = "Reminders";
|
||||
"ScheduledMessages.ScheduledDate" = "Scheduled for %@";
|
||||
"ScheduledMessages.ScheduledToday" = "Scheduled for today";
|
||||
"ScheduledMessages.SendNow" = "Send Now";
|
||||
"ScheduledMessages.EditTime" = "Reschedule";
|
||||
"ScheduledMessages.ClearAll" = "Clear All";
|
||||
"ScheduledMessages.Delete" = "Delete";
|
||||
"ScheduledMessages.ClearAllConfirmation" = "Clear Scheduled Messages";
|
||||
"ScheduledMessages.Delete" = "Delete Scheduled Message";
|
||||
"ScheduledMessages.DeleteMany" = "Delete Scheduled Messages";
|
||||
"ScheduledMessages.EmptyPlaceholder" = "No scheduled messages here yet...";
|
||||
"ScheduledMessages.BotActionUnavailable" = "This action will become available after the message is published.";
|
||||
"ScheduledMessages.PollUnavailable" = "Voting will become available after the message is published.";
|
||||
"ScheduledMessages.ReminderNotification" = "📅 Reminder";
|
||||
|
||||
"Conversation.SendMessage.SetReminder" = "Set a Reminder";
|
||||
|
||||
|
|
@ -4623,37 +4636,57 @@ Any member of this group will be able to see messages in the channel.";
|
|||
|
||||
"AccentColor.Title" = "Accent Color";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.1.Name" = "Eva Summer";
|
||||
"Appearance.ThemePreview.ChatList.1.Text" = "Text";
|
||||
"Appearance.ThemePreview.ChatList.1.Name" = "Alicia Torreaux";
|
||||
"Appearance.ThemePreview.ChatList.1.Text" = "Bob says hi.";
|
||||
"Appearance.ThemePreview.ChatList.2.Name" = "Roberto";
|
||||
"Appearance.ThemePreview.ChatList.2.Text" = "Say hello to Alice 👋";
|
||||
"Appearance.ThemePreview.ChatList.3.Name" = "Campus Public Chat";
|
||||
"Appearance.ThemePreview.ChatList.3.AuthorName" = "Jennie Alpha";
|
||||
"Appearance.ThemePreview.ChatList.3.Text" = "We just reached 2,500 members! WOO!";
|
||||
"Appearance.ThemePreview.ChatList.4.Name" = "Veronica";
|
||||
"Appearance.ThemePreview.ChatList.4.Text" = "Table for four, 2PM. Be there.";
|
||||
"Appearance.ThemePreview.ChatList.5.Name" = "Animal Videos";
|
||||
"Appearance.ThemePreview.ChatList.5.Text" = "Vote now! Moar cat videos in this channel?";
|
||||
"Appearance.ThemePreview.ChatList.6.Name" = "Little Sister";
|
||||
"Appearance.ThemePreview.ChatList.6.Text" = "Don't tell mom yet, but I got the job! I'm going to ROME!";
|
||||
"Appearance.ThemePreview.ChatList.7.Name" = "Jennie Alpha";
|
||||
"Appearance.ThemePreview.ChatList.7.Text" = "🖼 Check these out";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.2.Name" = "Your inner Competition";
|
||||
"Appearance.ThemePreview.ChatList.2.Text" = "Hey";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.3.Name" = "Mike Apple";
|
||||
"Appearance.ThemePreview.ChatList.3.Text" = "Mike Apple";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.4.Name" = "Paul Newman";
|
||||
"Appearance.ThemePreview.ChatList.4.Text" = "Any ideas?";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.5.Name" = "Old Pirates";
|
||||
"Appearance.ThemePreview.ChatList.5.Text" = "Yo-ho-ho";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.6.Name" = "Kate Bright";
|
||||
"Appearance.ThemePreview.ChatList.6.Text" = "Hola!";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.7.Name" = "What";
|
||||
"Appearance.ThemePreview.ChatList.7.Text" = "Hola!";
|
||||
|
||||
"Appearance.ThemePreview.ChatList.8.Name" = "What";
|
||||
"Appearance.ThemePreview.ChatList.8.Text" = "Hola!";
|
||||
|
||||
"Appearance.ThemePreview.Chat.1.Text" = "Reminds me of a Chinese proverb: the best time to plant a tree was 20 years ago. The second best time is now.";
|
||||
"Appearance.ThemePreview.Chat.1.ReplyName" = "Alex Cassio";
|
||||
"Appearance.ThemePreview.Chat.1.ReplyText" = "Mark Twain said that ☝️";
|
||||
|
||||
"Appearance.ThemePreview.Chat.2.Text" = "Mark Twain said that ☝️";
|
||||
"Appearance.ThemePreview.Chat.3.Text" = "Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do, so throw off the bowlines, sail away from safe harboor, catch the trade winds in your sails.";
|
||||
|
||||
"Appearance.ThemePreview.Chat.4.Text" = "Nearly missed the sunrise.";
|
||||
"Appearance.ThemePreview.Chat.1.Text" = "Does he want me to, to turn from the right or turn from the left? 🤔";
|
||||
"Appearance.ThemePreview.Chat.2.ReplyName" = "Bob Harris";
|
||||
"Appearance.ThemePreview.Chat.2.Text" = "Right side. And, uh, with intensity.";
|
||||
"Appearance.ThemePreview.Chat.3.Text" = "Is that everything? It seemed like he said quite a bit more than that. 😯";
|
||||
|
||||
"GroupInfo.Permissions.SlowmodeValue.Off" = "Off";
|
||||
|
||||
"Undo.ScheduledMessagesCleared" = "Scheduled messages cleared";
|
||||
|
||||
"Appearance.CreateTheme" = "Create New Theme";
|
||||
"Appearance.EditTheme" = "Edit Theme";
|
||||
"Appearance.ShareTheme" = "Share";
|
||||
"Appearance.RemoveTheme" = "Remove";
|
||||
"Appearance.RemoveThemeConfirmation" = "Remove Theme";
|
||||
|
||||
"Conversation.Theme" = "Color Theme";
|
||||
"Conversation.ViewTheme" = "VIEW THEME";
|
||||
|
||||
"Message.Theme" = "Color Theme";
|
||||
|
||||
"EditTheme.CreateTitle" = "Create Theme";
|
||||
"EditTheme.EditTitle" = "Edit Theme";
|
||||
"EditTheme.Title" = "Theme Name";
|
||||
"EditTheme.ShortLink" = "link";
|
||||
"EditTheme.ShortLinkInfo" = "Short Link Info";
|
||||
"EditTheme.Preview" = "CHAT PREVIEW";
|
||||
"EditTheme.UploadNewTheme" = "Select a File...";
|
||||
"EditTheme.UploadEditedTheme" = "Select Updated File...";
|
||||
"EditTheme.UploadNewInfo" = "This theme will be based on your current theme and wallpaper. Otherwise, you can use a custom theme file if you already have one.";
|
||||
"EditTheme.UploadEditedInfo" = "You can select a new file to update the theme. It will be updated for all users.";
|
||||
"EditTheme.ThemeTemplateAlert" = "A copy of your theme template has been added to your Saved Messages.";
|
||||
"EditTheme.FileReadError" = "Invalid theme file";
|
||||
|
||||
"Wallpaper.ErrorNotFound" = "Sorry, this chat background doesn't seem to exist.";
|
||||
"Theme.ErrorNotFound" = "Sorry, this color theme doesn't seem to exist.";
|
||||
"Theme.Unsupported" = "Sorry, this color theme doesn't support your device yet.";
|
||||
|
||||
"Conversation.SendMessageErrorTooMuchScheduled" = "Sorry, you can not schedule more than 100 messages.";
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ public enum ResolvedUrl {
|
|||
case cancelAccountReset(phone: String, hash: String)
|
||||
case share(url: String?, text: String?, to: String?)
|
||||
case wallpaper(WallpaperUrlParameter)
|
||||
case theme(String)
|
||||
}
|
||||
|
||||
public enum NavigateToChatKeepStack {
|
||||
|
|
@ -179,7 +180,7 @@ public final class NavigateToChatControllerParams {
|
|||
public let chatController: ChatController?
|
||||
public let context: AccountContext
|
||||
public let chatLocation: ChatLocation
|
||||
public let messageId: MessageId?
|
||||
public let subject: ChatControllerSubject?
|
||||
public let botStart: ChatControllerInitialBotStart?
|
||||
public let updateTextInputState: ChatTextInputState?
|
||||
public let activateInput: Bool
|
||||
|
|
@ -191,12 +192,12 @@ public final class NavigateToChatControllerParams {
|
|||
public let parentGroupId: PeerGroupId?
|
||||
public let completion: () -> Void
|
||||
|
||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
|
||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
|
||||
self.navigationController = navigationController
|
||||
self.chatController = chatController
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
self.messageId = messageId
|
||||
self.subject = subject
|
||||
self.botStart = botStart
|
||||
self.updateTextInputState = updateTextInputState
|
||||
self.activateInput = activateInput
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import TelegramUIPreferences
|
|||
|
||||
public enum ChatControllerInitialBotStartBehavior {
|
||||
case interactive
|
||||
case automatic(returnToPeerId: PeerId)
|
||||
case automatic(returnToPeerId: PeerId, scheduled: Bool)
|
||||
}
|
||||
|
||||
public struct ChatControllerInitialBotStart {
|
||||
|
|
@ -24,7 +24,7 @@ public struct ChatControllerInitialBotStart {
|
|||
|
||||
public enum ChatControllerInteractionNavigateToPeer {
|
||||
case `default`
|
||||
case chat(textInputState: ChatTextInputState?, messageId: MessageId?)
|
||||
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?)
|
||||
case info
|
||||
case withBotStartPayload(ChatControllerInitialBotStart)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import AccountContext
|
|||
public func textAlertController(context: AccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: title, text: text, actions: actions)
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: title, text: text, actions: actions, actionLayout: actionLayout)
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -301,6 +301,11 @@ public struct AnimatedStickerStatus: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
public enum AnimatedStickerNodeResource {
|
||||
case resource(MediaResource)
|
||||
case localFile(String)
|
||||
}
|
||||
|
||||
public final class AnimatedStickerNode: ASDisplayNode {
|
||||
private let queue: Queue
|
||||
private var account: Account?
|
||||
|
|
@ -381,28 +386,38 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||
self.addSubnode(self.renderer!)
|
||||
}
|
||||
|
||||
public func setup(account: Account, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
||||
public func setup(account: Account, resource: AnimatedStickerNodeResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
||||
if width < 2 || height < 2 {
|
||||
return
|
||||
}
|
||||
self.playbackMode = playbackMode
|
||||
switch mode {
|
||||
case .direct:
|
||||
case .direct:
|
||||
let f: (MediaResourceData) -> Void = { [weak self] data in
|
||||
guard let strongSelf = self, data.complete else {
|
||||
return
|
||||
}
|
||||
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
|
||||
strongSelf.directData = Tuple(directData, data.path, width, height)
|
||||
}
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.play()
|
||||
} else if strongSelf.canDisplayFirstFrame {
|
||||
strongSelf.play(firstFrame: true)
|
||||
}
|
||||
}
|
||||
switch resource {
|
||||
case let .resource(resource):
|
||||
self.disposable.set((account.postbox.mediaBox.resourceData(resource)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self, data.complete else {
|
||||
return
|
||||
}
|
||||
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
|
||||
strongSelf.directData = Tuple(directData, data.path, width, height)
|
||||
}
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.play()
|
||||
} else if strongSelf.canDisplayFirstFrame {
|
||||
strongSelf.play(firstFrame: true)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { data in
|
||||
f(data)
|
||||
}))
|
||||
case .cached:
|
||||
case let .localFile(path):
|
||||
f(MediaResourceData(path: path, offset: 0, size: Int(Int32.max - 1), complete: true))
|
||||
}
|
||||
case .cached:
|
||||
switch resource {
|
||||
case let .resource(resource):
|
||||
self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, fitzModifier: fitzModifier, width: width, height: height, synchronousLoad: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
if let strongSelf = self, data.complete {
|
||||
|
|
@ -414,6 +429,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
}))
|
||||
case .localFile:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import TelegramPresentationData
|
|||
import AnimationUI
|
||||
|
||||
private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed()
|
||||
private let savedMessagesIcon = UIImage(bundleImageName: "Avatar/SavedMessagesIcon")?.precomposed()
|
||||
private let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/SavedMessagesIcon"), color: .white)
|
||||
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
||||
|
||||
public enum AvatarNodeClipStyle {
|
||||
|
|
@ -300,7 +300,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||
representation = nil
|
||||
icon = .deletedIcon
|
||||
}
|
||||
} else if peer?.restrictionText == nil {
|
||||
} else if peer?.restrictionText(platform: "ios") == nil {
|
||||
representation = peer?.smallProfileImage
|
||||
}
|
||||
let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? PeerId(namespace: 0, id: 0), peer?.displayLetters ?? [], representation)
|
||||
|
|
@ -344,6 +344,9 @@ public final class AvatarNode: ASDisplayNode {
|
|||
if self.parameters == nil || self.parameters != parameters {
|
||||
self.parameters = parameters
|
||||
self.setNeedsDisplay()
|
||||
if synchronousLoad {
|
||||
self.recursivelyEnsureDisplaySynchronously(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: M
|
|||
}
|
||||
}
|
||||
|
||||
public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
|
||||
public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) -> Signal<UIImage?, NoError>? {
|
||||
if let imageData = peerAvatarImageData(account: account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
||||
return imageData
|
||||
|> mapToSignal { data -> Signal<UIImage?, NoError> in
|
||||
|
|
@ -74,21 +74,22 @@ public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: Messa
|
|||
return .single(generateImage(displayDimensions, contextGenerator: { size, context -> Void in
|
||||
if let data = data {
|
||||
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
|
||||
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
||||
context.setBlendMode(.copy)
|
||||
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions))
|
||||
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
||||
context.setBlendMode(.destinationOut)
|
||||
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions))
|
||||
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
||||
} else {
|
||||
if let emptyColor = emptyColor {
|
||||
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
||||
context.setFillColor(emptyColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
||||
}
|
||||
}
|
||||
} else if let emptyColor = emptyColor {
|
||||
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
||||
context.setFillColor(emptyColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -838,7 +838,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode<BotCheckoutEntry>,
|
|||
}
|
||||
|
||||
if let shippingOptions = strongSelf.currentValidatedFormInfo?.shippingOptions, let shippingOptionId = strongSelf.currentShippingOptionId {
|
||||
if let shippingOptionIndex = shippingOptions.index(where: { $0.id == shippingOptionId }) {
|
||||
if let shippingOptionIndex = shippingOptions.firstIndex(where: { $0.id == shippingOptionId }) {
|
||||
for price in shippingOptions[shippingOptionIndex].prices {
|
||||
totalAmount += price.amount
|
||||
|
||||
|
|
|
|||
|
|
@ -152,8 +152,9 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
|
|||
self.textFieldNode.textField.textColor = theme.actionSheet.primaryTextColor
|
||||
self.textFieldNode.textField.font = Font.regular(12.0)
|
||||
self.textFieldNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(12.0)]
|
||||
self.textFieldNode.textField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.textFieldNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.textFieldNode.textField.isSecureTextEntry = true
|
||||
self.textFieldNode.textField.tintColor = theme.list.itemAccentColor
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ final class BotPaymentCardInputItemNode: BotPaymentItemNode, STPPaymentCardTextF
|
|||
self.cardField.textColor = theme.list.itemPrimaryTextColor
|
||||
self.cardField.textErrorColor = theme.list.itemDestructiveColor
|
||||
self.cardField.placeholderColor = theme.list.itemPlaceholderTextColor
|
||||
self.cardField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.cardField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||
}
|
||||
|
||||
self.cardField.frame = CGRect(origin: CGPoint(x: 5.0, y: 0.0), size: CGSize(width: width - 10.0, height: 44.0))
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate {
|
|||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: titleFont, textColor: theme.list.itemPrimaryTextColor)
|
||||
self.textField.textField.textColor = theme.list.itemPrimaryTextColor
|
||||
self.textField.textField.attributedPlaceholder = NSAttributedString(string: placeholder, font: titleFont, textColor: theme.list.itemPlaceholderTextColor)
|
||||
self.textField.textField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.textField.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.textField.textField.tintColor = theme.list.itemAccentColor
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 16.0
|
||||
|
|
@ -96,7 +97,8 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate {
|
|||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: titleFont, textColor: theme.list.itemPrimaryTextColor)
|
||||
self.textField.textField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.textField.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.textField.textField.tintColor = theme.list.itemAccentColor
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 16.0
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
var leftInset: CGFloat = 86.0 + params.leftInset
|
||||
let rightInset: CGFloat = 13.0 + params.rightInset
|
||||
var infoIconRightInset: CGFloat = rightInset
|
||||
var infoIconRightInset: CGFloat = rightInset - 1.0
|
||||
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
|
@ -335,7 +335,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
}
|
||||
|
||||
var dateRightInset: CGFloat = 43.0 + params.rightInset
|
||||
var dateRightInset: CGFloat = 46.0 + params.rightInset
|
||||
if item.editing {
|
||||
leftInset += editingOffset
|
||||
dateRightInset += 5.0
|
||||
|
|
@ -558,7 +558,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||
if strongSelf.typeIconNode.image !== outgoingIcon {
|
||||
strongSelf.typeIconNode.image = outgoingIcon
|
||||
}
|
||||
transition.updateFrameAdditive(node: strongSelf.typeIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 76.0, y: floor((nodeLayout.contentSize.height - outgoingIcon.size.height) / 2.0)), size: outgoingIcon.size))
|
||||
transition.updateFrameAdditive(node: strongSelf.typeIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 81.0, y: floor((nodeLayout.contentSize.height - outgoingIcon.size.height) / 2.0)), size: outgoingIcon.size))
|
||||
}
|
||||
strongSelf.typeIconNode.isHidden = !hasOutgoing
|
||||
|
||||
|
|
@ -653,9 +653,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
let leftInset: CGFloat = 86.0 + params.leftInset + editingOffset
|
||||
let rightInset: CGFloat = 13.0 + params.rightInset
|
||||
var infoIconRightInset: CGFloat = rightInset
|
||||
var infoIconRightInset: CGFloat = rightInset - 1.0
|
||||
|
||||
var dateRightInset: CGFloat = 43.0 + params.rightInset
|
||||
var dateRightInset: CGFloat = 46.0 + params.rightInset
|
||||
if item.editing {
|
||||
dateRightInset += 5.0
|
||||
infoIconRightInset -= 36.0
|
||||
|
|
@ -669,7 +669,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
transition.updateFrameAdditive(node: self.dateNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + self.bounds.size.width - dateRightInset - self.dateNode.bounds.size.width, y: self.dateNode.frame.minY), size: self.dateNode.bounds.size))
|
||||
|
||||
transition.updateFrameAdditive(node: self.typeIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 76.0, y: self.typeIconNode.frame.minY), size: self.typeIconNode.bounds.size))
|
||||
transition.updateFrameAdditive(node: self.typeIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 81.0, y: self.typeIconNode.frame.minY), size: self.typeIconNode.bounds.size))
|
||||
|
||||
transition.updateFrameAdditive(node: self.infoButtonNode, frame: CGRect(origin: CGPoint(x: revealOffset + self.bounds.size.width - infoIconRightInset - self.infoButtonNode.bounds.width, y: self.infoButtonNode.frame.minY), size: self.infoButtonNode.bounds.size))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -570,7 +570,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||
return
|
||||
}
|
||||
|
||||
let beginClear: (InteractiveMessagesDeletionType) -> Void = { type in
|
||||
let beginClear: (InteractiveHistoryClearingType) -> Void = { type in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -741,14 +741,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||
|> deliverOnMainQueue).start(next: { [weak strongSelf] actualPeerId in
|
||||
if let strongSelf = strongSelf {
|
||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
||||
|
||||
var scrollToEndIfExists = false
|
||||
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
|
||||
scrollToEndIfExists = true
|
||||
}
|
||||
|
||||
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), messageId: messageId, purposefulAction: {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(messageId), purposefulAction: {
|
||||
self?.deactivateSearch(animated: false)
|
||||
}, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : []))
|
||||
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
|
|
@ -772,12 +769,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||
if dismissSearch {
|
||||
strongSelf.deactivateSearch(animated: true)
|
||||
}
|
||||
|
||||
var scrollToEndIfExists = false
|
||||
if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass {
|
||||
scrollToEndIfExists = true
|
||||
}
|
||||
|
||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
|
||||
self?.deactivateSearch(animated: false)
|
||||
|
|
@ -853,7 +848,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
|||
}
|
||||
|
||||
self.chatListDisplayNode.isEmptyUpdated = { [weak self] isEmpty in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout {
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let _ = strongSelf.validLayout {
|
||||
if isEmpty {
|
||||
searchContentNode.updateListVisibleContentOffset(.known(0.0))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,24 +12,24 @@ enum ChatTitleProxyStatus {
|
|||
}
|
||||
|
||||
private func generateIcon(color: UIColor, connected: Bool, off: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 18.0, height: 22.0), rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setFillColor(color.cgColor)
|
||||
context.scaleBy(x: 0.3333, y: 0.3333)
|
||||
context.setLineWidth(3.0)
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M27,1.6414763 L1.5,12.9748096 L1.5,30 C1.5,45.9171686 12.4507463,60.7063193 27,64.4535514 C41.5492537,60.7063193 52.5,45.9171686 52.5,30 L52.5,12.9748096 L27,1.6414763 S")
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/ProxyShieldIcon"), color: color) {
|
||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: image.size))
|
||||
}
|
||||
if connected {
|
||||
let _ = try? drawSvgPath(context, path: "M15.5769231,34.1735387 L23.5896918,42.2164446 C23.6840928,42.3112006 23.8352513,42.30478 23.9262955,42.2032393 L40.5,23.71875 S")
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/ProxyCheckIcon"), color: color) {
|
||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: image.size))
|
||||
}
|
||||
} else if off {
|
||||
let _ = try? drawSvgPath(context, path: "M27.5,15 C28.3284271,15 29,15.6715729 29,16.5 L29,28.5 C29,29.3284271 28.3284271,30 27.5,30 C26.6715729,30 26,29.3284271 26,28.5 L26,16.5 C26,15.6715729 26.6715729,15 27.5,15 Z")
|
||||
context.translateBy(x: 27.0, y: 33.0)
|
||||
context.rotate(by: 2.35619)
|
||||
context.translateBy(x: -27.0, y: -33.0)
|
||||
let _ = try? drawSvgPath(context, path: "M27,47 C34.7319865,47 41,40.7319865 41,33 C41,25.2680135 34.7319865,19 27,19 C19.2680135,19 13,25.2680135 13,33 S")
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/ProxyOnIcon"), color: color) {
|
||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: image.size))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ final class ChatTitleProxyNode: ASDisplayNode {
|
|||
case .available:
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true)
|
||||
}
|
||||
self.activityIndicator.type = .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.0, true)
|
||||
self.activityIndicator.type = .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.3333, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ final class ChatTitleProxyNode: ASDisplayNode {
|
|||
var status: ChatTitleProxyStatus = .connected {
|
||||
didSet {
|
||||
if self.status != oldValue {
|
||||
switch status {
|
||||
switch self.status {
|
||||
case .connecting:
|
||||
self.activityIndicator.isHidden = false
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: false)
|
||||
|
|
@ -81,17 +81,17 @@ final class ChatTitleProxyNode: ASDisplayNode {
|
|||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true)
|
||||
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.0, true), speed: .slow)
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.3333, true), speed: .slow)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.activityIndicator)
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(), size: CGSize(width: 18.0, height: 22.0))
|
||||
let iconFrame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: 30.0))
|
||||
self.iconNode.frame = iconFrame
|
||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: floor(iconFrame.midX - 5.0), y: 6.0), size: CGSize(width: 10.0, height: 10.0))
|
||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: floor(iconFrame.midX - 5.0), y: 10.0), size: CGSize(width: 10.0, height: 10.0))
|
||||
|
||||
self.frame = CGRect(origin: CGPoint(), size: CGSize(width: 18.0, height: 22.0))
|
||||
self.frame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: 30.0))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
|
|||
let titleFrame = titleContentRect
|
||||
self.titleNode.frame = titleFrame
|
||||
|
||||
let proxyFrame = CGRect(origin: CGPoint(x: clearBounds.maxX - 16.0 - self.proxyNode.bounds.width, y: 1.0 + floor((size.height - proxyNode.bounds.height) / 2.0)), size: proxyNode.bounds.size)
|
||||
let proxyFrame = CGRect(origin: CGPoint(x: clearBounds.maxX - 9.0 - self.proxyNode.bounds.width, y: floor((size.height - proxyNode.bounds.height) / 2.0)), size: proxyNode.bounds.size)
|
||||
self.proxyNode.frame = proxyFrame
|
||||
self.proxyButton.frame = proxyFrame.insetBy(dx: -2.0, dy: -2.0)
|
||||
|
||||
|
|
|
|||
|
|
@ -915,9 +915,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
if let mutedCount = unreadCount.mutedCount, mutedCount > 0 {
|
||||
let mutedUnreadCountText = compactNumericCountString(Int(mutedCount), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
|
||||
|
||||
currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme)
|
||||
|
||||
mentionBadgeContent = .text(NSAttributedString(string: mutedUnreadCountText, font: badgeFont, textColor: theme.unreadBadgeInactiveTextColor))
|
||||
}
|
||||
}
|
||||
|
|
@ -1331,8 +1329,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
if let currentMutedIconImage = currentMutedIconImage {
|
||||
strongSelf.mutedIconNode.image = currentMutedIconImage
|
||||
strongSelf.mutedIconNode.isHidden = false
|
||||
transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: contentRect.origin.y + 5.0), size: currentMutedIconImage.size))
|
||||
nextTitleIconOrigin += currentMutedIconImage.size.width + 3.0
|
||||
transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 4.0, y: contentRect.origin.y - 2.0), size: currentMutedIconImage.size))
|
||||
nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0
|
||||
} else {
|
||||
strongSelf.mutedIconNode.image = nil
|
||||
strongSelf.mutedIconNode.isHidden = true
|
||||
|
|
@ -1402,7 +1400,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
let titlePosition = strongSelf.titleNode.position
|
||||
transition.animatePosition(node: strongSelf.titleNode, from: CGPoint(x: titlePosition.x - contentDelta.x, y: titlePosition.y - contentDelta.y))
|
||||
|
||||
let textPosition = strongSelf.textNode.position
|
||||
transition.animatePositionAdditive(node: strongSelf.textNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
|
||||
let authorPosition = strongSelf.authorNode.position
|
||||
|
|
@ -1570,7 +1567,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
}
|
||||
|
||||
let mutedIconFrame = self.mutedIconNode.frame
|
||||
transition.updateFrame(node: self.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: contentRect.origin.y + 5.0), size: mutedIconFrame.size))
|
||||
transition.updateFrame(node: self.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 4.0, y: contentRect.origin.y - 2.0), size: mutedIconFrame.size))
|
||||
nextTitleIconOrigin += mutedIconFrame.size.width + 3.0
|
||||
|
||||
let badgeFrame = self.badgeNode.frame
|
||||
|
|
|
|||
|
|
@ -402,7 +402,7 @@ private class ChatListStatusFailedNode: ChatListStatusContentNode {
|
|||
}
|
||||
|
||||
let diameter: CGFloat = 14.0
|
||||
let rect = CGRect(origin: CGPoint(x: floor((bounds.width - diameter) / 2.0), y: floor((bounds.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter)).offsetBy(dx: 1.0, dy: 1.0)
|
||||
let rect = CGRect(origin: CGPoint(x: floor((bounds.width - diameter) / 2.0), y: floor((bounds.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter)).offsetBy(dx: 1.0, dy: UIScreenPixel)
|
||||
|
||||
context.setFillColor(parameters.fill.cgColor)
|
||||
context.fillEllipse(in: rect)
|
||||
|
|
@ -412,7 +412,7 @@ private class ChatListStatusFailedNode: ChatListStatusContentNode {
|
|||
let stringRect = string.boundingRect(with: rect.size, options: .usesLineFragmentOrigin, context: nil)
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
string.draw(at: CGPoint(x: rect.minX + floor((rect.width - stringRect.width) / 2.0), y: 1.0 + rect.minY + floor((rect.height - stringRect.height) / 2.0)))
|
||||
string.draw(at: CGPoint(x: rect.minX + floor((rect.width - stringRect.width) / 2.0), y: 1.0 - UIScreenPixel + rect.minY + floor((rect.height - stringRect.height) / 2.0)))
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -513,6 +513,7 @@ public func createPollController(context: AccountContext, peerId: PeerId, comple
|
|||
}
|
||||
}
|
||||
controller.isOpaqueWhenInOverlay = true
|
||||
//controller.isModalWhenInOverlay = true
|
||||
controller.blocksBackgroundWhenInOverlay = true
|
||||
controller.experimentalSnapScrollToItem = true
|
||||
|
||||
|
|
|
|||
|
|
@ -275,6 +275,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
|||
|
||||
if strongSelf.isNodeLoaded {
|
||||
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.theme.list.itemPrimaryTextColor]
|
||||
strongSelf.textNode.tintColor = item.theme.list.itemAccentColor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,7 +321,7 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
|
|||
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
|
||||
}
|
||||
|
||||
strongSelf.textNode.keyboardAppearance = item.theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
strongSelf.textNode.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
|
||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - rightInset, height: textLayout.size.height + 1.0))
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ class ContactListActionItemNode: ListViewItemNode {
|
|||
titleOffset = iconFrame.maxX - totalWidth
|
||||
}
|
||||
default:
|
||||
iconFrame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
|
||||
iconFrame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + 3.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ static_library(
|
|||
"//submodules/Display:Display#shared",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TextSelectionNode:TextSelectionNode",
|
||||
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
D038AC7522F8A06200320981 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D038AC7422F8A06200320981 /* Display.framework */; };
|
||||
D038AC7722F8A07000320981 /* ContextController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D038AC7622F8A07000320981 /* ContextController.swift */; };
|
||||
D038AC7922F8A08A00320981 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D038AC7822F8A08A00320981 /* AsyncDisplayKit.framework */; };
|
||||
D0624F93230C0CB7000FC2BD /* ReactionSelectionNode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0624F92230C0CB7000FC2BD /* ReactionSelectionNode.framework */; };
|
||||
D0624F95230C0D2C000FC2BD /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0624F94230C0D2C000FC2BD /* TelegramCore.framework */; };
|
||||
D09E777F22F8E47000B9CCA7 /* TelegramPresentationData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D09E777E22F8E47000B9CCA7 /* TelegramPresentationData.framework */; };
|
||||
D09E778122F8E62000B9CCA7 /* ContextActionsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09E778022F8E62000B9CCA7 /* ContextActionsContainerNode.swift */; };
|
||||
D09E778322F8E67300B9CCA7 /* ContextActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09E778222F8E67300B9CCA7 /* ContextActionNode.swift */; };
|
||||
|
|
@ -30,6 +32,8 @@
|
|||
D038AC7422F8A06200320981 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D038AC7622F8A07000320981 /* ContextController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextController.swift; sourceTree = "<group>"; };
|
||||
D038AC7822F8A08A00320981 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0624F92230C0CB7000FC2BD /* ReactionSelectionNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactionSelectionNode.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0624F94230C0D2C000FC2BD /* TelegramCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D09E777E22F8E47000B9CCA7 /* TelegramPresentationData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramPresentationData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D09E778022F8E62000B9CCA7 /* ContextActionsContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextActionsContainerNode.swift; sourceTree = "<group>"; };
|
||||
D09E778222F8E67300B9CCA7 /* ContextActionNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextActionNode.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -43,6 +47,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0624F95230C0D2C000FC2BD /* TelegramCore.framework in Frameworks */,
|
||||
D0624F93230C0CB7000FC2BD /* ReactionSelectionNode.framework in Frameworks */,
|
||||
D0C9CBE42302D45F00FAB518 /* TextSelectionNode.framework in Frameworks */,
|
||||
D09E777F22F8E47000B9CCA7 /* TelegramPresentationData.framework in Frameworks */,
|
||||
D038AC7922F8A08A00320981 /* AsyncDisplayKit.framework in Frameworks */,
|
||||
|
|
@ -89,6 +95,8 @@
|
|||
D038AC6F22F8A05A00320981 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0624F94230C0D2C000FC2BD /* TelegramCore.framework */,
|
||||
D0624F92230C0CB7000FC2BD /* ReactionSelectionNode.framework */,
|
||||
D0C9CBE32302D45F00FAB518 /* TextSelectionNode.framework */,
|
||||
D09E777E22F8E47000B9CCA7 /* TelegramPresentationData.framework */,
|
||||
D038AC7822F8A08A00320981 /* AsyncDisplayKit.framework */,
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ final class ContextActionNode: ASDisplayNode {
|
|||
|
||||
func updateLayout(constrainedWidth: CGFloat, previous: ContextActionSibling, next: ContextActionSibling) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let iconSideInset: CGFloat = 8.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let verticalInset: CGFloat = 12.0
|
||||
|
||||
let iconSize = self.iconNode.image.flatMap({ $0.size }) ?? CGSize()
|
||||
|
|
@ -149,6 +149,29 @@ final class ContextActionNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
func updateTheme(theme: PresentationTheme) {
|
||||
self.backgroundNode.backgroundColor = theme.contextMenu.itemBackgroundColor
|
||||
self.highlightedBackgroundNode.backgroundColor = theme.contextMenu.itemHighlightedBackgroundColor
|
||||
|
||||
let textColor: UIColor
|
||||
switch action.textColor {
|
||||
case .primary:
|
||||
textColor = theme.contextMenu.primaryColor
|
||||
case .destructive:
|
||||
textColor = theme.contextMenu.destructiveColor
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: self.action.text, font: textFont, textColor: textColor)
|
||||
|
||||
switch self.action.textLayout {
|
||||
case let .secondLineWithValue(value):
|
||||
self.statusNode?.attributedText = NSAttributedString(string: value, font: textFont, textColor: theme.contextMenu.secondaryColor)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.iconNode.image = self.action.icon(theme)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.performAction()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,21 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||
return CGSize(width: maxWidth, height: verticalOffset)
|
||||
}
|
||||
|
||||
func updateTheme(theme: PresentationTheme) {
|
||||
for itemNode in self.itemNodes {
|
||||
switch itemNode {
|
||||
case let .action(action):
|
||||
action.updateTheme(theme: theme)
|
||||
case let .separator(separator):
|
||||
separator.backgroundColor = theme.contextMenu.sectionSeparatorColor
|
||||
case let .itemSeparator(itemSeparator):
|
||||
itemSeparator.backgroundColor = theme.contextMenu.itemSeparatorColor
|
||||
}
|
||||
}
|
||||
|
||||
self.backgroundColor = theme.contextMenu.backgroundColor
|
||||
}
|
||||
|
||||
func actionNode(at point: CGPoint) -> ContextActionNode? {
|
||||
for itemNode in self.itemNodes {
|
||||
switch itemNode {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public final class ContextContentContainingNode: ASDisplayNode {
|
|||
public var applyAbsoluteOffset: ((CGFloat, ContainedViewLayoutTransitionCurve, Double) -> Void)?
|
||||
public var applyAbsoluteOffsetSpring: ((CGFloat, Double, CGFloat) -> Void)?
|
||||
public var layoutUpdated: ((CGSize) -> Void)?
|
||||
public var updateDistractionFreeMode: ((Bool) -> Void)?
|
||||
|
||||
public override init() {
|
||||
self.contentNode = ContextContentNode()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import AsyncDisplayKit
|
|||
import Display
|
||||
import TelegramPresentationData
|
||||
import TextSelectionNode
|
||||
import ReactionSelectionNode
|
||||
import TelegramCore
|
||||
|
||||
private let animationDurationFactor: Double = 1.0
|
||||
|
||||
public enum ContextMenuActionItemTextLayout {
|
||||
case singleLine
|
||||
|
|
@ -19,6 +23,7 @@ public enum ContextMenuActionItemTextColor {
|
|||
public enum ContextMenuActionResult {
|
||||
case `default`
|
||||
case dismissWithoutContent
|
||||
case custom(ContainedViewLayoutTransition)
|
||||
}
|
||||
|
||||
public final class ContextMenuActionItem {
|
||||
|
|
@ -42,12 +47,23 @@ public enum ContextMenuItem {
|
|||
case separator
|
||||
}
|
||||
|
||||
private func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIView) -> CGRect {
|
||||
let sourceWindowFrame = fromView.convert(frame, to: nil)
|
||||
var targetWindowFrame = toView.convert(sourceWindowFrame, from: nil)
|
||||
|
||||
if let fromWindow = fromView.window, let toWindow = toView.window {
|
||||
targetWindowFrame.origin.x += toWindow.bounds.width - fromWindow.bounds.width
|
||||
}
|
||||
return targetWindowFrame
|
||||
}
|
||||
|
||||
private final class ContextControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
private let source: ContextControllerContentSource
|
||||
private var items: [ContextMenuItem]
|
||||
private let beginDismiss: (ContextMenuActionResult) -> Void
|
||||
private let reactionSelected: (String) -> Void
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
|
|
@ -55,6 +71,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
private var propertyAnimator: AnyObject?
|
||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
||||
private let dimNode: ASDisplayNode
|
||||
private let dismissNode: ASDisplayNode
|
||||
|
||||
private let clippingNode: ASDisplayNode
|
||||
private let scrollNode: ASScrollNode
|
||||
|
|
@ -64,25 +81,31 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
private var contentParentNode: ContextContentContainingNode?
|
||||
private let contentContainerNode: ContextContentContainerNode
|
||||
private var actionsContainerNode: ContextActionsContainerNode
|
||||
private var reactionContextNode: ReactionContextNode?
|
||||
private var reactionContextNodeIsAnimatingOut = false
|
||||
|
||||
private var didCompleteAnimationIn = false
|
||||
private var initialContinueGesturePoint: CGPoint?
|
||||
private var didMoveFromInitialGesturePoint = false
|
||||
private var highlightedActionNode: ContextActionNode?
|
||||
private var highlightedReaction: String?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
init(controller: ContextController, theme: PresentationTheme, strings: PresentationStrings, source: ContextControllerContentSource, items: [ContextMenuItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) {
|
||||
private var isAnimatingOut = false
|
||||
|
||||
init(account: Account, controller: ContextController, theme: PresentationTheme, strings: PresentationStrings, source: ContextControllerContentSource, items: [ContextMenuItem], reactionItems: [ReactionContextItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, reactionSelected: @escaping (String) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.source = source
|
||||
self.items = items
|
||||
self.beginDismiss = beginDismiss
|
||||
self.reactionSelected = reactionSelected
|
||||
|
||||
self.effectView = UIVisualEffectView()
|
||||
if #available(iOS 9.0, *) {
|
||||
} else {
|
||||
if theme.chatList.searchBarKeyboardColor == .dark {
|
||||
if theme.rootController.keyboardColor == .dark {
|
||||
self.effectView.effect = UIBlurEffect(style: .dark)
|
||||
} else {
|
||||
self.effectView.effect = UIBlurEffect(style: .light)
|
||||
|
|
@ -94,6 +117,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
self.dimNode.backgroundColor = theme.contextMenu.dimColor
|
||||
self.dimNode.alpha = 0.0
|
||||
|
||||
self.dismissNode = ASDisplayNode()
|
||||
|
||||
self.clippingNode = ASDisplayNode()
|
||||
self.clippingNode.clipsToBounds = true
|
||||
|
||||
|
|
@ -114,16 +139,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
beginDismiss(result)
|
||||
})
|
||||
|
||||
super.init()
|
||||
if !reactionItems.isEmpty {
|
||||
let reactionContextNode = ReactionContextNode(account: account, theme: theme, items: reactionItems)
|
||||
self.reactionContextNode = reactionContextNode
|
||||
} else {
|
||||
self.reactionContextNode = nil
|
||||
}
|
||||
|
||||
/*if #available(iOS 10.0, *) {
|
||||
let propertyAnimator = UIViewPropertyAnimator(duration: 0.4, curve: .linear)
|
||||
propertyAnimator.isInterruptible = true
|
||||
propertyAnimator.addAnimations {
|
||||
self.effectView.effect = makeCustomZoomBlurEffect()
|
||||
}
|
||||
self.propertyAnimator = propertyAnimator
|
||||
}*/
|
||||
super.init()
|
||||
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
|
|
@ -133,9 +156,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
self.addSubnode(self.clippingNode)
|
||||
|
||||
self.clippingNode.addSubnode(self.scrollNode)
|
||||
self.scrollNode.addSubnode(self.dismissNode)
|
||||
|
||||
self.scrollNode.addSubnode(self.actionsContainerNode)
|
||||
self.scrollNode.addSubnode(self.contentContainerNode)
|
||||
self.reactionContextNode.flatMap(self.addSubnode)
|
||||
|
||||
getController = { [weak controller] in
|
||||
return controller
|
||||
|
|
@ -172,6 +197,24 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
strongSelf.hapticFeedback.tap()
|
||||
}
|
||||
}
|
||||
|
||||
if let reactionContextNode = strongSelf.reactionContextNode {
|
||||
let highlightedReaction = reactionContextNode.reaction(at: strongSelf.view.convert(localPoint, to: reactionContextNode.view)).flatMap { value -> String? in
|
||||
switch value {
|
||||
case let .reaction(reaction, _, _):
|
||||
return reaction
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if strongSelf.highlightedReaction != highlightedReaction {
|
||||
strongSelf.highlightedReaction = highlightedReaction
|
||||
reactionContextNode.setHighlightedReaction(highlightedReaction)
|
||||
if let _ = highlightedReaction {
|
||||
strongSelf.hapticFeedback.tap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -181,21 +224,43 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
}
|
||||
recognizer.externalUpdated = nil
|
||||
if strongSelf.didMoveFromInitialGesturePoint {
|
||||
if let (view, point) = viewAndPoint {
|
||||
let _ = strongSelf.view.convert(point, from: view)
|
||||
if let (_, _) = viewAndPoint {
|
||||
if let highlightedActionNode = strongSelf.highlightedActionNode {
|
||||
strongSelf.highlightedActionNode = nil
|
||||
highlightedActionNode.performAction()
|
||||
}
|
||||
if let _ = strongSelf.reactionContextNode {
|
||||
if let reaction = strongSelf.highlightedReaction {
|
||||
strongSelf.reactionSelected(reaction)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let highlightedActionNode = strongSelf.highlightedActionNode {
|
||||
strongSelf.highlightedActionNode = nil
|
||||
highlightedActionNode.setIsHighlighted(false)
|
||||
}
|
||||
if let reactionContextNode = strongSelf.reactionContextNode, let _ = strongSelf.highlightedReaction {
|
||||
strongSelf.highlightedReaction = nil
|
||||
reactionContextNode.setHighlightedReaction(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
reactionContextNode.reactionSelected = { [weak self] reaction in
|
||||
guard let _ = self else {
|
||||
return
|
||||
}
|
||||
switch reaction {
|
||||
case let .reaction(value, _, _):
|
||||
reactionSelected(value)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -210,7 +275,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapped)))
|
||||
self.dismissNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapped)))
|
||||
}
|
||||
|
||||
@objc private func dimNodeTapped() {
|
||||
|
|
@ -229,21 +294,49 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
guard let strongSelf = self, let contentParentNode = contentParentNode, let parentSupernode = contentParentNode.supernode else {
|
||||
return
|
||||
}
|
||||
strongSelf.originalProjectedContentViewFrame = (parentSupernode.view.convert(contentParentNode.frame, to: strongSelf.view), contentParentNode.view.convert(contentParentNode.contentRect, to: strongSelf.view))
|
||||
if strongSelf.isAnimatingOut {
|
||||
return
|
||||
}
|
||||
strongSelf.originalProjectedContentViewFrame = (convertFrame(contentParentNode.frame, from: parentSupernode.view, to: strongSelf.view), convertFrame(contentParentNode.contentRect, from: contentParentNode.view, to: strongSelf.view))
|
||||
if let validLayout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(layout: validLayout, transition: .animated(duration: 0.2, curve: .easeInOut), previousActionsContainerNode: nil)
|
||||
strongSelf.updateLayout(layout: validLayout, transition: .animated(duration: 0.2 * animationDurationFactor, curve: .easeInOut), previousActionsContainerNode: nil)
|
||||
}
|
||||
}
|
||||
takenViewInfo.contentContainingNode.updateDistractionFreeMode = { [weak self] value in
|
||||
guard let strongSelf = self, let reactionContextNode = strongSelf.reactionContextNode else {
|
||||
return
|
||||
}
|
||||
if value {
|
||||
if !reactionContextNode.alpha.isZero {
|
||||
reactionContextNode.alpha = 0.0
|
||||
reactionContextNode.allowsGroupOpacity = true
|
||||
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in
|
||||
reactionContextNode?.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
} else if reactionContextNode.alpha != 1.0 {
|
||||
reactionContextNode.alpha = 1.0
|
||||
reactionContextNode.allowsGroupOpacity = true
|
||||
reactionContextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in
|
||||
reactionContextNode?.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
}
|
||||
self.contentContainerNode.contentNode = takenViewInfo.contentContainingNode.contentNode
|
||||
|
||||
self.contentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace
|
||||
self.contentContainerNode.addSubnode(takenViewInfo.contentContainingNode.contentNode)
|
||||
takenViewInfo.contentContainingNode.isExtractedToContextPreview = true
|
||||
takenViewInfo.contentContainingNode.isExtractedToContextPreviewUpdated?(true)
|
||||
|
||||
self.originalProjectedContentViewFrame = (parentSupernode.view.convert(takenViewInfo.contentContainingNode.frame, to: self.view), takenViewInfo.contentContainingNode.view.convert(takenViewInfo.contentContainingNode.contentRect, to: self.view))
|
||||
self.originalProjectedContentViewFrame = (convertFrame(takenViewInfo.contentContainingNode.frame, from: parentSupernode.view, to: self.view), convertFrame(takenViewInfo.contentContainingNode.contentRect, from: takenViewInfo.contentContainingNode.view, to: self.view))
|
||||
|
||||
self.clippingNode.layer.animateFrame(from: takenViewInfo.contentAreaInScreenSpace, to: self.clippingNode.frame, duration: 0.18, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: takenViewInfo.contentAreaInScreenSpace.minY, to: 0.0, duration: 0.18, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
var updatedContentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace
|
||||
updatedContentAreaInScreenSpace.origin.x = 0.0
|
||||
updatedContentAreaInScreenSpace.size.width = self.bounds.width
|
||||
|
||||
self.clippingNode.layer.animateFrame(from: updatedContentAreaInScreenSpace, to: self.clippingNode.frame, duration: 0.18 * animationDurationFactor, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: updatedContentAreaInScreenSpace.minY, to: 0.0, duration: 0.18 * animationDurationFactor, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
}
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
|
|
@ -251,10 +344,21 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
}
|
||||
|
||||
self.dimNode.alpha = 1.0
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
|
||||
|
||||
if #available(iOS 10.0, *) {
|
||||
if let propertyAnimator = self.propertyAnimator {
|
||||
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
|
||||
propertyAnimator?.stopAnimation(true)
|
||||
}
|
||||
self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor, curve: .easeInOut, animations: { [weak self] in
|
||||
self?.effectView.effect = makeCustomZoomBlurEffect()
|
||||
})
|
||||
}
|
||||
|
||||
if let _ = self.propertyAnimator {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.25, from: 0.0, to: 1.0, update: { [weak self] value in
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 1.0, update: { [weak self] value in
|
||||
(self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value
|
||||
}, completion: { [weak self] in
|
||||
self?.didCompleteAnimationIn = true
|
||||
|
|
@ -262,39 +366,73 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
})
|
||||
}
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: {
|
||||
self.effectView.effect = makeCustomZoomBlurEffect()
|
||||
}, completion: { [weak self] _ in
|
||||
self?.didCompleteAnimationIn = true
|
||||
})
|
||||
}
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
|
||||
|
||||
let springDuration: Double = 0.42
|
||||
let springDuration: Double = 0.42 * animationDurationFactor
|
||||
let springDamping: CGFloat = 104.0
|
||||
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode {
|
||||
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size))
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
|
||||
let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y)
|
||||
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
contentParentNode.applyAbsoluteOffsetSpring?(-contentContainerOffset.y, springDuration, springDamping)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(result: ContextMenuActionResult, completion: @escaping () -> Void) {
|
||||
func animateOut(result initialResult: ContextMenuActionResult, completion: @escaping () -> Void) {
|
||||
var transitionDuration: Double = 0.2
|
||||
var transitionCurve: ContainedViewLayoutTransitionCurve = .easeInOut
|
||||
|
||||
var result = initialResult
|
||||
let putBackInfo = self.source.putBack()
|
||||
|
||||
if putBackInfo == nil {
|
||||
result = .dismissWithoutContent
|
||||
}
|
||||
|
||||
switch result {
|
||||
case let .custom(value):
|
||||
switch value {
|
||||
case let .animated(duration, curve):
|
||||
transitionDuration = duration
|
||||
transitionCurve = curve
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
self.isAnimatingOut = true
|
||||
|
||||
self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false)
|
||||
|
||||
var completedEffect = false
|
||||
var completedContentNode = false
|
||||
var completedActionsNode = false
|
||||
|
||||
let putBackInfo = self.source.putBack()
|
||||
|
||||
if let putBackInfo = putBackInfo, let contentParentNode = self.contentParentNode, let parentSupernode = contentParentNode.supernode {
|
||||
self.originalProjectedContentViewFrame = (parentSupernode.view.convert(contentParentNode.frame, to: self.view), contentParentNode.view.convert(contentParentNode.contentRect, to: self.view))
|
||||
self.originalProjectedContentViewFrame = (convertFrame(contentParentNode.frame, from: parentSupernode.view, to: self.view), convertFrame(contentParentNode.contentRect, from: contentParentNode.view, to: self.view))
|
||||
|
||||
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: putBackInfo.contentAreaInScreenSpace, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
|
||||
var updatedContentAreaInScreenSpace = putBackInfo.contentAreaInScreenSpace
|
||||
updatedContentAreaInScreenSpace.origin.x = 0.0
|
||||
updatedContentAreaInScreenSpace.size.width = self.bounds.width
|
||||
|
||||
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
let contentParentNode = self.contentParentNode
|
||||
|
|
@ -304,7 +442,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
let intermediateCompletion: () -> Void = { [weak contentParentNode] in
|
||||
if completedEffect && completedContentNode && completedActionsNode {
|
||||
switch result {
|
||||
case .default:
|
||||
case .default, .custom:
|
||||
if let contentParentNode = contentParentNode {
|
||||
contentParentNode.addSubnode(contentParentNode.contentNode)
|
||||
contentParentNode.isExtractedToContextPreview = false
|
||||
|
|
@ -318,18 +456,28 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
}
|
||||
}
|
||||
|
||||
if let propertyAnimator = self.propertyAnimator {
|
||||
if #available(iOS 10.0, *) {
|
||||
if let propertyAnimator = self.propertyAnimator {
|
||||
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
|
||||
propertyAnimator?.stopAnimation(true)
|
||||
}
|
||||
self.propertyAnimator = UIViewPropertyAnimator(duration: transitionDuration, curve: .easeInOut, animations: { [weak self] in
|
||||
self?.effectView.effect = nil
|
||||
})
|
||||
}
|
||||
|
||||
if let _ = self.propertyAnimator {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2, from: (propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete ?? 0.2, to: 0.0, update: { [weak self] value in
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 0.999, update: { [weak self] value in
|
||||
(self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value
|
||||
}, completion: { [weak self] in
|
||||
self?.effectView.isHidden = true
|
||||
}, completion: {
|
||||
completedEffect = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
}
|
||||
self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.05 * animationDurationFactor, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
UIView.animate(withDuration: 0.21 * animationDurationFactor, animations: {
|
||||
if #available(iOS 9.0, *) {
|
||||
self.effectView.effect = nil
|
||||
} else {
|
||||
|
|
@ -341,22 +489,35 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
})
|
||||
}
|
||||
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
|
||||
completedActionsNode = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
self.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
|
||||
if case .default = result, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode {
|
||||
self.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
|
||||
let animateOutToItem: Bool
|
||||
switch result {
|
||||
case .default, .custom:
|
||||
animateOutToItem = true
|
||||
case .dismissWithoutContent:
|
||||
animateOutToItem = false
|
||||
}
|
||||
|
||||
if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode {
|
||||
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
|
||||
self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: 0.2, removeOnCompletion: false, additive: true)
|
||||
self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true)
|
||||
let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
|
||||
self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completedContentNode = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
contentParentNode.updateAbsoluteRect?(self.contentContainerNode.frame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y + contentContainerOffset.y), self.bounds.size)
|
||||
contentParentNode.applyAbsoluteOffset?(-contentContainerOffset.y, .easeInOut, 0.2)
|
||||
contentParentNode.applyAbsoluteOffset?(-contentContainerOffset.y, transitionCurve, transitionDuration)
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
reactionContextNode.animateOut(to: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size), animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
|
||||
}
|
||||
} else if let contentParentNode = self.contentParentNode {
|
||||
if let snapshotView = contentParentNode.contentNode.view.snapshotContentTree() {
|
||||
self.contentContainerNode.view.addSubview(snapshotView)
|
||||
|
|
@ -366,13 +527,52 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
contentParentNode.isExtractedToContextPreview = false
|
||||
contentParentNode.isExtractedToContextPreviewUpdated?(false)
|
||||
|
||||
self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
self.contentContainerNode.allowsGroupOpacity = true
|
||||
self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false, completion: { _ in
|
||||
completedContentNode = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
//self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateOutToReaction(value: String, into targetNode: ASImageNode, hideNode: Bool, completion: @escaping () -> Void) {
|
||||
guard let reactionContextNode = self.reactionContextNode else {
|
||||
self.animateOut(result: .default, completion: completion)
|
||||
return
|
||||
}
|
||||
var contentCompleted = false
|
||||
var reactionCompleted = false
|
||||
let intermediateCompletion: () -> Void = {
|
||||
if contentCompleted && reactionCompleted {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
self.reactionContextNodeIsAnimatingOut = true
|
||||
self.animateOut(result: .default, completion: {
|
||||
contentCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
reactionContextNode.animateOutToReaction(value: value, targetNode: targetNode, hideNode: hideNode, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.reactionContextNode?.removeFromSupernode()
|
||||
strongSelf.reactionContextNode = nil
|
||||
reactionCompleted = true
|
||||
intermediateCompletion()
|
||||
/*strongSelf.animateOut(result: .default, completion: {
|
||||
reactionCompleted = true
|
||||
intermediateCompletion()
|
||||
})*/
|
||||
})
|
||||
}
|
||||
|
||||
func setItems(controller: ContextController, items: [ContextMenuItem]) {
|
||||
self.items = items
|
||||
|
||||
|
|
@ -392,7 +592,22 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
}
|
||||
}
|
||||
|
||||
func updateTheme(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.dimNode.backgroundColor = theme.contextMenu.dimColor
|
||||
self.actionsContainerNode.updateTheme(theme: theme)
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(layout: validLayout, transition: .immediate, previousActionsContainerNode: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?) {
|
||||
if self.isAnimatingOut {
|
||||
return
|
||||
}
|
||||
|
||||
self.validLayout = layout
|
||||
|
||||
var actionsContainerTransition = transition
|
||||
|
|
@ -406,9 +621,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
let contentActionsSpacing: CGFloat = 11.0
|
||||
let contentActionsSpacing: CGFloat = 7.0
|
||||
let actionsSideInset: CGFloat = 11.0
|
||||
let contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
|
||||
var contentTopInset: CGFloat = max(11.0, layout.statusBarHeight ?? 0.0)
|
||||
if let _ = self.reactionContextNode {
|
||||
contentTopInset += 34.0
|
||||
}
|
||||
let actionsBottomInset: CGFloat = 11.0
|
||||
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode {
|
||||
|
|
@ -452,7 +670,15 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
}
|
||||
}
|
||||
|
||||
contentParentNode.updateAbsoluteRect?(contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y), layout.size)
|
||||
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
|
||||
|
||||
contentParentNode.updateAbsoluteRect?(absoluteContentRect, layout.size)
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
let insets = layout.insets(options: [.statusBar])
|
||||
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX + contentParentNode.contentRect.minX, y: absoluteContentRect.minY + contentParentNode.contentRect.minY), size: contentParentNode.contentRect.size), transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
if let previousActionsContainerNode = previousActionsContainerNode {
|
||||
|
|
@ -470,6 +696,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
previousActionsContainerNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.dismissNode, frame: CGRect(origin: CGPoint(), size: scrollNode.view.contentSize))
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
|
@ -483,20 +711,28 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
|
||||
if self.actionsContainerNode.frame.contains(mappedPoint) {
|
||||
return self.actionsContainerNode.hitTest(self.view.convert(point, to: self.actionsContainerNode.view), with: event)
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
if let result = reactionContextNode.hitTest(self.view.convert(point, to: reactionContextNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
|
||||
if let contentParentNode = self.contentParentNode {
|
||||
let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view)
|
||||
if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) {
|
||||
if result is TextSelectionNodeView {
|
||||
return result
|
||||
} else if contentParentNode.contentRect.contains(contentPoint) {
|
||||
return contentParentNode.contentNode.view
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.dimNode.view
|
||||
if self.actionsContainerNode.frame.contains(mappedPoint) {
|
||||
return self.actionsContainerNode.hitTest(self.view.convert(point, to: self.actionsContainerNode.view), with: event)
|
||||
}
|
||||
|
||||
return self.dismissNode.view
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,10 +760,12 @@ public protocol ContextControllerContentSource: class {
|
|||
}
|
||||
|
||||
public final class ContextController: ViewController {
|
||||
private let account: Account
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
private let source: ContextControllerContentSource
|
||||
private var items: [ContextMenuItem]
|
||||
private var reactionItems: [ReactionContextItem]
|
||||
|
||||
private weak var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
|
|
@ -538,11 +776,15 @@ public final class ContextController: ViewController {
|
|||
return self.displayNode as! ContextControllerNode
|
||||
}
|
||||
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, source: ContextControllerContentSource, items: [ContextMenuItem], recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil) {
|
||||
public var reactionSelected: ((String) -> Void)?
|
||||
|
||||
public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, source: ContextControllerContentSource, items: [ContextMenuItem], reactionItems: [ReactionContextItem], recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil) {
|
||||
self.account = account
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.source = source
|
||||
self.items = items
|
||||
self.reactionItems = reactionItems
|
||||
self.recognizer = recognizer
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
|
@ -555,9 +797,14 @@ public final class ContextController: ViewController {
|
|||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ContextControllerNode(controller: self, theme: self.theme, strings: self.strings, source: self.source, items: self.items, beginDismiss: { [weak self] result in
|
||||
self.displayNode = ContextControllerNode(account: self.account, controller: self, theme: self.theme, strings: self.strings, source: self.source, items: self.items, reactionItems: self.reactionItems, beginDismiss: { [weak self] result in
|
||||
self?.dismiss(result: result, completion: nil)
|
||||
}, recognizer: self.recognizer)
|
||||
}, recognizer: self.recognizer, reactionSelected: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.reactionSelected?(value)
|
||||
})
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
|
@ -587,6 +834,13 @@ public final class ContextController: ViewController {
|
|||
}
|
||||
}
|
||||
|
||||
public func updateTheme(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.updateTheme(theme: theme)
|
||||
}
|
||||
}
|
||||
|
||||
private func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
|
||||
if !self.wasDismissed {
|
||||
self.wasDismissed = true
|
||||
|
|
@ -600,4 +854,14 @@ public final class ContextController: ViewController {
|
|||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.dismiss(result: .default, completion: completion)
|
||||
}
|
||||
|
||||
public func dismissWithReaction(value: String, into targetNode: ASImageNode, hideNode: Bool, completion: (() -> Void)?) {
|
||||
if !self.wasDismissed {
|
||||
self.wasDismissed = true
|
||||
self.controllerNode.animateOutToReaction(value: value, into: targetNode, hideNode: hideNode, completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,6 +225,664 @@
|
|||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
D0208AAC2306E7EB00A23503 /* HockeyappMacAlpha */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = HockeyappMacAlpha;
|
||||
};
|
||||
D0208AAD2306E7EB00A23503 /* HockeyappMacAlpha */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Crc32;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = HockeyappMacAlpha;
|
||||
};
|
||||
D0208AAE2306E7EB00A23503 /* HockeyappMacAlpha */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = crc32mac/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32mac;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = HockeyappMacAlpha;
|
||||
};
|
||||
D0208AAF2306E7F700A23503 /* DebugAppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = DebugAppStore;
|
||||
};
|
||||
D0208AB02306E7F700A23503 /* DebugAppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Crc32;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = DebugAppStore;
|
||||
};
|
||||
D0208AB12306E7F700A23503 /* DebugAppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = crc32mac/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32mac;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = DebugAppStore;
|
||||
};
|
||||
D0208AB22306E7FD00A23503 /* ReleaseAppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = ReleaseAppStore;
|
||||
};
|
||||
D0208AB32306E7FD00A23503 /* ReleaseAppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Crc32;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = ReleaseAppStore;
|
||||
};
|
||||
D0208AB42306E7FD00A23503 /* ReleaseAppStore */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = crc32mac/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32mac;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = ReleaseAppStore;
|
||||
};
|
||||
D0208AB52306E80300A23503 /* ReleaseHockeyapp */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = ReleaseHockeyapp;
|
||||
};
|
||||
D0208AB62306E80300A23503 /* ReleaseHockeyapp */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Crc32;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = ReleaseHockeyapp;
|
||||
};
|
||||
D0208AB72306E80300A23503 /* ReleaseHockeyapp */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
FRAMEWORK_VERSION = A;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = crc32mac/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32mac;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = ReleaseHockeyapp;
|
||||
};
|
||||
D03E456A2305CC310049C28B /* DebugAppStoreLLC */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
|
@ -636,6 +1294,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
|
|
@ -717,6 +1376,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
|
|
@ -792,6 +1452,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
|
|
@ -866,6 +1527,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
|
|
@ -887,7 +1549,11 @@
|
|||
buildConfigurations = (
|
||||
D03E456A2305CC310049C28B /* DebugAppStoreLLC */,
|
||||
D03E456F2305CC4E0049C28B /* DebugHockeyapp */,
|
||||
D0208AAF2306E7F700A23503 /* DebugAppStore */,
|
||||
D0208AAC2306E7EB00A23503 /* HockeyappMacAlpha */,
|
||||
D03E456B2305CC310049C28B /* ReleaseAppStoreLLC */,
|
||||
D0208AB22306E7FD00A23503 /* ReleaseAppStore */,
|
||||
D0208AB52306E80300A23503 /* ReleaseHockeyapp */,
|
||||
D03E45712305CC590049C28B /* ReleaseHockeyappInternal */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
|
|
@ -898,7 +1564,11 @@
|
|||
buildConfigurations = (
|
||||
D03E456D2305CC310049C28B /* DebugAppStoreLLC */,
|
||||
D03E45702305CC4E0049C28B /* DebugHockeyapp */,
|
||||
D0208AB02306E7F700A23503 /* DebugAppStore */,
|
||||
D0208AAD2306E7EB00A23503 /* HockeyappMacAlpha */,
|
||||
D03E456E2305CC310049C28B /* ReleaseAppStoreLLC */,
|
||||
D0208AB32306E7FD00A23503 /* ReleaseAppStore */,
|
||||
D0208AB62306E80300A23503 /* ReleaseHockeyapp */,
|
||||
D03E45722305CC590049C28B /* ReleaseHockeyappInternal */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
|
|
@ -909,7 +1579,11 @@
|
|||
buildConfigurations = (
|
||||
D03E46372306E0BB0049C28B /* DebugAppStoreLLC */,
|
||||
D03E46382306E0BB0049C28B /* DebugHockeyapp */,
|
||||
D0208AB12306E7F700A23503 /* DebugAppStore */,
|
||||
D0208AAE2306E7EB00A23503 /* HockeyappMacAlpha */,
|
||||
D03E46392306E0BB0049C28B /* ReleaseAppStoreLLC */,
|
||||
D0208AB42306E7FD00A23503 /* ReleaseAppStore */,
|
||||
D0208AB72306E80300A23503 /* ReleaseHockeyapp */,
|
||||
D03E463A2306E0BB0049C28B /* ReleaseHockeyappInternal */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
|
|
|
|||
|
|
@ -223,6 +223,8 @@ public final class DeviceAccess {
|
|||
subscriber.putNext(.denied)
|
||||
case .notDetermined:
|
||||
subscriber.putNext(.notDetermined)
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
|
|
@ -333,6 +335,8 @@ public final class DeviceAccess {
|
|||
value = false
|
||||
case .authorized:
|
||||
value = true
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
let _ = cachedMediaLibraryAccessStatus.swap(value)
|
||||
continueWithValue(value)
|
||||
|
|
@ -376,7 +380,9 @@ public final class DeviceAccess {
|
|||
}
|
||||
case .notDetermined:
|
||||
completion(true)
|
||||
}
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
case .contacts:
|
||||
let _ = (self.contactsPromise.get()
|
||||
|> take(1)
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ public extension CALayer {
|
|||
self.add(animation, forKey: keyPath)
|
||||
}
|
||||
|
||||
public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double = 0.0, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
let animation: CABasicAnimation
|
||||
if #available(iOS 9.0, *) {
|
||||
animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping)
|
||||
|
|
@ -177,6 +177,11 @@ public extension CALayer {
|
|||
speed = Float(1.0) / k
|
||||
}
|
||||
|
||||
if !delay.isZero {
|
||||
animation.beginTime = CACurrentMediaTime() + delay
|
||||
animation.fillMode = .both
|
||||
}
|
||||
|
||||
animation.speed = speed * Float(animation.duration / duration)
|
||||
animation.isAdditive = additive
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,17 @@ public protocol ContainableController: class {
|
|||
var displayNode: ASDisplayNode { get }
|
||||
var isViewLoaded: Bool { get }
|
||||
var isOpaqueWhenInOverlay: Bool { get }
|
||||
var isModalWhenInOverlay: Bool { get }
|
||||
var blocksBackgroundWhenInOverlay: Bool { get }
|
||||
var ready: Promise<Bool> { get }
|
||||
var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)? { get set }
|
||||
|
||||
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations
|
||||
var deferScreenEdgeGestures: UIRectEdge { get }
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
|
||||
func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation)
|
||||
func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition)
|
||||
|
||||
func viewWillAppear(_ animated: Bool)
|
||||
func viewWillDisappear(_ animated: Bool)
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ public extension ContainedViewLayoutTransition {
|
|||
}
|
||||
}
|
||||
|
||||
func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
func updateAlpha(node: ASDisplayNode, alpha: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if node.alpha.isEqual(to: alpha) {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
|
|
@ -393,7 +393,12 @@ public extension ContainedViewLayoutTransition {
|
|||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let previousAlpha = node.alpha
|
||||
let previousAlpha: CGFloat
|
||||
if beginWithCurrentState, let presentation = node.layer.presentation() {
|
||||
previousAlpha = CGFloat(presentation.opacity)
|
||||
} else {
|
||||
previousAlpha = node.alpha
|
||||
}
|
||||
node.alpha = alpha
|
||||
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
|
||||
if let completion = completion {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ final class ContextMenuActionNode: ASDisplayNode {
|
|||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
self.backgroundColor = UIColor(rgb: 0x2f2f2f)
|
||||
if let textNode = self.textNode {
|
||||
self.addSubnode(textNode)
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ final class ContextMenuActionNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
self.button.highligthedChanged = { [weak self] highlighted in
|
||||
self?.backgroundColor = highlighted ? UIColor(white: 0.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.8)
|
||||
self?.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0x2f2f2f)
|
||||
}
|
||||
self.view.addSubview(self.button)
|
||||
self.addSubnode(self.actionArea)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public final class ContextMenuContainerNode: ASDisplayNode {
|
|||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = UIColor(rgb: 0xeaecec)
|
||||
self.backgroundColor = UIColor(rgb: 0x8c8e8e)
|
||||
//self.view.addSubview(self.effectView)
|
||||
//self.effectView.mask = self.maskView
|
||||
self.view.mask = self.maskView
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ private final class ContextMenuContentScrollNode: ASDisplayNode {
|
|||
self.rightShadow.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
|
||||
self.leftOverscrollNode = ASDisplayNode()
|
||||
self.leftOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
//self.leftOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
self.rightOverscrollNode = ASDisplayNode()
|
||||
self.rightOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
//self.rightOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -55,8 +55,8 @@ private final class ContextMenuContentScrollNode: ASDisplayNode {
|
|||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
self.view.addGestureRecognizer(panRecognizer)
|
||||
//let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
//self.view.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
|
||||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
|
|
@ -228,9 +228,12 @@ final class ContextMenuNode: ASDisplayNode {
|
|||
self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: containerPosition.x, y: containerPosition.y + (self.arrowOnBottom ? 1.0 : -1.0) * self.containerNode.bounds.size.height / 2.0)), to: NSValue(cgPoint: containerPosition), keyPath: "position", duration: 0.4)
|
||||
}
|
||||
|
||||
self.containerNode.allowsGroupOpacity = true
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in
|
||||
self?.containerNode.allowsGroupOpacity = false
|
||||
self.allowsGroupOpacity = true
|
||||
self.layer.rasterizationScale = UIScreen.main.scale
|
||||
self.layer.shouldRasterize = true
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in
|
||||
self?.allowsGroupOpacity = false
|
||||
self?.layer.shouldRasterize = false
|
||||
})
|
||||
|
||||
if let feedback = self.feedback {
|
||||
|
|
@ -239,9 +242,12 @@ final class ContextMenuNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
func animateOut(bounce: Bool, completion: @escaping () -> Void) {
|
||||
self.containerNode.allowsGroupOpacity = true
|
||||
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.containerNode.allowsGroupOpacity = false
|
||||
self.allowsGroupOpacity = true
|
||||
self.layer.rasterizationScale = UIScreen.main.scale
|
||||
self.layer.shouldRasterize = true
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.allowsGroupOpacity = false
|
||||
self?.layer.shouldRasterize = false
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,30 @@ private func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) ->
|
|||
}
|
||||
}
|
||||
|
||||
private final class HierarchyTrackingNode: ASDisplayNode {
|
||||
private let f: (Bool) -> Void
|
||||
|
||||
init(_ f: @escaping (Bool) -> Void) {
|
||||
self.f = f
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
}
|
||||
|
||||
override func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
|
||||
self.f(true)
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.f(false)
|
||||
}
|
||||
}
|
||||
|
||||
final class GlobalOverlayPresentationContext {
|
||||
private let statusBarHost: StatusBarHost?
|
||||
private weak var parentView: UIView?
|
||||
|
|
@ -41,9 +65,34 @@ final class GlobalOverlayPresentationContext {
|
|||
self.parentView = parentView
|
||||
}
|
||||
|
||||
private var currentTrackingNode: HierarchyTrackingNode?
|
||||
|
||||
private func currentPresentationView(underStatusBar: Bool) -> UIView? {
|
||||
if let statusBarHost = self.statusBarHost {
|
||||
if let keyboardWindow = statusBarHost.keyboardWindow, let keyboardView = statusBarHost.keyboardView, !keyboardView.frame.height.isZero, isViewVisibleInHierarchy(keyboardView) {
|
||||
var updateTrackingNode = false
|
||||
if let trackingNode = self.currentTrackingNode {
|
||||
if trackingNode.layer.superlayer !== keyboardView.layer {
|
||||
updateTrackingNode = true
|
||||
}
|
||||
} else {
|
||||
updateTrackingNode = true
|
||||
}
|
||||
|
||||
if updateTrackingNode {
|
||||
/*self.currentTrackingNode?.removeFromSupernode()
|
||||
let trackingNode = HierarchyTrackingNode({ [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if !value {
|
||||
strongSelf.addViews(justMove: true)
|
||||
}
|
||||
})
|
||||
|
||||
self.currentTrackingNode = trackingNode
|
||||
keyboardView.layer.addSublayer(trackingNode.layer)*/
|
||||
}
|
||||
return keyboardWindow
|
||||
} else {
|
||||
if underStatusBar, let view = self.parentView {
|
||||
|
|
@ -69,8 +118,8 @@ final class GlobalOverlayPresentationContext {
|
|||
underStatusBar = true
|
||||
}
|
||||
}
|
||||
if let _ = self.currentPresentationView(underStatusBar: underStatusBar), let initialLayout = self.layout {
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
|
||||
if let presentationView = self.currentPresentationView(underStatusBar: underStatusBar), let initialLayout = self.layout {
|
||||
controller.view.frame = CGRect(origin: CGPoint(x: presentationView.bounds.width - initialLayout.size.width, y: 0.0), size: initialLayout.size)
|
||||
controller.containerLayoutUpdated(initialLayout, transition: .immediate)
|
||||
|
||||
self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in
|
||||
|
|
@ -88,7 +137,7 @@ final class GlobalOverlayPresentationContext {
|
|||
}, rootController: nil)
|
||||
(controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
|
||||
if layout != initialLayout {
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size)
|
||||
view.addSubview(controller.view)
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
} else {
|
||||
|
|
@ -134,13 +183,13 @@ final class GlobalOverlayPresentationContext {
|
|||
|
||||
private func readyChanged(wasReady: Bool) {
|
||||
if !wasReady {
|
||||
self.addViews()
|
||||
self.addViews(justMove: false)
|
||||
} else {
|
||||
self.removeViews()
|
||||
}
|
||||
}
|
||||
|
||||
private func addViews() {
|
||||
private func addViews(justMove: Bool) {
|
||||
if let layout = self.layout {
|
||||
for controller in self.controllers {
|
||||
var underStatusBar = false
|
||||
|
|
@ -150,11 +199,15 @@ final class GlobalOverlayPresentationContext {
|
|||
}
|
||||
}
|
||||
if let view = self.currentPresentationView(underStatusBar: underStatusBar) {
|
||||
controller.viewWillAppear(false)
|
||||
if !justMove {
|
||||
controller.viewWillAppear(false)
|
||||
}
|
||||
view.addSubview(controller.view)
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
controller.viewDidAppear(false)
|
||||
if !justMove {
|
||||
controller.view.frame = CGRect(origin: CGPoint(x: view.bounds.width - layout.size.width, y: 0.0), size: layout.size)
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
controller.viewDidAppear(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -763,7 +763,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
|||
var lowestHeaderNode: ASDisplayNode?
|
||||
var lowestHeaderNodeIndex: Int?
|
||||
for (_, headerNode) in self.sectionNodes {
|
||||
if let index = self.subnodes?.index(of: headerNode) {
|
||||
if let index = self.subnodes?.firstIndex(of: headerNode) {
|
||||
if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! {
|
||||
lowestHeaderNodeIndex = index
|
||||
lowestHeaderNode = headerNode
|
||||
|
|
|
|||
|
|
@ -19,6 +19,26 @@ private func getFirstResponder(_ view: UIView) -> UIView? {
|
|||
}
|
||||
}
|
||||
|
||||
private func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) -> Bool {
|
||||
guard let window = view.window else {
|
||||
return false
|
||||
}
|
||||
if view.isHidden || view.alpha == 0.0 {
|
||||
return false
|
||||
}
|
||||
if view.superview === window {
|
||||
return true
|
||||
} else if let superview = view.superview {
|
||||
if initial && view.frame.minY >= superview.frame.height {
|
||||
return false
|
||||
} else {
|
||||
return isViewVisibleInHierarchy(superview, false)
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class KeyboardManager {
|
||||
private let host: StatusBarHost
|
||||
|
||||
|
|
@ -40,6 +60,9 @@ class KeyboardManager {
|
|||
guard let keyboardView = self.host.keyboardView else {
|
||||
return 0.0
|
||||
}
|
||||
if !isViewVisibleInHierarchy(keyboardView) {
|
||||
return 0.0
|
||||
}
|
||||
return keyboardView.bounds.height
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1059,7 +1059,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||
if self.stackFromBottom {
|
||||
topOffset = self.visibleSize.height
|
||||
} else {
|
||||
topOffset = 0.0
|
||||
topOffset = self.insets.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1155,52 +1155,51 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||
private func updateScroller(transition: ContainedViewLayoutTransition) {
|
||||
self.updateOverlayHighlight(transition: transition)
|
||||
|
||||
if self.itemNodes.count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var topItemFound: Bool = false
|
||||
var bottomItemFound: Bool = false
|
||||
var topItemEdge: CGFloat = 0.0
|
||||
var bottomItemEdge: CGFloat = 0.0
|
||||
var completeHeight: CGFloat = 0.0
|
||||
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
if let index = self.itemNodes[i].index {
|
||||
if index == 0 {
|
||||
topItemFound = true
|
||||
topItemEdge = self.itemNodes[0].apparentFrame.origin.y
|
||||
break
|
||||
if !self.itemNodes.isEmpty {
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
if let index = self.itemNodes[i].index {
|
||||
if index == 0 {
|
||||
topItemFound = true
|
||||
topItemEdge = self.itemNodes[0].apparentFrame.origin.y
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var effectiveInsets = self.insets
|
||||
if topItemFound && !self.stackFromBottomInsetItemFactor.isZero {
|
||||
let additionalInverseTopInset = self.calculateAdditionalTopInverseInset()
|
||||
effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset)
|
||||
}
|
||||
|
||||
var completeHeight = effectiveInsets.top + effectiveInsets.bottom
|
||||
|
||||
if let index = self.itemNodes[self.itemNodes.count - 1].index, index == self.items.count - 1 {
|
||||
bottomItemFound = true
|
||||
bottomItemEdge = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY
|
||||
}
|
||||
|
||||
topItemEdge -= effectiveInsets.top
|
||||
bottomItemEdge += effectiveInsets.bottom
|
||||
|
||||
if topItemFound && bottomItemFound {
|
||||
for itemNode in self.itemNodes {
|
||||
completeHeight += itemNode.apparentBounds.height
|
||||
|
||||
var effectiveInsets = self.insets
|
||||
if topItemFound && !self.stackFromBottomInsetItemFactor.isZero {
|
||||
let additionalInverseTopInset = self.calculateAdditionalTopInverseInset()
|
||||
effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset)
|
||||
}
|
||||
|
||||
if self.stackFromBottom {
|
||||
let updatedCompleteHeight = max(completeHeight, self.visibleSize.height)
|
||||
let deltaCompleteHeight = updatedCompleteHeight - completeHeight
|
||||
topItemEdge -= deltaCompleteHeight
|
||||
bottomItemEdge -= deltaCompleteHeight
|
||||
completeHeight = updatedCompleteHeight
|
||||
completeHeight = effectiveInsets.top + effectiveInsets.bottom
|
||||
|
||||
if let index = self.itemNodes[self.itemNodes.count - 1].index, index == self.items.count - 1 {
|
||||
bottomItemFound = true
|
||||
bottomItemEdge = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY
|
||||
}
|
||||
|
||||
topItemEdge -= effectiveInsets.top
|
||||
bottomItemEdge += effectiveInsets.bottom
|
||||
|
||||
if topItemFound && bottomItemFound {
|
||||
for itemNode in self.itemNodes {
|
||||
completeHeight += itemNode.apparentBounds.height
|
||||
}
|
||||
|
||||
if self.stackFromBottom {
|
||||
let updatedCompleteHeight = max(completeHeight, self.visibleSize.height)
|
||||
let deltaCompleteHeight = updatedCompleteHeight - completeHeight
|
||||
topItemEdge -= deltaCompleteHeight
|
||||
bottomItemEdge -= deltaCompleteHeight
|
||||
completeHeight = updatedCompleteHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2015,7 +2014,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
|||
lowestHeaderNode = self.verticalScrollIndicator
|
||||
var lowestHeaderNodeIndex: Int?
|
||||
for (_, headerNode) in self.itemHeaderNodes {
|
||||
if let index = self.view.subviews.index(of: headerNode.view) {
|
||||
if let index = self.view.subviews.firstIndex(of: headerNode.view) {
|
||||
if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! {
|
||||
lowestHeaderNodeIndex = index
|
||||
lowestHeaderNode = headerNode
|
||||
|
|
|
|||
|
|
@ -347,6 +347,13 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView, UI
|
|||
rootViewController.viewDidAppear(false)
|
||||
|
||||
let aboveStatusbarWindow = AboveStatusBarWindow(frame: UIScreen.main.bounds)
|
||||
aboveStatusbarWindow.supportedOrientations = { [weak rootViewController] in
|
||||
if let rootViewController = rootViewController {
|
||||
return rootViewController.supportedInterfaceOrientations
|
||||
} else {
|
||||
return .portrait
|
||||
}
|
||||
}
|
||||
|
||||
let hostView = WindowHostView(containerView: rootViewController.view, eventView: window, aboveStatusBarView: rootViewController.view, isRotating: {
|
||||
return window.isRotating()
|
||||
|
|
|
|||
|
|
@ -290,14 +290,14 @@ final class NavigationButtonNode: ASDisplayNode {
|
|||
node.color = self.color
|
||||
node.highlightChanged = { [weak node, weak self] value in
|
||||
if let strongSelf = self, let node = node {
|
||||
if let index = strongSelf.nodes.index(where: { $0 === node }) {
|
||||
if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) {
|
||||
strongSelf.highlightChanged(index, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
node.pressed = { [weak self, weak node] in
|
||||
if let strongSelf = self, let node = node {
|
||||
if let index = strongSelf.nodes.index(where: { $0 === node }) {
|
||||
if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) {
|
||||
strongSelf.pressed(index)
|
||||
}
|
||||
}
|
||||
|
|
@ -339,14 +339,14 @@ final class NavigationButtonNode: ASDisplayNode {
|
|||
node.color = self.color
|
||||
node.highlightChanged = { [weak node, weak self] value in
|
||||
if let strongSelf = self, let node = node {
|
||||
if let index = strongSelf.nodes.index(where: { $0 === node }) {
|
||||
if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) {
|
||||
strongSelf.highlightChanged(index, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
node.pressed = { [weak self, weak node] in
|
||||
if let strongSelf = self, let node = node {
|
||||
if let index = strongSelf.nodes.index(where: { $0 === node }) {
|
||||
if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) {
|
||||
strongSelf.pressed(index)
|
||||
}
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ final class NavigationButtonNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
func updateLayout(constrainedSize: CGSize) -> CGSize {
|
||||
func updateLayout(constrainedSize: CGSize) -> CGSize {
|
||||
var nodeOrigin = CGPoint()
|
||||
var totalSize = CGSize()
|
||||
for node in self.nodes {
|
||||
|
|
@ -382,7 +382,7 @@ final class NavigationButtonNode: ASDisplayNode {
|
|||
nodeSize.height = ceil(nodeSize.height)
|
||||
totalSize.width += nodeSize.width
|
||||
totalSize.height = max(totalSize.height, nodeSize.height)
|
||||
node.frame = CGRect(origin: nodeOrigin, size: nodeSize)
|
||||
node.frame = CGRect(origin: CGPoint(x: nodeOrigin.x, y: floor((totalSize.height - nodeSize.height) / 2.0)), size: nodeSize)
|
||||
nodeOrigin.x += node.bounds.width
|
||||
}
|
||||
return totalSize
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ private final class NavigationControllerView: UITracingLayerView {
|
|||
var navigationBackgroundView: UIView?
|
||||
var navigationSeparatorView: UIView?
|
||||
var emptyDetailView: UIImageView?
|
||||
var detailsBackground: WallpaperbackgroundNode?
|
||||
var detailsBackground: WallpaperBackgroundNode?
|
||||
var masterDetailsBlackout: ASDisplayNode?
|
||||
var topControllerNode: ASDisplayNode?
|
||||
|
||||
|
|
@ -120,6 +120,8 @@ public enum MasterDetailLayoutBlackout : Equatable {
|
|||
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
|
||||
public var isOpaqueWhenInOverlay: Bool = true
|
||||
public var blocksBackgroundWhenInOverlay: Bool = true
|
||||
public var isModalWhenInOverlay: Bool = false
|
||||
public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
private let _ready = Promise<Bool>(true)
|
||||
open var ready: Promise<Bool> {
|
||||
|
|
@ -332,11 +334,11 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
emptyDetailView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
let detailsBackground: WallpaperbackgroundNode
|
||||
let detailsBackground: WallpaperBackgroundNode
|
||||
if let background = self.controllerView.detailsBackground {
|
||||
detailsBackground = background
|
||||
} else {
|
||||
detailsBackground = WallpaperbackgroundNode()
|
||||
detailsBackground = WallpaperBackgroundNode()
|
||||
detailsBackground.alpha = 0.0
|
||||
self.controllerView.detailsBackground = detailsBackground
|
||||
}
|
||||
|
|
@ -362,44 +364,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
}
|
||||
}
|
||||
|
||||
if let _ = self.controllerView.emptyDetailView {
|
||||
// transition.updateFrame(view: navigationBackgroundView, frame: navigationBackgroundFrame)
|
||||
// transition.updateFrame(view: navigationSeparatorView, frame: CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel)))
|
||||
// if let image = emptyDetailView.image {
|
||||
// transition.updateFrame(view: emptyDetailView, frame: CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size))
|
||||
// }
|
||||
} else {
|
||||
// let navigationBackgroundView = UIView()
|
||||
// navigationBackgroundView.backgroundColor = self.theme.navigationBar.c
|
||||
// let navigationSeparatorView = UIView()
|
||||
// navigationSeparatorView.backgroundColor = self.theme.navigationBar.separatorColor
|
||||
// let emptyDetailView = UIImageView()
|
||||
// emptyDetailView.image = self.theme.emptyDetailIcon
|
||||
// emptyDetailView.alpha = 0.0
|
||||
//
|
||||
// self.controllerView.navigationBackgroundView = navigationBackgroundView
|
||||
// self.controllerView.navigationSeparatorView = navigationSeparatorView
|
||||
// self.controllerView.emptyDetailView = emptyDetailView
|
||||
//
|
||||
// self.controllerView.insertSubview(navigationBackgroundView, at: 0)
|
||||
// self.controllerView.insertSubview(navigationSeparatorView, at: 1)
|
||||
// self.controllerView.insertSubview(emptyDetailView, at: 0)
|
||||
|
||||
// navigationBackgroundView.frame = navigationBackgroundFrame
|
||||
// navigationSeparatorView.frame = CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel))
|
||||
//
|
||||
// transition.animatePositionAdditive(layer: navigationBackgroundView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0))
|
||||
// transition.animatePositionAdditive(layer: navigationSeparatorView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0))
|
||||
|
||||
// if let image = emptyDetailView.image {
|
||||
// emptyDetailView.frame = CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size)
|
||||
// }
|
||||
//
|
||||
// transition.updateAlpha(layer: emptyDetailView.layer, alpha: 1.0)
|
||||
}
|
||||
|
||||
if let blackout = self.masterDetailsBlackout {
|
||||
|
||||
let blackoutFrame: CGRect
|
||||
switch blackout {
|
||||
case .details:
|
||||
|
|
@ -489,15 +454,15 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
} else if case .masterDetail = layoutConfiguration, i == 1 {
|
||||
controller.navigationBar?.previousItem = .close
|
||||
} else {
|
||||
controller.navigationBar?.previousItem = .item(viewControllers[i - 1].navigationItem)
|
||||
controller.navigationBar?.previousItem = .item(self.viewControllers[i - 1].navigationItem)
|
||||
}
|
||||
if i < self._viewControllers.count - 1 {
|
||||
controller.updateNavigationCustomData((viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0, transition: transition)
|
||||
controller.updateNavigationCustomData((self.viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0, transition: transition)
|
||||
} else {
|
||||
controller.updateNavigationCustomData(nil, progress: 1.0, transition: transition)
|
||||
}
|
||||
}
|
||||
viewControllers[i].navigation_setNavigationController(self)
|
||||
self.viewControllers[i].navigation_setNavigationController(self)
|
||||
|
||||
if i == 0, let (_, layout) = firstControllerFrameAndLayout {
|
||||
controllersAndFrames.append((true, self._viewControllers[i], layout))
|
||||
|
|
@ -522,7 +487,15 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
frame = lastControllerFrameAndLayout.0
|
||||
}
|
||||
let isAppearing = record.controller.view.superview == nil
|
||||
(record.controller as? ViewController)?.containerLayoutUpdated(layout, transition: isAppearing ? .immediate : transition)
|
||||
if let controller = record.controller as? ViewController {
|
||||
let updatedLayout: ContainerViewLayout
|
||||
if previousControllers.count == self.viewControllers.count + 1, previousControllers[previousControllers.count - 2].controller === controller {
|
||||
updatedLayout = layout.withUpdatedInputHeight(controller.hasActiveInput ? layout.inputHeight : nil)
|
||||
} else {
|
||||
updatedLayout = layout
|
||||
}
|
||||
controller.containerLayoutUpdated(updatedLayout, transition: isAppearing ? .immediate : transition)
|
||||
}
|
||||
if isAppearing {
|
||||
if isMaster {
|
||||
appearingMasterController = record
|
||||
|
|
@ -576,7 +549,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
self.controllerView.containerView.addSubview(record.controller.view)
|
||||
record.controller.setIgnoreAppearanceMethodInvocations(false)
|
||||
|
||||
if let _ = previousControllers.index(where: { $0.controller === record.controller }) {
|
||||
if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) {
|
||||
//previousControllers[index].transition = .appearance
|
||||
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar)
|
||||
self.navigationTransitionCoordinator = navigationTransitionCoordinator
|
||||
|
|
@ -596,7 +569,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
}
|
||||
})
|
||||
} else {
|
||||
if let index = self._viewControllers.index(where: { $0.controller === previousController }) {
|
||||
if let index = self._viewControllers.firstIndex(where: { $0.controller === previousController }) {
|
||||
self._viewControllers[index].transition = .appearance
|
||||
}
|
||||
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.containerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar)
|
||||
|
|
@ -605,7 +578,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
self.controllerView.inTransition = true
|
||||
navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let index = strongSelf._viewControllers.index(where: { $0.controller === previousController }) {
|
||||
if let index = strongSelf._viewControllers.firstIndex(where: { $0.controller === previousController }) {
|
||||
strongSelf._viewControllers[index].transition = .none
|
||||
}
|
||||
strongSelf.navigationTransitionCoordinator = nil
|
||||
|
|
@ -757,6 +730,33 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
}
|
||||
}
|
||||
|
||||
private var modalTransition: CGFloat = 0.0
|
||||
|
||||
public func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.modalTransition == value {
|
||||
return
|
||||
}
|
||||
let scale = (self.view.bounds.width - 20.0 * 2.0) / self.view.bounds.width
|
||||
let cornerRadius = value * 10.0 / scale
|
||||
switch transition {
|
||||
case let .animated(duration, curve):
|
||||
let previous = self.displayNode.layer.cornerRadius
|
||||
self.displayNode.layer.cornerRadius = cornerRadius
|
||||
if !cornerRadius.isZero {
|
||||
self.displayNode.clipsToBounds = true
|
||||
}
|
||||
self.displayNode.layer.animate(from: previous as NSNumber, to: cornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, completion: { [weak self] _ in
|
||||
if cornerRadius.isZero {
|
||||
self?.displayNode.clipsToBounds = false
|
||||
}
|
||||
})
|
||||
case .immediate:
|
||||
self.displayNode.layer.cornerRadius = cornerRadius
|
||||
self.displayNode.clipsToBounds = !cornerRadius.isZero
|
||||
}
|
||||
self.modalTransition = value
|
||||
}
|
||||
|
||||
public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
|
||||
for record in self._viewControllers {
|
||||
if let controller = record.controller as? ContainableController {
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ open class TabBarController: ViewController {
|
|||
public func setControllers(_ controllers: [ViewController], selectedIndex: Int?) {
|
||||
var updatedSelectedIndex: Int? = selectedIndex
|
||||
if updatedSelectedIndex == nil, let selectedIndex = self._selectedIndex, selectedIndex < self.controllers.count {
|
||||
if let index = controllers.index(where: { $0 === self.controllers[selectedIndex] }) {
|
||||
if let index = controllers.firstIndex(where: { $0 === self.controllers[selectedIndex] }) {
|
||||
updatedSelectedIndex = index
|
||||
} else {
|
||||
updatedSelectedIndex = 0
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
|||
}
|
||||
context.restoreGState()
|
||||
} else {
|
||||
let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize)
|
||||
let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0), size: imageSize)
|
||||
context.saveGState()
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
|
|
@ -72,9 +72,9 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
|||
|
||||
if !imageMode {
|
||||
if horizontal {
|
||||
(title as NSString).draw(at: CGPoint(x: imageSize.width + horizontalSpacing, y: floor((size.height - titleSize.height) / 2.0) - 2.0), withAttributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: tintColor])
|
||||
(title as NSString).draw(at: CGPoint(x: imageSize.width + horizontalSpacing, y: floor((size.height - titleSize.height) / 2.0)), withAttributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: tintColor])
|
||||
} else {
|
||||
(title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: tintColor])
|
||||
(title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 1.0), withAttributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: tintColor])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ class TabBarNode: ASDisplayNode {
|
|||
let nodeSize = node.textImageNode.image?.size ?? CGSize()
|
||||
|
||||
let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - nodeSize.width / 2.0)
|
||||
let nodeFrame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize)
|
||||
let nodeFrame = CGRect(origin: CGPoint(x: originX, y: 3.0), size: nodeSize)
|
||||
transition.updateFrame(node: node, frame: nodeFrame)
|
||||
node.imageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
|
||||
node.textImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size)
|
||||
|
|
@ -430,10 +430,10 @@ class TabBarNode: ASDisplayNode {
|
|||
let backgroundSize = CGSize(width: hasSingleLetterValue ? 18.0 : max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0)
|
||||
let backgroundFrame: CGRect
|
||||
if horizontal {
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: originX + 8.0, y: 2.0), size: backgroundSize)
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: originX + 10.0, y: 2.0), size: backgroundSize)
|
||||
} else {
|
||||
let contentWidth = node.contentWidth ?? node.frame.width
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 1.0 + contentWidth - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize)
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) + contentWidth - backgroundSize.width - 5.0, y: 2.0), size: backgroundSize)
|
||||
}
|
||||
transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame)
|
||||
container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
|
|||
self.lastRecognizedGestureAndLocation = (.longTap, location)
|
||||
if let longTap = self.longTap {
|
||||
self.recognizedLongTap = true
|
||||
self.state = .began
|
||||
longTap(location, self)
|
||||
cancelScrollViewGestures(view: self.view?.superview)
|
||||
self.state = .began
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ private func displayLineFrame(frame: CGRect, isRTL: Bool, boundingRect: CGRect,
|
|||
return frame
|
||||
}
|
||||
var lineFrame = frame
|
||||
let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.height)
|
||||
if isRTL {
|
||||
lineFrame.origin.x = max(0.0, floor(boundingRect.width - lineFrame.size.width))
|
||||
let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.height / 4.5)
|
||||
if let topRight = cutout?.topRight {
|
||||
let topRightRect = CGRect(origin: CGPoint(x: boundingRect.width - topRight.width, y: 0.0), size: topRight)
|
||||
if intersectionFrame.intersects(topRightRect) {
|
||||
|
|
@ -120,6 +120,7 @@ public final class TextNodeLayout: NSObject {
|
|||
fileprivate let cutout: TextNodeCutout?
|
||||
fileprivate let insets: UIEdgeInsets
|
||||
public let size: CGSize
|
||||
public let rawTextSize: CGSize
|
||||
public let truncated: Bool
|
||||
fileprivate let firstLineOffset: CGFloat
|
||||
fileprivate let lines: [TextNodeLine]
|
||||
|
|
@ -128,7 +129,7 @@ public final class TextNodeLayout: NSObject {
|
|||
fileprivate let textShadowColor: UIColor?
|
||||
public let hasRTL: Bool
|
||||
|
||||
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?) {
|
||||
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?) {
|
||||
self.attributedString = attributedString
|
||||
self.maximumNumberOfLines = maximumNumberOfLines
|
||||
self.truncationType = truncationType
|
||||
|
|
@ -138,6 +139,7 @@ public final class TextNodeLayout: NSObject {
|
|||
self.cutout = cutout
|
||||
self.insets = insets
|
||||
self.size = size
|
||||
self.rawTextSize = rawTextSize
|
||||
self.truncated = truncated
|
||||
self.firstLineOffset = firstLineOffset
|
||||
self.lines = lines
|
||||
|
|
@ -223,9 +225,6 @@ public final class TextNodeLayout: NSObject {
|
|||
case .center:
|
||||
lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0)
|
||||
case .natural:
|
||||
if line.isRTL {
|
||||
lineFrame.origin.x = self.size.width - lineFrame.size.width
|
||||
}
|
||||
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout)
|
||||
default:
|
||||
break
|
||||
|
|
@ -294,9 +293,6 @@ public final class TextNodeLayout: NSObject {
|
|||
case .center:
|
||||
lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0)
|
||||
case .natural:
|
||||
if line.isRTL {
|
||||
lineFrame.origin.x = self.size.width - lineFrame.size.width
|
||||
}
|
||||
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout)
|
||||
default:
|
||||
break
|
||||
|
|
@ -372,9 +368,6 @@ public final class TextNodeLayout: NSObject {
|
|||
case .center:
|
||||
lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0)
|
||||
case .natural:
|
||||
if line.isRTL {
|
||||
lineFrame.origin.x = floor(self.size.width - lineFrame.size.width)
|
||||
}
|
||||
lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout)
|
||||
default:
|
||||
break
|
||||
|
|
@ -816,7 +809,7 @@ public class TextNode: ASDisplayNode {
|
|||
var maybeTypesetter: CTTypesetter?
|
||||
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
||||
if maybeTypesetter == nil {
|
||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||
}
|
||||
|
||||
let typesetter = maybeTypesetter!
|
||||
|
|
@ -997,6 +990,7 @@ public class TextNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
let rawLayoutSize = layoutSize
|
||||
if !lines.isEmpty && bottomCutoutEnabled {
|
||||
let proposedWidth = lines[lines.count - 1].frame.width + bottomCutoutSize.width
|
||||
if proposedWidth > layoutSize.width {
|
||||
|
|
@ -1008,9 +1002,9 @@ public class TextNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), rawTextSize: CGSize(width: ceil(rawLayoutSize.width) + insets.left + insets.right, height: ceil(rawLayoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||
} else {
|
||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t)
|
|||
|
||||
@interface CustomBlurEffect : UIBlurEffect
|
||||
|
||||
@property (nonatomic) double blurRadius;
|
||||
/*@property (nonatomic) double blurRadius;
|
||||
@property (nonatomic) double colorBurnTintAlpha;
|
||||
@property (nonatomic) double colorBurnTintLevel;
|
||||
@property (nonatomic, retain) UIColor *colorTint;
|
||||
|
|
@ -104,7 +104,7 @@ CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t)
|
|||
@property (nonatomic) bool lightenGrayscaleWithSourceOver;
|
||||
@property (nonatomic) double saturationDeltaFactor;
|
||||
@property (nonatomic) double scale;
|
||||
@property (nonatomic) double zoom;
|
||||
@property (nonatomic) double zoom;*/
|
||||
|
||||
+ (id)effectWithStyle:(long long)arg1;
|
||||
|
||||
|
|
@ -113,17 +113,68 @@ CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t)
|
|||
void testZoomBlurEffect(UIVisualEffect *effect) {
|
||||
}
|
||||
|
||||
UIBlurEffect *makeCustomZoomBlurEffect() {
|
||||
NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""];
|
||||
CustomBlurEffect *result = (CustomBlurEffect *)[NSClassFromString(string) effectWithStyle:0];
|
||||
result.blurRadius = 9.0;
|
||||
result.zoom = 0.015;
|
||||
result.colorTint = nil;
|
||||
result.colorTintAlpha = 0.0;
|
||||
result.darkeningTintAlpha = 0.0;
|
||||
result.grayscaleTintAlpha = 0.0;
|
||||
result.saturationDeltaFactor = 1.0;
|
||||
result.scale = 0.5;
|
||||
static NSString *encodeText(NSString *string, int key) {
|
||||
NSMutableString *result = [[NSMutableString alloc] init];
|
||||
|
||||
for (int i = 0; i < (int)[string length]; i++) {
|
||||
unichar c = [string characterAtIndex:i];
|
||||
c += key;
|
||||
[result appendString:[NSString stringWithCharacters:&c length:1]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void setField(CustomBlurEffect *object, NSString *name, double value) {
|
||||
SEL selector = NSSelectorFromString(name);
|
||||
NSMethodSignature *signature = [[object class] instanceMethodSignatureForSelector:selector];
|
||||
if (signature == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[inv setSelector:selector];
|
||||
[inv setArgument:&value atIndex:2];
|
||||
[inv setTarget:object];
|
||||
[inv invoke];
|
||||
}
|
||||
|
||||
static void setNilField(CustomBlurEffect *object, NSString *name) {
|
||||
SEL selector = NSSelectorFromString(name);
|
||||
NSMethodSignature *signature = [[object class] instanceMethodSignatureForSelector:selector];
|
||||
if (signature == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[inv setSelector:selector];
|
||||
id value = nil;
|
||||
[inv setArgument:&value atIndex:2];
|
||||
[inv setTarget:object];
|
||||
[inv invoke];
|
||||
}
|
||||
|
||||
UIBlurEffect *makeCustomZoomBlurEffect() {
|
||||
if (@available(iOS 11.0, *)) {
|
||||
NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""];
|
||||
CustomBlurEffect *result = (CustomBlurEffect *)[NSClassFromString(string) effectWithStyle:0];
|
||||
|
||||
setField(result, encodeText(@"tfuCmvsSbejvt;", -1), 10.0);
|
||||
setField(result, encodeText(@"tfu[ppn;", -1), 0.015);
|
||||
setNilField(result, encodeText(@"tfuDpmpsUjou;", -1));
|
||||
setField(result, encodeText(@"tfuDpmpsUjouBmqib;", -1), 0.0);
|
||||
setField(result, encodeText(@"tfuEbslfojohUjouBmqib;", -1), 0.0);
|
||||
setField(result, encodeText(@"tfuHsbztdbmfUjouBmqib;", -1), 0.0);
|
||||
setField(result, encodeText(@"tfuTbuvsbujpoEfmubGbdups;", -1), 1.0);
|
||||
|
||||
if ([UIScreen mainScreen].scale > 2.5f) {
|
||||
setField(result, encodeText(@"setScale:", 0), 0.3);
|
||||
} else {
|
||||
setField(result, encodeText(@"setScale:", 0), 0.5);
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return [UIBlurEffect effectWithStyle:UIBlurEffectStyleRegular];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,9 +83,13 @@ public extension UIColor {
|
|||
var red: CGFloat = 0.0
|
||||
var green: CGFloat = 0.0
|
||||
var blue: CGFloat = 0.0
|
||||
self.getRed(&red, green: &green, blue: &blue, alpha: nil)
|
||||
|
||||
return (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0))
|
||||
if self.getRed(&red, green: &green, blue: &blue, alpha: nil) {
|
||||
return (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, green) * 255.0) << 8) | (UInt32(max(0.0, blue) * 255.0))
|
||||
} else if self.getWhite(&red, alpha: nil) {
|
||||
return (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, red) * 255.0) << 8) | (UInt32(max(0.0, red) * 255.0))
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
var argb: UInt32 {
|
||||
|
|
@ -93,9 +97,13 @@ public extension UIColor {
|
|||
var green: CGFloat = 0.0
|
||||
var blue: CGFloat = 0.0
|
||||
var alpha: CGFloat = 0.0
|
||||
self.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
||||
|
||||
return (UInt32(alpha * 255.0) << 24) | (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0))
|
||||
if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
|
||||
return (UInt32(alpha * 255.0) << 24) | (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, green) * 255.0) << 8) | (UInt32(max(0.0, blue) * 255.0))
|
||||
} else if self.getWhite(&red, alpha: &alpha) {
|
||||
return (UInt32(max(0.0, alpha) * 255.0) << 24) | (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, red) * 255.0) << 8) | (UInt32(max(0.0, red) * 255.0))
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
var hsv: (CGFloat, CGFloat, CGFloat) {
|
||||
|
|
@ -329,6 +337,7 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? {
|
|||
for sublayer in sublayers {
|
||||
let subtree = makeLayerSubtreeSnapshot(layer: sublayer)
|
||||
if let subtree = subtree {
|
||||
subtree.transform = sublayer.transform
|
||||
subtree.frame = sublayer.frame
|
||||
subtree.bounds = sublayer.bounds
|
||||
layer.addSublayer(subtree)
|
||||
|
|
|
|||
|
|
@ -36,4 +36,6 @@ void applyKeyboardAutocorrection();
|
|||
|
||||
@interface AboveStatusBarWindow : UIWindow
|
||||
|
||||
@property (nonatomic, copy) UIInterfaceOrientationMask (^ _Nullable supportedOrientations)(void);
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -311,6 +311,8 @@ void applyKeyboardAutocorrection() {
|
|||
|
||||
@interface AboveStatusBarWindowController : UIViewController
|
||||
|
||||
@property (nonatomic, copy) UIInterfaceOrientationMask (^ _Nullable supportedOrientations)(void);
|
||||
|
||||
@end
|
||||
|
||||
@implementation AboveStatusBarWindowController
|
||||
|
|
@ -330,6 +332,10 @@ void applyKeyboardAutocorrection() {
|
|||
[self viewDidLoad];
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AboveStatusBarWindow
|
||||
|
|
@ -348,6 +354,11 @@ void applyKeyboardAutocorrection() {
|
|||
return self;
|
||||
}
|
||||
|
||||
- (void)setSupportedOrientations:(UIInterfaceOrientationMask (^)(void))supportedOrientations {
|
||||
_supportedOrientations = [supportedOrientations copy];
|
||||
((AboveStatusBarWindowController *)self.rootViewController).supportedOrientations = _supportedOrientations;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAffectStatusBarAppearance {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,14 @@ open class ViewControllerPresentationArguments {
|
|||
public final var isOpaqueWhenInOverlay: Bool = false
|
||||
public final var blocksBackgroundWhenInOverlay: Bool = false
|
||||
public final var automaticallyControlPresentationContextLayout: Bool = true
|
||||
public final var isModalWhenInOverlay: Bool = false {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
self.displayNode.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
}
|
||||
public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
||||
return self.supportedOrientations
|
||||
|
|
@ -186,7 +194,7 @@ open class ViewControllerPresentationArguments {
|
|||
|
||||
open var visualNavigationInsetHeight: CGFloat {
|
||||
if let navigationBar = self.navigationBar {
|
||||
var height = navigationBar.frame.maxY
|
||||
let height = navigationBar.frame.maxY
|
||||
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode {
|
||||
//height += contentNode.height
|
||||
}
|
||||
|
|
@ -229,7 +237,7 @@ open class ViewControllerPresentationArguments {
|
|||
private func updateScrollToTopView() {
|
||||
if self.scrollToTop != nil {
|
||||
if let displayNode = self._displayNode , self.scrollToTopView == nil {
|
||||
let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.frame.size.width, height: 1.0))
|
||||
let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.bounds.size.width, height: 1.0))
|
||||
scrollToTopView.action = { [weak self] in
|
||||
if let scrollToTop = self?.scrollToTop {
|
||||
scrollToTop()
|
||||
|
|
@ -289,16 +297,16 @@ open class ViewControllerPresentationArguments {
|
|||
|
||||
private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
||||
let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + (self.navigationBar?.contentHeight ?? 44.0)
|
||||
let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight ?? 44.0)
|
||||
let navigationBarOffset: CGFloat
|
||||
if statusBarHeight.isZero {
|
||||
navigationBarOffset = -20.0
|
||||
navigationBarOffset = 0.0
|
||||
} else {
|
||||
navigationBarOffset = 0.0
|
||||
}
|
||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight))
|
||||
if layout.statusBarHeight == nil {
|
||||
navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0
|
||||
//navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0
|
||||
}
|
||||
|
||||
if !self.displayNavigationBar {
|
||||
|
|
@ -344,6 +352,10 @@ open class ViewControllerPresentationArguments {
|
|||
}
|
||||
}
|
||||
|
||||
open func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
}
|
||||
|
||||
open func navigationStackConfigurationUpdated(next: [ViewController]) {
|
||||
}
|
||||
|
||||
|
|
@ -373,6 +385,10 @@ open class ViewControllerPresentationArguments {
|
|||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
}
|
||||
|
||||
if self.isModalWhenInOverlay {
|
||||
self.displayNode.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
|
||||
public func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||
|
|
@ -506,7 +522,7 @@ open class ViewControllerPresentationArguments {
|
|||
|
||||
public final func navigationNextSibling() -> UIViewController? {
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
if let index = navigationController.viewControllers.index(where: { $0 === self }) {
|
||||
if let index = navigationController.viewControllers.firstIndex(where: { $0 === self }) {
|
||||
if index != navigationController.viewControllers.count - 1 {
|
||||
return navigationController.viewControllers[index + 1]
|
||||
}
|
||||
|
|
@ -576,7 +592,7 @@ private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool {
|
|||
if view.window == nil {
|
||||
return false
|
||||
}
|
||||
if let index = siblings.index(where: { $0 === view.layer }) {
|
||||
if let index = siblings.firstIndex(where: { $0 === view.layer }) {
|
||||
let viewFrame = view.convert(rect, to: superview)
|
||||
for i in (index + 1) ..< siblings.count {
|
||||
if siblings[i].frame.contains(viewFrame) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,10 @@
|
|||
//
|
||||
// WallpaperBackgroundNode.swift
|
||||
// Display
|
||||
//
|
||||
// Created by Mikhail Filimonov on 13/06/2019.
|
||||
// Copyright © 2019 Telegram. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
private let motionAmount: CGFloat = 32.0
|
||||
|
||||
public final class WallpaperbackgroundNode: ASDisplayNode {
|
||||
public final class WallpaperBackgroundNode: ASDisplayNode {
|
||||
let contentNode: ASDisplayNode
|
||||
|
||||
public var motionEnabled: Bool = false {
|
||||
|
|
|
|||
|
|
@ -1050,6 +1050,14 @@ public class Window1 {
|
|||
}
|
||||
}
|
||||
|
||||
public func simulateKeyboardDismiss(transition: ContainedViewLayoutTransition) {
|
||||
if self.windowLayout.inputHeight != nil {
|
||||
self.updateLayout {
|
||||
$0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: transition, overrideTransition: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func panGestureEnded(location: CGPoint, velocity: CGPoint?) {
|
||||
if self.keyboardGestureBeginLocation == nil {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -14,54 +14,13 @@ import RadialStatusNode
|
|||
import ShareController
|
||||
import OpenInExternalAppUI
|
||||
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionThrash"), color: .white)
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
||||
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: .white)
|
||||
|
||||
private let backwardImage = UIImage(bundleImageName: "Media Gallery/BackwardButton")
|
||||
private let forwardImage = UIImage(bundleImageName: "Media Gallery/ForwardButton")
|
||||
|
||||
private let pauseImage = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let color = UIColor.white
|
||||
let diameter: CGFloat = 16.0
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
|
||||
context.translateBy(x: (diameter - size.width) / 2.0 + 3.0 - UIScreenPixel, y: (diameter - size.height) / 2.0 + 2.0)
|
||||
let _ = try? drawSvgPath(context, path: "M0,1.00087166 C0,0.448105505 0.443716645,0 0.999807492,0 L4.00019251,0 C4.55237094,0 5,0.444630861 5,1.00087166 L5,14.9991283 C5,15.5518945 4.55628335,16 4.00019251,16 L0.999807492,16 C0.447629061,16 0,15.5553691 0,14.9991283 L0,1.00087166 Z M10,1.00087166 C10,0.448105505 10.4437166,0 10.9998075,0 L14.0001925,0 C14.5523709,0 15,0.444630861 15,1.00087166 L15,14.9991283 C15,15.5518945 14.5562834,16 14.0001925,16 L10.9998075,16 C10.4476291,16 10,15.5553691 10,14.9991283 L10,1.00087166 ")
|
||||
context.fillPath()
|
||||
if (diameter < 40.0) {
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.25, y: 1.25)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
}
|
||||
context.translateBy(x: -(diameter - size.width) / 2.0, y: -(diameter - size.height) / 2.0)
|
||||
})
|
||||
|
||||
private let playImage = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let color = UIColor.white
|
||||
let diameter: CGFloat = 16.0
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
|
||||
context.translateBy(x: (diameter - size.width) / 2.0 + 2.5, y: (diameter - size.height) / 2.0 + 1.0)
|
||||
if (diameter < 40.0) {
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 0.8, y: 0.8)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
}
|
||||
let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ")
|
||||
context.fillPath()
|
||||
if (diameter < 40.0) {
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
}
|
||||
context.translateBy(x: -(diameter - size.width) / 2.0 - 1.5, y: -(diameter - size.height) / 2.0)
|
||||
})
|
||||
private let backwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/BackwardButton"), color: .white)
|
||||
private let forwardImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/ForwardButton"), color: .white)
|
||||
private let pauseImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/PauseButton"), color: .white)
|
||||
private let playImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/PlayButton"), color: .white)
|
||||
|
||||
private let cloudFetchIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: UIColor.white)
|
||||
|
||||
|
|
@ -641,8 +600,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0) + 66.0, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
if let image = self.backwardButton.image(for: .normal) {
|
||||
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
|
||||
}
|
||||
if let image = self.forwardButton.image(for: .normal) {
|
||||
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) + 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
}
|
||||
|
||||
self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ public class GalleryController: ViewController {
|
|||
switch source {
|
||||
case .peerMessagesAtId:
|
||||
if let tags = tagsForMessage(message!) {
|
||||
let namespaces: HistoryViewNamespaces
|
||||
let namespaces: MessageIdNamespaces
|
||||
if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
|
||||
namespaces = .just(Namespaces.Message.allScheduled)
|
||||
} else {
|
||||
|
|
@ -643,7 +643,7 @@ public class GalleryController: ViewController {
|
|||
UIPasteboard.general.string = mention
|
||||
}))
|
||||
}
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items:items), ActionSheetItemGroup(items: [
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ import AccountContext
|
|||
|
||||
public protocol GalleryItemTransitionNode: class {
|
||||
func isAvailableForGalleryTransition() -> Bool
|
||||
func isAvailableForInstantPageTransition() -> Bool
|
||||
var decoration: UniversalVideoDecoration? { get }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let result = result {
|
||||
if let result = result, strongSelf.scrubbingFrames {
|
||||
switch result {
|
||||
case .waitingForData:
|
||||
strongSelf.footerContentNode.setFramePreviewImageIsLoading()
|
||||
|
|
@ -473,7 +473,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
seekable = value.duration >= 45.0
|
||||
seekable = value.duration >= 30.0
|
||||
}
|
||||
|
||||
var fetching = false
|
||||
|
|
@ -922,7 +922,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
videoNode.continuePlayingWithoutSound()
|
||||
}
|
||||
}
|
||||
} else if let _ = node.0 as? GalleryItemTransitionNode, videoNode.hasAttachedContext {
|
||||
} else if let instantNode = node.0 as? GalleryItemTransitionNode, instantNode.isAvailableForInstantPageTransition(), videoNode.hasAttachedContext {
|
||||
copyView.removeFromSuperview()
|
||||
|
||||
let previousFrame = videoNode.frame
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||
if let strongSelf = self {
|
||||
strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.context.account, peer: peer) |> deliverOnMainQueue).start(next: { actualPeerId in
|
||||
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), messageId: message.id.peerId == actualPeerId ? message.id : nil, keepStack: .always))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: message.id.peerId == actualPeerId ? .message(message.id) : nil, keepStack: .always))
|
||||
}
|
||||
}))
|
||||
strongSelf.controllerNode.listNode.clearHighlightAnimated(true)
|
||||
|
|
|
|||
|
|
@ -1199,9 +1199,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||
strongSelf.loadProgress.set(1.0)
|
||||
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), openPeer: { peerId, navigation in
|
||||
switch navigation {
|
||||
case let .chat(_, messageId):
|
||||
case let .chat(_, subject):
|
||||
if let navigationController = strongSelf.getNavigationController() {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), messageId: messageId))
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject))
|
||||
}
|
||||
case let .withBotStartPayload(botStart):
|
||||
if let navigationController = strongSelf.getNavigationController() {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ final class InstantPageFeedbackNode: ASDisplayNode, InstantPageNode {
|
|||
|
||||
@objc func buttonPressed() {
|
||||
self.resolveDisposable.set((resolvePeerByName(account: self.context.account, name: "previews") |> deliverOnMainQueue).start(next: { [weak self] peerId in
|
||||
if let strongSelf = self, let peerId = peerId, let webPageId = strongSelf.webPage.id?.id {
|
||||
if let strongSelf = self, let _ = peerId, let webPageId = strongSelf.webPage.id?.id {
|
||||
strongSelf.openUrl(InstantPageUrlItem(url: "https://t.me/previews?start=webpage\(webPageId)", webpageId: nil))
|
||||
}
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode {
|
|||
let sideInset: CGFloat = leftInset + 8.0
|
||||
let topInset: CGFloat = 0.0
|
||||
let bottomInset: CGFloat = 0.0
|
||||
var textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
var x = sideInset
|
||||
if let hasRTL = self.textNode.cachedLayout?.hasRTL, hasRTL {
|
||||
|
|
|
|||
|
|
@ -627,7 +627,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
|||
let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size)
|
||||
let item: InstantPageItem
|
||||
if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage {
|
||||
let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: size, duration: nil, author: nil, image: image, file: nil, instantPage: nil)
|
||||
let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: size, duration: nil, author: nil, image: image, file: nil, files: nil, instantPage: nil)
|
||||
let content = TelegramMediaWebpageContent.Loaded(loadedContent)
|
||||
|
||||
item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false)
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler
|
|||
return true
|
||||
}
|
||||
|
||||
func isAvailableForInstantPageTransition() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
|
|
|
|||
|
|
@ -787,12 +787,12 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
|||
}
|
||||
|
||||
if requiresScroll {
|
||||
textItem.frame = textItem.frame.offsetBy(dx: 0.0, dy: fabs(topInset))
|
||||
textItem.frame = textItem.frame.offsetBy(dx: 0.0, dy: abs(topInset))
|
||||
for var item in additionalItems {
|
||||
item.frame = item.frame.offsetBy(dx: 0.0, dy: fabs(topInset))
|
||||
item.frame = item.frame.offsetBy(dx: 0.0, dy: abs(topInset))
|
||||
}
|
||||
|
||||
let scrollableItem = InstantPageScrollableTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth + horizontalInset * 2.0, height: height + fabs(topInset) + bottomInset), item: textItem, additionalItems: additionalItems, totalWidth: textWidth, horizontalInset: horizontalInset, rtl: textItem.containsRTL)
|
||||
let scrollableItem = InstantPageScrollableTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth + horizontalInset * 2.0, height: height + abs(topInset) + bottomInset), item: textItem, additionalItems: additionalItems, totalWidth: textWidth, horizontalInset: horizontalInset, rtl: textItem.containsRTL)
|
||||
items.append(scrollableItem)
|
||||
} else {
|
||||
items.append(contentsOf: additionalItems)
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||
if strongSelf.inputSeparator == nil {
|
||||
animateIn = true
|
||||
}
|
||||
let keyboardAppearance = item.theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
let keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
switch editingName {
|
||||
case let .personName(firstName, lastName):
|
||||
if strongSelf.inputSeparator == nil {
|
||||
|
|
@ -927,7 +927,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
|||
if strongSelf.arrowNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.arrowNode)
|
||||
}
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 15.0 - arrowImage.size.width, y: floor((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floor((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
} else if strongSelf.arrowNode.supernode != nil {
|
||||
strongSelf.arrowNode.removeFromSupernode()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
|||
|
||||
strongSelf.iconNode.image = item.icon
|
||||
if let image = item.icon {
|
||||
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
|
||||
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + 3.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
|
|
|
|||
|
|
@ -35,9 +35,14 @@ public enum ItemListPeerItemText {
|
|||
case none
|
||||
}
|
||||
|
||||
public enum ItemListPeerItemLabelFont {
|
||||
case standard
|
||||
case custom(UIFont)
|
||||
}
|
||||
|
||||
public enum ItemListPeerItemLabel {
|
||||
case none
|
||||
case text(String)
|
||||
case text(String, ItemListPeerItemLabelFont)
|
||||
case disclosure(String)
|
||||
case badge(String)
|
||||
}
|
||||
|
|
@ -497,8 +502,15 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||
switch item.label {
|
||||
case .none:
|
||||
break
|
||||
case let .text(text):
|
||||
labelAttributedString = NSAttributedString(string: text, font: labelFont, textColor: item.theme.list.itemSecondaryTextColor)
|
||||
case let .text(text, font):
|
||||
let selectedFont: UIFont
|
||||
switch font {
|
||||
case .standard:
|
||||
selectedFont = labelFont
|
||||
case let .custom(value):
|
||||
selectedFont = value
|
||||
}
|
||||
labelAttributedString = NSAttributedString(string: text, font: selectedFont, textColor: item.theme.list.itemSecondaryTextColor)
|
||||
labelInset += 15.0
|
||||
case let .disclosure(text):
|
||||
if let currentLabelArrowNode = currentLabelArrowNode {
|
||||
|
|
@ -859,7 +871,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||
|
||||
if let labelArrowNode = self.labelArrowNode {
|
||||
if let image = labelArrowNode.image {
|
||||
let labelArrowNodeFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - image.size.width, y: labelArrowNode.frame.minY), size: image.size)
|
||||
let labelArrowNodeFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - image.size.width + 8.0, y: labelArrowNode.frame.minY), size: image.size)
|
||||
transition.updateFrame(node: labelArrowNode, frame: labelArrowNodeFrame)
|
||||
rightLabelInset += 19.0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -582,7 +582,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
|||
animationNode = AnimatedStickerNode()
|
||||
strongSelf.animationNode = animationNode
|
||||
strongSelf.addSubnode(animationNode)
|
||||
animationNode.setup(account: item.account, resource: resource, width: 80, height: 80, mode: .cached)
|
||||
animationNode.setup(account: item.account, resource: .resource(resource), width: 80, height: 80, mode: .cached)
|
||||
}
|
||||
animationNode.visibility = strongSelf.visibility != .none && item.playAnimatedStickers
|
||||
animationNode.isHidden = !item.playAnimatedStickers
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ public enum ItemListNavigationButtonStyle {
|
|||
public enum ItemListNavigationButtonContentIcon {
|
||||
case search
|
||||
case add
|
||||
case action
|
||||
}
|
||||
|
||||
public enum ItemListNavigationButtonContent: Equatable {
|
||||
|
|
@ -229,6 +230,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
||||
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
//self.isModalWhenInOverlay = true
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
|
||||
self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
|
|
@ -294,7 +296,8 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||
}
|
||||
strongSelf.navigationButtonActions = (left: controllerState.leftNavigationButton?.action, right: controllerState.rightNavigationButton?.action, secondaryRight: controllerState.secondaryRightNavigationButton?.action)
|
||||
|
||||
if strongSelf.leftNavigationButtonTitleAndStyle?.0 != controllerState.leftNavigationButton?.content || strongSelf.leftNavigationButtonTitleAndStyle?.1 != controllerState.leftNavigationButton?.style {
|
||||
let themeUpdated = strongSelf.theme !== controllerState.theme
|
||||
if strongSelf.leftNavigationButtonTitleAndStyle?.0 != controllerState.leftNavigationButton?.content || strongSelf.leftNavigationButtonTitleAndStyle?.1 != controllerState.leftNavigationButton?.style || themeUpdated {
|
||||
if let leftNavigationButton = controllerState.leftNavigationButton {
|
||||
let item: UIBarButtonItem
|
||||
switch leftNavigationButton.content {
|
||||
|
|
@ -309,6 +312,8 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||
image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme)
|
||||
case .add:
|
||||
image = PresentationResourcesRootController.navigationAddIcon(controllerState.theme)
|
||||
case .action:
|
||||
image = PresentationResourcesRootController.navigationShareIcon(controllerState.theme)
|
||||
}
|
||||
item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
|
||||
}
|
||||
|
|
@ -342,7 +347,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||
}
|
||||
}
|
||||
|
||||
if updateRightButtonItems {
|
||||
if updateRightButtonItems || themeUpdated {
|
||||
strongSelf.rightNavigationButtonTitleAndStyle = rightNavigationButtonTitleAndStyle.map { ($0.0, $0.1) }
|
||||
var items: [UIBarButtonItem] = []
|
||||
var index = 0
|
||||
|
|
@ -364,6 +369,8 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||
image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.theme)
|
||||
case .add:
|
||||
image = PresentationResourcesRootController.navigationAddIcon(controllerState.theme)
|
||||
case .action:
|
||||
image = PresentationResourcesRootController.navigationShareIcon(controllerState.theme)
|
||||
}
|
||||
item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action)
|
||||
}
|
||||
|
|
@ -473,6 +480,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||
presentationArguments.completion?()
|
||||
completion()
|
||||
})
|
||||
self.updateTransitionWhenPresentedAsModal?(1.0, .animated(duration: 0.5, curve: .spring))
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
|
|
@ -501,6 +509,7 @@ open class ItemListController<Entry: ItemListNodeEntry>: ViewController, KeyShor
|
|||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
(self.displayNode as! ItemListControllerNode<Entry>).animateOut(completion: completion)
|
||||
self.updateTransitionWhenPresentedAsModal?(0.0, .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -523,13 +523,11 @@ open class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UISc
|
|||
}
|
||||
if updatedFocusItemTag {
|
||||
if let focusItemTag = focusItemTag {
|
||||
var applied = false
|
||||
strongSelf.listNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode {
|
||||
if let itemTag = itemNode.tag {
|
||||
if itemTag.isEqual(to: focusItemTag) {
|
||||
if let focusableNode = itemNode as? ItemListItemFocusableNode {
|
||||
applied = true
|
||||
focusableNode.focus()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||
}
|
||||
|
||||
if let arrowImage = strongSelf.arrowNode.image {
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 15.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
}
|
||||
|
||||
switch item.disclosureStyle {
|
||||
|
|
|
|||
|
|
@ -208,6 +208,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
|||
|
||||
if strongSelf.isNodeLoaded {
|
||||
strongSelf.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: item.theme.list.itemPrimaryTextColor]
|
||||
strongSelf.textNode.tintColor = item.theme.list.itemAccentColor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +251,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod
|
|||
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
|
||||
}
|
||||
|
||||
strongSelf.textNode.keyboardAppearance = item.theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
strongSelf.textNode.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
|
||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: params.width - leftInset - params.rightInset, height: textLayout.size.height))
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: params.width - leftInset - 16.0 - rightInset, height: textLayout.size.height + 1.0))
|
||||
|
|
|
|||
|
|
@ -13,6 +13,21 @@ public enum ItemListSingleLineInputItemType: Equatable {
|
|||
case username
|
||||
}
|
||||
|
||||
public enum ItemListSingleLineInputClearType: Equatable {
|
||||
case none
|
||||
case always
|
||||
case onFocus
|
||||
|
||||
var hasButton: Bool {
|
||||
switch self {
|
||||
case .none:
|
||||
return false
|
||||
case .always, .onFocus:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
|
|
@ -22,7 +37,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
|||
let type: ItemListSingleLineInputItemType
|
||||
let returnKeyType: UIReturnKeyType
|
||||
let spacing: CGFloat
|
||||
let clearButton: Bool
|
||||
let clearType: ItemListSingleLineInputClearType
|
||||
let enabled: Bool
|
||||
public let sectionId: ItemListSectionId
|
||||
let action: () -> Void
|
||||
|
|
@ -32,7 +47,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
|||
let updatedFocus: ((Bool) -> Void)?
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearButton: Bool = false, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void) {
|
||||
public init(theme: PresentationTheme, strings: PresentationStrings, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.title = title
|
||||
|
|
@ -41,7 +56,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
|||
self.type = type
|
||||
self.returnKeyType = returnKeyType
|
||||
self.spacing = spacing
|
||||
self.clearButton = clearButton
|
||||
self.clearType = clearType
|
||||
self.enabled = enabled
|
||||
self.tag = tag
|
||||
self.sectionId = sectionId
|
||||
|
|
@ -153,7 +168,8 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||
self.textNode.textField.font = Font.regular(17.0)
|
||||
if let item = self.item {
|
||||
self.textNode.textField.textColor = item.theme.list.itemPrimaryTextColor
|
||||
self.textNode.textField.keyboardAppearance = item.theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.textNode.textField.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.textNode.textField.tintColor = item.theme.list.itemAccentColor
|
||||
self.textNode.textField.accessibilityHint = item.placeholder
|
||||
}
|
||||
self.textNode.clipsToBounds = true
|
||||
|
|
@ -179,7 +195,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||
let leftInset: CGFloat = 16.0 + params.leftInset
|
||||
var rightInset: CGFloat = 16.0 + params.rightInset
|
||||
|
||||
if item.clearButton {
|
||||
if item.clearType.hasButton {
|
||||
rightInset += 32.0
|
||||
}
|
||||
|
||||
|
|
@ -209,7 +225,8 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
strongSelf.textNode.textField.textColor = item.theme.list.itemPrimaryTextColor
|
||||
strongSelf.textNode.textField.keyboardAppearance = item.theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
strongSelf.textNode.textField.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance
|
||||
strongSelf.textNode.textField.tintColor = item.theme.list.itemAccentColor
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
|
|
@ -288,9 +305,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||
strongSelf.clearIconNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - buttonSize.width + floor((buttonSize.width - image.size.width) / 2.0), y: floor((layout.contentSize.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
strongSelf.clearIconNode.isHidden = !item.clearButton || item.text.isEmpty
|
||||
strongSelf.clearButtonNode.isHidden = !item.clearButton || item.text.isEmpty
|
||||
strongSelf.clearButtonNode.isAccessibilityElement = !strongSelf.clearButtonNode.isHidden
|
||||
strongSelf.updateClearButtonVisibility()
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
|
|
@ -332,6 +347,24 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||
}
|
||||
}
|
||||
|
||||
private func updateClearButtonVisibility() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
let isHidden: Bool
|
||||
switch item.clearType {
|
||||
case .none:
|
||||
isHidden = true
|
||||
case .always:
|
||||
isHidden = item.text.isEmpty
|
||||
case .onFocus:
|
||||
isHidden = !self.textNode.textField.isFirstResponder || item.text.isEmpty
|
||||
}
|
||||
self.clearIconNode.isHidden = isHidden
|
||||
self.clearButtonNode.isHidden = isHidden
|
||||
self.clearButtonNode.isAccessibilityElement = isHidden
|
||||
}
|
||||
|
||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
|
@ -390,10 +423,12 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
|||
|
||||
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
self.item?.updatedFocus?(true)
|
||||
self.updateClearButtonVisibility()
|
||||
}
|
||||
|
||||
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
self.item?.updatedFocus?(false)
|
||||
self.updateClearButtonVisibility()
|
||||
}
|
||||
|
||||
public func animateError() {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue