Merge branch 'master' into buck-test

This commit is contained in:
Peter 2019-08-30 04:16:26 +04:00
commit 2d56c99495
660 changed files with 20469 additions and 11858 deletions

View file

@ -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)"

View file

@ -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
View 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

File diff suppressed because it is too large Load diff

View file

@ -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 {
}
}

View file

@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
id _Nullable parseAttachment(NSData * _Nonnull data);
NS_ASSUME_NONNULL_END

View 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];
}

View file

@ -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)))
}
}

View 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

View 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];
};
}

View file

@ -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()
}
}

View file

@ -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>

View file

@ -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)
}
}

View 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

View 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

View file

@ -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)
}
}
}

View file

@ -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
}
}

View 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

View 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

View file

@ -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
}
}
}

View 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

View 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;
}

View file

@ -19,7 +19,6 @@ class ShareRootController: UIViewController {
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
let apiId: Int32 = buildConfig.apiId
let languagesCategory = "ios"
let appGroupName = "group.\(baseAppBundleId)"

View file

@ -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;
};

View file

@ -57,7 +57,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "DebugAppStoreLLC"
buildConfiguration = "DebugHockeyapp"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"

View file

@ -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:"

View file

@ -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>

View file

@ -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>

View file

@ -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.";

View file

@ -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

View file

@ -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)
}

View file

@ -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)
})

View file

@ -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
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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))
}
}))
}

View file

@ -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

View file

@ -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()

View file

@ -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))

View file

@ -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

View file

@ -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))
}

View file

@ -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))
}

View file

@ -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))
}
}

View file

@ -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)

View file

@ -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

View file

@ -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()
}

View file

@ -513,6 +513,7 @@ public func createPollController(context: AccountContext, peerId: PeerId, comple
}
}
controller.isOpaqueWhenInOverlay = true
//controller.isModalWhenInOverlay = true
controller.blocksBackgroundWhenInOverlay = true
controller.experimentalSnapScrollToItem = true

View file

@ -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))

View file

@ -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
}

View file

@ -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",

View file

@ -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 */,

View file

@ -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()
}

View file

@ -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 {

View file

@ -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()

View file

@ -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?()
})
}
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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

View file

@ -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()
})
}

View file

@ -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)
}
}
}
}

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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];
}
}

View file

@ -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)

View file

@ -36,4 +36,6 @@ void applyKeyboardAutocorrection();
@interface AboveStatusBarWindow : UIWindow
@property (nonatomic, copy) UIInterfaceOrientationMask (^ _Nullable supportedOrientations)(void);
@end

View file

@ -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;
}

View file

@ -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) {

View file

@ -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 {

View file

@ -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

View file

@ -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))

View file

@ -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()
})

View file

@ -3,5 +3,6 @@ import AccountContext
public protocol GalleryItemTransitionNode: class {
func isAvailableForGalleryTransition() -> Bool
func isAvailableForInstantPageTransition() -> Bool
var decoration: UniversalVideoDecoration? { get }
}

View file

@ -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

View file

@ -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)

View file

@ -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() {

View file

@ -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))
}
}))

View file

@ -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 {

View file

@ -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)

View file

@ -86,6 +86,10 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler
return true
}
func isAvailableForInstantPageTransition() -> Bool {
return true
}
override func didLoad() {
super.didLoad()

View file

@ -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)

View file

@ -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()
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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))
}
}

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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))

View file

@ -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