Merge branch 'master' into beta

This commit is contained in:
Ali 2022-06-05 19:25:13 +04:00
commit f27e8beac6
900 changed files with 44046 additions and 15714 deletions

View File

@ -8,7 +8,7 @@ on:
jobs:
build:
runs-on: macos-11
runs-on: macos-12
steps:
- uses: actions/checkout@v2
@ -17,7 +17,7 @@ jobs:
fetch-depth: '0'
- name: Set active Xcode path
run: sudo xcode-select -s /Applications/Xcode_12.5.1.app/Contents/Developer
run: sudo xcode-select -s /Applications/Xcode_13.2.1.app/Contents/Developer
- name: Create canonical source directory
run: |

View File

@ -28,6 +28,24 @@ internal:
- build/artifacts
expire_in: 1 week
appstore_development:
tags:
- ios_internal
stage: build
only:
- appstore-development
except:
- tags
script:
- bash buildbox/build-telegram.sh appstore-development
- bash buildbox/deploy-telegram.sh appstore-development
environment:
name: appstore-development
artifacts:
paths:
- build/artifacts/Telegram.DSYMs.zip
expire_in: 1 week
experimental_i:
tags:
- ios_internal

2
.gitmodules vendored
View File

@ -4,7 +4,7 @@
url=../rlottie.git
[submodule "build-system/bazel-rules/rules_apple"]
path = build-system/bazel-rules/rules_apple
url=https://github.com/bazelbuild/rules_apple.git
url=https://github.com/ali-fareed/rules_apple.git
[submodule "build-system/bazel-rules/rules_swift"]
path = build-system/bazel-rules/rules_swift
url=https://github.com/bazelbuild/rules_swift.git

View File

@ -298,6 +298,9 @@ alternate_icon_folders = [
"WhiteFilledIcon",
"New1",
"New2",
"Premium",
"PremiumBlack",
"PremiumTurbo",
]
[
@ -329,6 +332,7 @@ swift_library(
"Telegram-iOS/Application.swift",
]),
data = [
":Icons",
":AppResources",
":AppIntentVocabularyResources",
":InfoPlistStringResources",
@ -337,6 +341,7 @@ swift_library(
"//submodules/OverlayStatusController:OverlayStatusControllerResources",
"//submodules/PasswordSetupUI:PasswordSetupUIResources",
"//submodules/PasswordSetupUI:PasswordSetupUIAssets",
"//submodules/PremiumUI:PremiumUIResources",
"//submodules/TelegramUI:TelegramUIResources",
"//submodules/TelegramUI:TelegramUIAssets",
":GeneratedPresentationStrings/Resources/PresentationStrings.data",
@ -1797,6 +1802,8 @@ plist_fragment(
<string>We need this so that you can share photos and videos from your photo library.</string>
<key>NSSiriUsageDescription</key>
<string>You can use Siri to send messages.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>We need this to transcribe audio messages on your request.</string>
<key>NSUserActivityTypes</key>
<array>
<string>INSendMessageIntent</string>

View File

@ -38,7 +38,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
self?.preferredContentSize = size
})
}

View File

@ -1,31 +0,0 @@
objc_library(
name = "NotificationServiceObjC",
enable_modules = True,
module_name = "NotificationServiceObjC",
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.h",
]),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
deps = [
"//submodules/BuildConfig:BuildConfig",
"//submodules/MtProtoKit:MtProtoKit",
"//submodules/NotificationsPresentationData:NotificationsPresentationData",
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
],
sdk_frameworks = [
"Foundation",
],
weak_sdk_frameworks = [
"BackgroundTasks",
],
visibility = [
"//visibility:public",
],
)

View File

@ -1,17 +0,0 @@
#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>
#import <BuildConfig/BuildConfig.h>
NS_ASSUME_NONNULL_BEGIN
@interface NotificationServiceImpl : NSObject
- (instancetype)initWithSerialDispatch:(void (^)(dispatch_block_t))serialDispatch countIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(bool (^)(NSString *))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText;
- (void)updateUnreadCount:(int32_t)unreadCount;
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler;
- (void)serviceExtensionTimeWillExpire;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,308 +0,0 @@
#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_PhotoSize_photoSizeProgressive;
@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;
+ (Api1_PhotoSize_photoSizeProgressive *)photoSizeProgressiveWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h sizes:(NSArray *)sizes;
@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_PhotoSize_photoSizeProgressive : Api1_PhotoSize
@property (nonatomic, strong) Api1_FileLocation * location;
@property (nonatomic, strong) NSNumber * w;
@property (nonatomic, strong) NSNumber * h;
@property (nonatomic, strong) NSArray * sizes;
@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

View File

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

View File

@ -1,31 +0,0 @@
#import "Attachments.h"
#import <MTProtoKit/MTProtoKit.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,11 +0,0 @@
#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

@ -1,209 +0,0 @@
#import "FetchImage.h"
#import <MtProtoKit/MtProtoKit.h>
#import <OpenSSLEncryptionProvider/OpenSSLEncryptionProvider.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:@""];
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 encryptionProvider:[[OpenSSLEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:true];
context.tempKeyExpiration = 10 * 60 * 60;
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];
MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{}];
[context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:authInfo selector:MTDatacenterAuthInfoSelectorPersistent];
if (info.ephemeralMainKey != nil) {
MTDatacenterAuthInfo *ephemeralMainAuthInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:info.ephemeralMainKey.data authKeyId:info.ephemeralMainKey.keyId saltSet:@[] authKeyAttributes:@{}];
[context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:ephemeralMainAuthInfo selector:MTDatacenterAuthInfoSelectorEphemeralMain];
}
if (info.ephemeralMediaKey != nil) {
MTDatacenterAuthInfo *ephemeralMediaAuthInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:info.ephemeralMediaKey.data authKeyId:info.ephemeralMediaKey.keyId saltSet:@[] authKeyAttributes:@{}];
[context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:ephemeralMediaAuthInfo selector:MTDatacenterAuthInfoSelectorEphemeralMedia];
}
}
MTProto *mtProto = [[MTProto alloc] initWithContext:context datacenterId:datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0];
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,494 +0,0 @@
#import <NotificationServiceObjC/NotificationServiceObjC.h>
#import <mach/mach.h>
#import <UIKit/UIKit.h>
#import <BuildConfig/BuildConfig.h>
#ifdef __IPHONE_13_0
#import <BackgroundTasks/BackgroundTasks.h>
#endif
#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)));
}
#if DEBUG
static void reportMemory() {
struct task_basic_info info;
mach_msg_type_number_t size = TASK_BASIC_INFO_COUNT;
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if (kerr == KERN_SUCCESS) {
NSLog(@"Memory in use (in bytes): %lu", info.resident_size);
NSLog(@"Memory in use (in MiB): %f", ((CGFloat)info.resident_size / 1048576));
} else {
NSLog(@"Error with task_info(): %s", mach_error_string(kerr));
}
}
#endif
@interface NotificationServiceImpl () {
void (^_serialDispatch)(dispatch_block_t);
void (^_countIncomingMessage)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t);
NSString * _Nullable _rootPath;
DeviceSpecificEncryptionParameters * _Nullable _deviceSpecificEncryptionParameters;
bool _isLockedValue;
NSString *_lockedMessageTextValue;
NSString * _Nullable _baseAppBundleId;
void (^_contentHandler)(UNNotificationContent *);
UNMutableNotificationContent * _Nullable _bestAttemptContent;
void (^_cancelFetch)(void);
NSNumber * _Nullable _updatedUnreadCount;
bool _contentReady;
}
@end
@implementation NotificationServiceImpl
- (instancetype)initWithSerialDispatch:(void (^)(dispatch_block_t))serialDispatch countIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(nonnull bool (^)(NSString * _Nonnull))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText {
self = [super init];
if (self != nil) {
#if DEBUG
reportMemory();
#endif
_serialDispatch = [serialDispatch copy];
_countIncomingMessage = [countIncomingMessage copy];
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;
if (rootPath != nil) {
_deviceSpecificEncryptionParameters = [BuildConfig deviceSpecificEncryptionParameters:rootPath baseAppBundleId:_baseAppBundleId];
_isLockedValue = isLocked(rootPath);
if (_isLockedValue) {
_lockedMessageTextValue = lockedMessageText(rootPath);
}
}
} else {
NSAssert(false, @"appGroupUrl == nil");
}
} else {
NSAssert(false, @"Invalid bundle id");
}
}
return self;
}
- (void)completeWithBestAttemptContent {
_contentReady = true;
_updatedUnreadCount = @(-1);
if (_contentReady && _updatedUnreadCount) {
[self _internalComplete];
}
}
- (void)updateUnreadCount:(int32_t)unreadCount {
_updatedUnreadCount = @(unreadCount);
if (_contentReady && _updatedUnreadCount) {
[self _internalComplete];
}
}
- (void)_internalComplete {
#if DEBUG
reportMemory();
#endif
NSString *baseAppBundleId = _baseAppBundleId;
void (^contentHandler)(UNNotificationContent *) = [_contentHandler copy];
UNMutableNotificationContent *bestAttemptContent = _bestAttemptContent;
NSNumber *updatedUnreadCount = updatedUnreadCount;
dispatch_async(dispatch_get_main_queue(), ^{
#ifdef __IPHONE_13_0
if (baseAppBundleId != nil && false) {
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:[baseAppBundleId stringByAppendingString:@".refresh"]];
request.earliestBeginDate = nil;
NSError *error = nil;
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
if (error != nil) {
NSLog(@"Error: %@", error);
}
}
#endif
if (updatedUnreadCount != nil) {
int32_t unreadCount = (int32_t)[updatedUnreadCount intValue];
if (unreadCount > 0) {
bestAttemptContent.badge = @(unreadCount);
}
}
contentHandler(bestAttemptContent);
});
}
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
if (_rootPath == nil) {
_bestAttemptContent = (UNMutableNotificationContent *)[request.content mutableCopy];
[self completeWithBestAttemptContent];
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]);
}
/*if (_countIncomingMessage && _deviceSpecificEncryptionParameters) {
_countIncomingMessage(_rootPath, account.accountId, _deviceSpecificEncryptionParameters, peerId, messageId);
}*/
NSString *silentString = decryptedPayload[@"silent"];
if ([silentString isKindOfClass:[NSString class]]) {
silent = [silentString intValue] != 0;
}
NSData *attachmentData = nil;
id parsedAttachment = nil;
if (!_isLockedValue) {
NSString *attachmentDataString = decryptedPayload[@"attachb64"];
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;
int32_t progressiveFileLimit = -1;
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_photoSizeProgressive class]]) {
Api1_PhotoSize_photoSizeProgressive *sizeValue = size;
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;
}
}*/
if (inputFileLocation == nil) {
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;
if (_isLockedValue) {
_bestAttemptContent.title = @"";
_bestAttemptContent.subtitle = @"";
if (_lockedMessageTextValue != nil) {
_bestAttemptContent.body = _lockedMessageTextValue;
} else {
_bestAttemptContent.body = @"^You have a new message";
}
} else {
_bestAttemptContent.subtitle = subtitle;
_bestAttemptContent.body = body;
}
} else if ([alert isKindOfClass:[NSString class]]) {
_bestAttemptContent.title = @"";
_bestAttemptContent.subtitle = @"";
if (_isLockedValue) {
if (_lockedMessageTextValue != nil) {
_bestAttemptContent.body = _lockedMessageTextValue;
} else {
_bestAttemptContent.body = @"^You have a new message";
}
} else {
_bestAttemptContent.body = alert;
}
}
if (_isLockedValue) {
_bestAttemptContent.threadIdentifier = @"locked";
} else {
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];
}
if (_isLockedValue) {
_bestAttemptContent.categoryIdentifier = @"locked";
} else {
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];
}
}
[self completeWithBestAttemptContent];
} else {
BuildConfig *buildConfig = [[BuildConfig alloc] initWithBaseAppBundleId:_baseAppBundleId];
void (^serialDispatch)(dispatch_block_t) = _serialDispatch;
__weak typeof(self) weakSelf = self;
_cancelFetch = fetchImage(buildConfig, accountInfos.proxy, account, inputFileLocation, fileDatacenterId, ^(NSData * _Nullable data) {
serialDispatch(^{
__strong typeof(weakSelf) 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];
}
}
}
[strongSelf completeWithBestAttemptContent];
}
});
});
}
} else {
[self completeWithBestAttemptContent];
}
} else {
[self completeWithBestAttemptContent];
}
}
- (void)serviceExtensionTimeWillExpire {
if (_cancelFetch) {
_cancelFetch();
_cancelFetch = nil;
}
if (_contentHandler) {
if(_bestAttemptContent) {
if (_updatedUnreadCount == nil) {
_updatedUnreadCount = @(-1);
}
[self completeWithBestAttemptContent];
}
_contentHandler = nil;
}
}
@end

View File

@ -1,11 +0,0 @@
#import <Foundation/Foundation.h>
#import <MTProtoKit/MTProtoKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface Serialization : NSObject <MTSerialization>
@end
NS_ASSUME_NONNULL_END

View File

@ -1,35 +0,0 @@
#import "Serialization.h"
@implementation Serialization
- (NSUInteger)currentLayer {
return 133;
}
- (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:(int64_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,69 +0,0 @@
#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) AccountDatacenterKey *ephemeralMainKey;
@property (nonatomic, strong, readonly) AccountDatacenterKey *ephemeralMediaKey;
@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

@ -1,427 +0,0 @@
#import "StoredAccountInfos.h"
#import <MTProtoKit/MTProtoKit.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 ephemeralMainKey:(AccountDatacenterKey * _Nullable)ephemeralMainKey ephemeralMediaKey:(AccountDatacenterKey * _Nullable)ephemeralMediaKey addressList:(NSArray<AccountDatacenterAddress *> *)addressList {
self = [super init];
if (self != nil) {
_masterKey = masterKey;
_ephemeralMainKey = ephemeralMainKey;
_ephemeralMediaKey = ephemeralMediaKey;
_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;
}
NSDictionary *ephemeralMainKeyDict = dict[@"ephemeralMainKey"];
AccountDatacenterKey *ephemeralMainKey = nil;
if ([ephemeralMainKeyDict isKindOfClass:[NSDictionary class]]) {
ephemeralMainKey = [AccountDatacenterKey parse:ephemeralMainKeyDict];
}
NSDictionary *ephemeralMediaKeyDict = dict[@"ephemeralMediaKey"];
AccountDatacenterKey *ephemeralMediaKey = nil;
if ([ephemeralMediaKeyDict isKindOfClass:[NSDictionary class]]) {
ephemeralMediaKey = [AccountDatacenterKey parse:ephemeralMediaKeyDict];
}
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 ephemeralMainKey:ephemeralMainKey ephemeralMediaKey:ephemeralMediaKey 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

@ -437,6 +437,7 @@ private struct NotificationContent: CustomStringConvertible {
var category: String?
var userInfo: [AnyHashable: Any] = [:]
var attachments: [UNNotificationAttachment] = []
var silent = false
var senderPerson: INPerson?
var senderImage: INImage?
@ -469,13 +470,18 @@ private struct NotificationContent: CustomStringConvertible {
self.senderImage = image
var displayName: String = peer.debugDisplayTitle
if self.silent {
displayName = "\(displayName) 🔕"
}
var personNameComponents = PersonNameComponents()
personNameComponents.nickname = peer.debugDisplayTitle
personNameComponents.nickname = displayName
self.senderPerson = INPerson(
personHandle: INPersonHandle(value: "\(peer.id.toInt64())", type: .unknown),
nameComponents: personNameComponents,
displayName: peer.debugDisplayTitle,
displayName: displayName,
image: image,
contactIdentifier: nil,
customIdentifier: "\(peer.id.toInt64())",
@ -489,7 +495,11 @@ private struct NotificationContent: CustomStringConvertible {
var content = UNMutableNotificationContent()
if let title = self.title {
content.title = title
if self.silent {
content.title = "\(title) 🔕"
} else {
content.title = title
}
}
if let subtitle = self.subtitle {
content.subtitle = subtitle
@ -567,6 +577,31 @@ private struct NotificationContent: CustomStringConvertible {
}
}
private func getCurrentRenderedTotalUnreadCount(accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox) -> Signal<(Int32, RenderedTotalUnreadCountType), NoError> {
let counters = postbox.transaction { transaction -> ChatListTotalUnreadState in
return transaction.getTotalUnreadState(groupId: .root)
}
return combineLatest(
accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
|> take(1),
counters
)
|> map { sharedData, totalReadCounters -> (Int32, RenderedTotalUnreadCountType) in
let inAppSettings: InAppNotificationSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings]?.get(InAppNotificationSettings.self) {
inAppSettings = value
} else {
inAppSettings = .defaultSettings
}
let type: RenderedTotalUnreadCountType
switch inAppSettings.totalUnreadCountDisplayStyle {
case .filtered:
type = .filtered
}
return (totalReadCounters.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags), type)
}
}
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
private final class NotificationServiceHandler {
private let queue: Queue
@ -912,9 +947,7 @@ private final class NotificationServiceHandler {
if let silentString = payloadJson["silent"] as? String {
if let silentValue = Int(silentString), silentValue != 0 {
if let title = content.title {
content.title = "\(title) 🔕"
}
content.silent = true
}
}
if var attachmentDataString = payloadJson["attachb64"] as? String {
@ -1062,7 +1095,7 @@ private final class NotificationServiceHandler {
if let resource = fetchResource {
if let _ = strongSelf.stateManager?.postbox.mediaBox.completedResourcePath(resource) {
} else {
let intervals: Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int(Int32.max), MediaBoxFetchPriority.maximum)])
let intervals: Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int64.max, MediaBoxFetchPriority.maximum)])
fetchMediaSignal = Signal { subscriber in
let collectedData = Atomic<Data>(value: Data())
return standaloneMultipartFetch(
@ -1113,7 +1146,7 @@ private final class NotificationServiceHandler {
if let path = strongSelf.stateManager?.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
fetchNotificationSoundSignal = .single(data)
} else {
let intervals: Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int(Int32.max), MediaBoxFetchPriority.maximum)])
let intervals: Signal<[(Range<Int64>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int64.max, MediaBoxFetchPriority.maximum)])
fetchNotificationSoundSignal = Signal { subscriber in
let collectedData = Atomic<Data>(value: Data())
return standaloneMultipartFetch(

View File

@ -45,7 +45,7 @@ class ShareRootController: UIViewController {
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
return self?.extensionContext
})
}

View File

@ -1,104 +0,0 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "icon@120px.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "icon@180px.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "icon@76px.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "icon@152px.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "icon@167px.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "icon@1024px.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,119 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "BlueNotificationIcon@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "BlueNotificationIcon@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Simple@58x58.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Simple@87x87.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Simple@80x80.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "BlueIcon@2x-1.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BlueIcon@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "BlueIcon@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "BlueNotificationIcon.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "BlueNotificationIcon@2x-1.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Simple@29x29.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Simple@58x58-1.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Simple@40x40-1.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Simple@80x80-1.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BlueIconIpad.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "BlueIconIpad@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "BlueIconLargeIpad@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Simple-iTunesArtwork.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"pre-rendered" : true
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,12 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_lt_user.pdf"
"filename" : "ic_lt_user.pdf",
"idiom" : "universal"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -388,7 +388,7 @@
"Tour.Text5" = "**Telegram** lets you access your\nmessages from multiple devices.";
"Tour.Title6" = "Free";
"Tour.Text6" = "**Telegram** is free forever. No ads.\nNo subscription fees.";
"Tour.Text6" = "**Telegram** isprovides free unlimited cloud storage\nfor chats and media.";
"Tour.StartButton" = "Start Messaging";
@ -2494,6 +2494,13 @@ Unused sets are archived when you add more.";
"Message.PaymentSent" = "Payment: %@";
"Notification.PaymentSent" = "You have just successfully transferred {amount} to {name} for {title}";
"Notification.PaymentSentNoTitle" = "You have just successfully transferred {amount} to {name}";
"Notification.PaymentSentRecurringInit" = "You have just successfully transferred {amount} to {name} for {title} and allowed future reccurrent payments";
"Notification.PaymentSentRecurringInitNoTitle" = "You have just successfully transferred {amount} to {name} and allowed future reccurrent payments";
"Notification.PaymentSentRecurringUsed" = "You have just successfully transferred {amount} to {name} for {title} via recurrent payments";
"Notification.PaymentSentRecurringUsedNoTitle" = "You have just successfully transferred {amount} to {name} via recurrent payments";
"Common.NotNow" = "Not Now";
@ -6861,6 +6868,7 @@ Sorry for the inconvenience.";
"Chat.NavigationNoChannels" = "You have no unread channels";
"Message.SponsoredLabel" = "sponsored";
"Message.RecommendedLabel" = "recommended";
"Stickers.Favorites" = "Favorites";
"Stickers.Recent" = "Recent";
@ -7526,3 +7534,163 @@ Sorry for the inconvenience.";
"Share.UploadProgress" = "Uploading • %d%";
"Share.UploadDone" = "Done";
"StickerPack.Share" = "Share Stickers";
"StickerPack.CopyLink" = "Copy Link";
"Stickers.PremiumStickers" = "Premium Stickers";
"Channel.AddUserKickedError" = "Sorry, you can't add this user because they are on the list of Removed Users and you can't unban them.";
"Channel.AddAdminKickedError" = "Sorry, you can't add this user as an admin because they are in the Removed Users list and you can't unban them.";
"Premium.Stickers.Description" = "Unlock this sticker and more by subscribing to Telegram Premium.";
"Premium.Stickers.Proceed" = "Unlock Premium Stickers";
"Premium.Reactions.Proceed" = "Unlock Premium Reactions";
"Premium.AppIcons.Proceed" = "Unlock Premium Icons";
"AccessDenied.LocationPreciseDenied" = "To share your specific location in this chat, please go to Settings > Privacy > Location Services > Telegram and set Precise Location to On.";
"Chat.MultipleTypingPair" = "%@ and %@";
"Chat.MultipleTypingMore" = "%@ and %@ others";
"Group.Username.RemoveExistingUsernamesOrExtendInfo" = "You have reserved too many public links. Try revoking a link from an older group or channel, or upgrade to **Telegram Premium** to double the limit to **%@** public links.";
"Group.Username.RemoveExistingUsernamesFinalInfo" = "You have reserved too many public links. Try revoking the link from an older group or channel, or create a private one instead.";
"OldChannels.TooManyCommunitiesText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one or upgrade to **Telegram Premium** to double the limit to **%@** groups and channels.";
"OldChannels.TooManyCommunitiesFinalText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one.";
"OldChannels.LeaveCommunities_1" = "Leave %@ Community";
"OldChannels.LeaveCommunities_any" = "Leave %@ Communities";
"Premium.FileTooLarge" = "File Too Large";
"Premium.LimitReached" = "Limit Reached";
"Premium.IncreaseLimit" = "Increase Limit";
"Premium.MaxFoldersCountText" = "You have reached the limit of **%1$@** folders. You can double the limit to **%2$@** folders by subscribing to **Telegram Premium**.";
"Premium.MaxFoldersCountFinalText" = "Sorry, you can't create more than **%1$@** folders.";
"Premium.MaxChatsInFolderText" = "Sorry, you can't add more than **%1$@** chats to a folder. You can increase this limit to **%2$@** by upgrading to **Telegram Premium**.";
"Premium.MaxChatsInFolderFinalText" = "Sorry, you can't add more than **%@** chats to a folder.";
"Premium.MaxFileSizeText" = "Double this limit to %@ per file by subscribing to **Telegram Premium**.";
"Premium.MaxFileSizeFinalText" = "The document can't be sent, because it is larger than **%@**.";
"Premium.MaxPinsText" = "Sorry, you can't pin more than **%1$@** chats to the top. Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **%2$@** chats.";
"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **%@** chats to the top. Unpin some of the currently pinned ones.";
"Premium.MaxFavedStickersTitle" = "The Limit of %@ Stickers Reached";
"Premium.MaxFavedStickersText" = "An older sticker was replaced with this one. You can [increase the limit]() to %@ stickers.";
"Premium.MaxFavedStickersFinalText" = "An older sticker was replaced with this one.";
"Premium.MaxSavedGifsTitle" = "The Limit of %@ GIFs Reached";
"Premium.MaxSavedGifsText" = "An older GIF was replaced with this one. You can [increase the limit]() to %@ GIFS.";
"Premium.MaxSavedGifsFinalText" = "An older GIF was replaced with this one.";
"Premium.MaxAccountsText" = "You have reached the limit of **%@** connected accounts. You can free one place by subscribing to **Telegram Premium**.";
"Premium.MaxAccountsFinalText" = "You have reached the limit of **%@** connected accounts.";
"Premium.Free" = "Free";
"Premium.Premium" = "Premium";
"Premium.Title" = "Telegram Premium";
"Premium.Description" = "Go **beyond the limits**, get **exclusive features** and support us by subscribing to **Telegram Premium**.";
"Premium.PersonalTitle" = "[%@]() is a subscriber\nof Telegram Premium";
"Premium.PersonalDescription" = "Owners of **Telegram Premium** accounts have exclusive access to multiple additional features.";
"Premium.SubscribedTitle" = "You are all set!";
"Premium.SubscribedDescription" = "Thank you for subsribing to **Telegram Premium**. Here's what is now unlocked.";
"Premium.DoubledLimits" = "Doubled Limits";
"Premium.DoubledLimitsInfo" = "Up to 1000 channels, 20 folders, 10 pins, 20 public links, 4 accounts and more.";
"Premium.UploadSize" = "4 GB Upload Size";
"Premium.UploadSizeInfo" = "Increased upload size from 2 GB to 4 GB per document, unlimited storage overall.";
"Premium.FasterSpeed" = "Faster Download Speed";
"Premium.FasterSpeedInfo" = "No more limits on the speed with which media and documents are downloaded.";
"Premium.VoiceToText" = "Voice-to-Text Conversion";
"Premium.VoiceToTextInfo" = "Ability to read the transcript of any incoming voice message.";
"Premium.NoAds" = "No Ads";
"Premium.NoAdsInfo" = "No more ads in public channels where Telegram sometimes shows ads.";
"Premium.Reactions" = "Unique Reactions";
"Premium.ReactionsInfo" = "Additional animated reactions on messages, available only to the Premium subscribers.";
"Premium.ReactionsStandalone" = "Additional Reactions";
"Premium.ReactionsStandaloneInfo" = "Unlock a wider range of reactions by subscribing to **Telegram Premium**.";
"Premium.Stickers" = "Premium Stickers";
"Premium.StickersInfo" = "Exclusive enlarged stickers featuring additional effects, updated monthly.";
"Premium.ChatManagement" = "Advanced Chat Management";
"Premium.ChatManagementInfo" = "Tools to set default folder, auto-archive and hide new chats.";
"Premium.Badge" = "Profile Badge";
"Premium.BadgeInfo" = "A badge next to your name showing that you are helping support Telegram.";
"Premium.Avatar" = "Animated Profile Pictures";
"Premium.AvatarInfo" = "Video avatars animated in chat lists and chats to allow for additional self-expression.";
"Premium.AppIcon" = "Telegram App Icon";
"Premium.AppIconInfo" = "Choose from a selection of Telegram app icons for your homescreen.";
"Premium.AppIconStandalone" = "Additional App Icons";
"Premium.AppIconStandaloneInfo" = "Unlock a wider range of app icons by subscribing to **Telegram Premium**.";
"Premium.SubscribeFor" = "Subscribe for %@ / month";
"Premium.AboutTitle" = "ABOUT TELEGRAM PREMIUM";
"Premium.AboutText" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
"Premium.Terms" = "By purchasing a Premium subscription, you agree to our [Terms of Service](terms) and [Privacy Policy](privacy).";
"Premium.ChargeInfo" = "Next charge: %1$@ on %2$@. [Cancel](cancel).";
"Conversation.CopyProtectionSavingDisabledSecret" = "Saving is restricted";
"Conversation.CopyProtectionForwardingDisabledSecret" = "Forwards are restricted";
"Settings.Terms_URL" = "https://telegram.org/tos";
"Settings.PrivacyPolicy_URL" = "https://telegram.org/privacy";
"Stickers.PremiumPackInfoText" = "This pack contains premium stickers like this one.";
"Stickers.PremiumPackView" = "View";
"Conversation.PremiumUploadFileTooLarge" = "File could not be sent, because it is larger than 4 GB.\n\nYou can send as many files as you like, but each must be smaller than 4 GB.";
"SponsoredMessageMenu.Hide" = "Hide";
"ChatListFolder.MaxChatsInFolder" = "Sorry, you can't add more than %d chats to a folder.";
"Conversation.SaveGif" = "Save GIF";
"Premium.Limits.Title" = "Doubled Limits";
"Premium.Limits.GroupsAndChannels" = "Groups and Channels";
"Premium.Limits.PinnedChats" = "Pinned Chats";
"Premium.Limits.PublicLinks" = "Public Links";
"Premium.Limits.SavedGifs" = "Saved GIFs";
"Premium.Limits.FavedStickers" = "Favorite Stickers";
"Premium.Limits.Bio" = "Bio";
"Premium.Limits.Captions" = "Captions";
"Premium.Limits.Folders" = "Folders";
"Premium.Limits.ChatsPerFolder" = "Chats per Folder";
"Premium.Limits.Accounts" = "Connected Accounts";
"Premium.Limits.GroupsAndChannelsInfo" = "Join up to 1000 channels and large groups";
"Premium.Limits.PinnedChatsInfo" = "Pin up to 10 chats in your main chat list";
"Premium.Limits.PublicLinksInfo" = "Reserve up to 20 [t.me/name]() links";
"Premium.Limits.SavedGifsInfo" = "Save up to 400 GIFs in your Favorite GIFs";
"Premium.Limits.FavedStickersInfo" = "Save up to 10 stickers in your Favorite stickers";
"Premium.Limits.BioInfo" = "Add more symbols and use links in your bio";
"Premium.Limits.CaptionsInfo" = "Use longer descriptions for your photos and videos";
"Premium.Limits.FoldersInfo" = "Organize your chats into 20 folders";
"Premium.Limits.ChatsPerFolderInfo" = "Add up to 200 chats into each of your folders";
"Premium.Limits.AccountsInfo" = "Connect 4 accounts with different mobile numbers";
"WebApp.Settings" = "Settings";
"Bot.AccepRecurrentInfo" = "I accept [Terms of Service]() of **%1$@**";
"Chat.AudioTranscriptionRateAction" = "Rate Transcription";
"Chat.AudioTranscriptionFeedbackTip" = "Thank you for your feedback.";
"Message.AudioTranscription.ErrorEmpty" = "No speech detected";
"Message.AudioTranscription.ErrorTooLong" = "The audio is too long";
"WebApp.SelectChat" = "Select Chat";

View File

@ -53,8 +53,8 @@ bazel_skylib_workspace()
http_file(
name = "cmake_tar_gz",
urls = ["https://github.com/Kitware/CMake/releases/download/v3.19.2/cmake-3.19.2-macos-universal.tar.gz"],
sha256 = "50afa2cb66bea6a0314ef28034f3ff1647325e30cf5940f97906a56fd9640bd8",
urls = ["https://github.com/Kitware/CMake/releases/download/v3.23.1/cmake-3.23.1-macos-universal.tar.gz"],
sha256 = "f794ed92ccb4e9b6619a77328f313497d7decf8fb7e047ba35a348b838e0e1e2",
)
http_archive(

Binary file not shown.

View File

@ -8,6 +8,7 @@ telegram_is_internal_build = "false"
telegram_is_appstore_build = "true"
telegram_appstore_id = "686449807"
telegram_app_specific_url_scheme = "tg"
telegram_premium_iap_product_id = ""
telegram_aps_environment = "production"
telegram_enable_siri = True
telegram_enable_icloud = True

View File

@ -35,6 +35,7 @@ prepare_build_variables () {
IS_APPSTORE_BUILD \
APPSTORE_ID \
APP_SPECIFIC_URL_SCHEME \
PREMIUM_IAP_PRODUCT_ID \
TELEGRAM_DISABLE_EXTENSIONS \
)
@ -66,6 +67,7 @@ prepare_build_variables () {
echo "telegram_is_appstore_build = \"$IS_APPSTORE_BUILD\"" >> "$VARIABLES_PATH"
echo "telegram_appstore_id = \"$APPSTORE_ID\"" >> "$VARIABLES_PATH"
echo "telegram_app_specific_url_scheme = \"$APP_SPECIFIC_URL_SCHEME\"" >> "$VARIABLES_PATH"
echo "telegram_premium_iap_product_id = \"$PREMIUM_IAP_PRODUCT_ID\"" >> "$VARIABLES_PATH"
echo "telegram_aps_environment = \"$APS_ENVIRONMENT\"" >> "$VARIABLES_PATH"
if [ "$TELEGRAM_DISABLE_EXTENSIONS" == "1" ]; then

View File

@ -15,6 +15,7 @@ export IS_INTERNAL_BUILD="false"
export IS_APPSTORE_BUILD="true"
export APPSTORE_ID="686449807"
export APP_SPECIFIC_URL_SCHEME="tgapp"
export PREMIUM_IAP_PRODUCT_ID="org.telegram.telegramPremium.monthly"
if [ -z "$BUILD_NUMBER" ]; then
echo "BUILD_NUMBER is not defined"

View File

@ -0,0 +1,141 @@
#!/bin/bash
set -e
MACOS_VERSION="12"
XCODE_VERSION="13.2.1"
GUEST_SHELL="bash"
if [ -z "$VIRTUALBUILD_HOST" ]; then
echo "VIRTUALBUILD_HOST is not defined"
exit 1
fi
VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)-Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)"
echo "Base VM: \"$VM_BASE_NAME\""
if [ -z "$BAZEL" ]; then
echo "BAZEL is not defined"
exit 1
fi
if [ ! -f "$BAZEL" ]; then
echo "bazel not found at $BAZEL"
exit 1
fi
BUILDBOX_DIR="buildbox"
mkdir -p "$BUILDBOX_DIR/transient-data"
rm -f "tools/bazel"
cp "$BAZEL" "tools/bazel"
BUILD_CONFIGURATION="$1"
if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ] || [ "$BUILD_CONFIGURATION" == "App Store-development" ]; then
CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning"
elif [ "$BUILD_CONFIGURATION" == "appstore" ]; then
CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning"
elif [ "$BUILD_CONFIGURATION" == "verify" ]; then
CODESIGNING_SUBPATH="build-system/fake-codesigning"
else
echo "Unknown configuration $1"
exit 1
fi
COMMIT_COMMENT="$(git log -1 --pretty=%B)"
case "$COMMIT_COMMENT" in
*"[nocache]"*)
export BAZEL_HTTP_CACHE_URL=""
;;
esac
COMMIT_ID="$(git rev-parse HEAD)"
COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an')
if [ -z "$2" ]; then
COMMIT_COUNT=$(git rev-list --count HEAD)
BUILD_NUMBER_OFFSET="$(cat build_number_offset)"
COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))"
BUILD_NUMBER="$COMMIT_COUNT"
else
BUILD_NUMBER="$2"
fi
BASE_DIR=$(pwd)
if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ] || [ "$BUILD_CONFIGURATION" == "appstore" ] || [ "$BUILD_CONFIGURATION" == "appstore-development" ]; then
if [ ! `which generate-configuration.sh` ]; then
echo "generate-configuration.sh not found in PATH $PATH"
exit 1
fi
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning"
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
case "$BUILD_CONFIGURATION" in
"hockeyapp"|"appcenter-experimental"|"appcenter-experimental-2")
generate-configuration.sh internal release "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
;;
"appstore")
generate-configuration.sh appstore release "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
;;
"appstore-development")
generate-configuration.sh appstore development "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
;;
*)
echo "Unknown build configuration $BUILD_CONFIGURATION"
exit 1
;;
esac
elif [ "$BUILD_CONFIGURATION" == "verify" ]; then
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning"
mkdir -p "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
cp -R build-system/fake-codesigning/* "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning/"
cp -R build-system/example-configuration/* "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration/"
fi
if [ ! -d "$CODESIGNING_SUBPATH" ]; then
echo "$CODESIGNING_SUBPATH does not exist"
exit 1
fi
SOURCE_DIR=$(basename "$BASE_DIR")
rm -f "$BUILDBOX_DIR/transient-data/source.tar"
set -x
find . -type f -a -not -regex "\\." -a -not -regex ".*\\./git" -a -not -regex ".*\\./git/.*" -a -not -regex "\\./bazel-bin" -a -not -regex "\\./bazel-bin/.*" -a -not -regex "\\./bazel-out" -a -not -regex "\\./bazel-out/.*" -a -not -regex "\\./bazel-testlogs" -a -not -regex "\\./bazel-testlogs/.*" -a -not -regex "\\./bazel-telegram-ios" -a -not -regex "\\./bazel-telegram-ios/.*" -a -not -regex "\\./buildbox" -a -not -regex "\\./buildbox/.*" -a -not -regex "\\./buck-out" -a -not -regex "\\./buck-out/.*" -a -not -regex "\\./\\.buckd" -a -not -regex "\\./\\.buckd/.*" -a -not -regex "\\./build" -a -not -regex "\\./build/.*" -print0 | tar cf "$BUILDBOX_DIR/transient-data/source.tar" --null -T -
PROCESS_ID="$$"
initialization_params="$VM_BASE_NAME"
initialization_params="$initialization_params&watchpid=$PROCESS_ID"
ssh_credentials=$(curl --fail --insecure "https://$VIRTUALBUILD_HOST/run-image?name=$initialization_params")
ssh_username=$(echo "$ssh_credentials" | python3 -c "import sys, json; print(json.load(sys.stdin)['sshCredentials']['username'])")
ssh_host=$(echo "$ssh_credentials" | python3 -c "import sys, json; print(json.load(sys.stdin)['sshCredentials']['host'])")
ssh_privateKey=$(echo "$ssh_credentials" | python3 -c "import sys, json; print(json.load(sys.stdin)['sshCredentials']['privateKey'])")
ssh_privateKeyFile=$(mktemp)
echo "$ssh_privateKey" | base64 --decode > "$ssh_privateKeyFile"
scp -i "$ssh_privateKeyFile" -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$CODESIGNING_SUBPATH" $ssh_username@"$ssh_host":codesigning_data
scp -i "$ssh_privateKeyFile" -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration" $ssh_username@"$ssh_host":telegram-configuration
scp -i "$ssh_privateKeyFile" -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BUILDBOX_DIR/guest-build-telegram.sh" "$BUILDBOX_DIR/transient-data/source.tar" $ssh_username@"$ssh_host":
ssh -i "$ssh_privateKeyFile" -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $ssh_username@"$ssh_host" -o ServerAliveInterval=60 -t "export BUILD_NUMBER=\"$BUILD_NUMBER\"; export BAZEL_HTTP_CACHE_URL=\"$BAZEL_HTTP_CACHE_URL\"; $GUEST_SHELL -l guest-build-telegram.sh $BUILD_CONFIGURATION" || true
OUTPUT_PATH="build/artifacts"
rm -rf "$OUTPUT_PATH"
mkdir -p "$OUTPUT_PATH"
scp -i "$ssh_privateKeyFile" -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr $ssh_username@"$ssh_host":"telegram-ios/build/artifacts/*" "$OUTPUT_PATH/"
if [ ! -f "$OUTPUT_PATH/Telegram.ipa" ]; then
exit 1
fi

View File

@ -4,8 +4,8 @@ set -e
BUILD_TELEGRAM_VERSION="1"
MACOS_VERSION="11"
XCODE_VERSION="13.2.1"
MACOS_VERSION="12"
XCODE_VERSION="13.4.1"
GUEST_SHELL="bash"
VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)"
@ -59,7 +59,7 @@ BUILD_CONFIGURATION="$1"
if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ]; then
CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning"
elif [ "$BUILD_CONFIGURATION" == "appstore" ]; then
elif [ "$BUILD_CONFIGURATION" == "appstore" ] || [ "$BUILD_CONFIGURATION" == "appstore-development" ]; then
CODESIGNING_SUBPATH="$BUILDBOX_DIR/transient-data/telegram-codesigning/codesigning"
elif [ "$BUILD_CONFIGURATION" == "verify" ]; then
CODESIGNING_SUBPATH="build-system/fake-codesigning"
@ -88,7 +88,7 @@ fi
BASE_DIR=$(pwd)
if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ] || [ "$BUILD_CONFIGURATION" == "appstore" ]; then
if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental" ] || [ "$BUILD_CONFIGURATION" == "appcenter-experimental-2" ] || [ "$BUILD_CONFIGURATION" == "appstore" ] || [ "$BUILD_CONFIGURATION" == "appstore-development" ]; then
if [ ! `which generate-configuration.sh` ]; then
echo "generate-configuration.sh not found in PATH $PATH"
exit 1
@ -106,6 +106,10 @@ if [ "$BUILD_CONFIGURATION" == "hockeyapp" ] || [ "$BUILD_CONFIGURATION" == "app
generate-configuration.sh appstore release "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
;;
"appstore-development")
generate-configuration.sh appstore development "$BASE_DIR/$BUILDBOX_DIR/transient-data/telegram-codesigning" "$BASE_DIR/$BUILDBOX_DIR/transient-data/build-configuration"
;;
*)
echo "Unknown build configuration $BUILD_CONFIGURATION"
exit 1

View File

@ -6,7 +6,11 @@ set -x
IPA_PATH="build/artifacts/Telegram.ipa"
DSYM_PATH="build/artifacts/Telegram.DSYMs.zip"
APPCENTER="/usr/local/bin/appcenter"
if [ `which appcenter` ]; then
APPCENTER="$(which appcenter)"
else
APPCENTER="/usr/local/bin/appcenter"
fi
$APPCENTER login --token "$API_TOKEN"

View File

@ -38,7 +38,7 @@ else
BUILD_NUMBER="$2"
fi
if [ "$CONFIGURATION" == "hockeyapp" ] || [ "$CONFIGURATION" == "appcenter-experimental" ] || [ "$CONFIGURATION" == "appcenter-experimental-2" ]; then
if [ "$CONFIGURATION" == "hockeyapp" ] || [ "$CONFIGURATION" == "appcenter-experimental" ] || [ "$CONFIGURATION" == "appcenter-experimental-2" ] || [ "$CONFIGURATION" == "appstore-development" ]; then
FASTLANE_PASSWORD=""
FASTLANE_ITC_TEAM_NAME=""
elif [ "$CONFIGURATION" == "appstore" ]; then
@ -69,6 +69,6 @@ if [ "$1" == "appstore" ]; then
FASTLANE_PASSWORD="$FASTLANE_PASSWORD" xcrun altool --upload-app --type ios --file "$IPA_PATH" --username "$FASTLANE_ITC_USERNAME" --password "@env:FASTLANE_PASSWORD"
elif [ "$1" == "hockeyapp" ]; then
API_USER_NAME="$API_USER_NAME" API_APP_NAME="$API_APP_NAME" API_TOKEN="$API_TOKEN" sh buildbox/deploy-appcenter.sh
elif [ "$1" == "appcenter-experimental" ] || [ "$1" == "appcenter-experimental-2" ]; then
elif [ "$1" == "appcenter-experimental" ] || [ "$1" == "appcenter-experimental-2" ] || [ "$1" == "appstore-development" ]; then
API_USER_NAME="$API_USER_NAME" API_APP_NAME="$API_APP_NAME" API_TOKEN="$API_TOKEN" sh buildbox/deploy-appcenter.sh
fi

View File

@ -13,6 +13,8 @@ elif [ "$1" == "testinghockeyapp-local" ]; then
CERTS_PATH="$HOME/codesigning_data/certs/enterprise"
elif [ "$1" == "appstore" ]; then
CERTS_PATH="$HOME/codesigning_data/certs/distribution"
elif [ "$1" == "appstore-development" ]; then
CERTS_PATH="$HOME/codesigning_data/certs/development"
elif [ "$1" == "verify" ]; then
CERTS_PATH="$HOME/codesigning_data/certs/distribution"
else
@ -73,12 +75,15 @@ for f in "$CERTS_PATH"/*.p12; do
done
for f in "$CERTS_PATH"/*.cer; do
sudo security add-trusted-cert -d -r trustRoot -p codeSign -k "$MY_KEYCHAIN" "$f"
#sudo security add-trusted-cert -d -r trustRoot -p codeSign -k "$MY_KEYCHAIN" "$f"
security import "$f" -k "$MY_KEYCHAIN" -P "" -T /usr/bin/codesign -T /usr/bin/security
done
security import "build-system/AppleWWDRCAG3.cer" -k "$MY_KEYCHAIN" -P "" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -k "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
if [ "$1" == "hockeyapp" ] || [ "$1" == "appcenter-experimental" ] || [ "$1" == "appcenter-experimental-2" ]; then
if [ "$1" == "hockeyapp" ] || [ "$1" == "appcenter-experimental" ] || [ "$1" == "appcenter-experimental-2" ] || [ "$1" == "appstore-development" ]; then
APP_CONFIGURATION="release_arm64"
elif [ "$1" == "appstore" ]; then
APP_CONFIGURATION="release_universal"

View File

@ -20,7 +20,9 @@ swift_library(
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
"//submodules/MeshAnimationCache:MeshAnimationCache"
"//submodules/MeshAnimationCache:MeshAnimationCache",
"//submodules/Utils/RangeSet:RangeSet",
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
],
visibility = [
"//visibility:public",

View File

@ -11,6 +11,7 @@ import Display
import DeviceLocationManager
import TemporaryCachedPeerDataManager
import MeshAnimationCache
import InAppPurchaseManager
public final class TelegramApplicationOpenUrlCompletion {
public let completion: (Bool) -> Void
@ -28,9 +29,15 @@ public enum AccessType {
case unreachable
}
public enum TelegramAppBuildType {
case `internal`
case `public`
}
public final class TelegramApplicationBindings {
public let isMainApp: Bool
public let appBundleId: String
public let appBuildType: TelegramAppBuildType
public let containerPath: String
public let appSpecificScheme: String
public let openUrl: (String) -> Void
@ -55,9 +62,10 @@ public final class TelegramApplicationBindings {
public let requestSetAlternateIconName: (String?, @escaping (Bool) -> Void) -> Void
public let forceOrientation: (UIInterfaceOrientation) -> Void
public init(isMainApp: Bool, appBundleId: String, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) {
public init(isMainApp: Bool, appBundleId: String, appBuildType: TelegramAppBuildType, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) {
self.isMainApp = isMainApp
self.appBundleId = appBundleId
self.appBuildType = appBuildType
self.containerPath = containerPath
self.appSpecificScheme = appSpecificScheme
self.openUrl = openUrl
@ -168,6 +176,23 @@ public enum ResolvedUrlSettingsSection {
case devices
}
public struct ResolvedBotChoosePeerTypes: OptionSet {
public var rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let users = ResolvedBotChoosePeerTypes(rawValue: 1)
public static let bots = ResolvedBotChoosePeerTypes(rawValue: 2)
public static let groups = ResolvedBotChoosePeerTypes(rawValue: 4)
public static let channels = ResolvedBotChoosePeerTypes(rawValue: 16)
}
public struct ResolvedBotAdminRights: OptionSet {
public var rawValue: UInt32
@ -252,13 +277,12 @@ public enum ResolvedUrl {
case share(url: String?, text: String?, to: String?)
case wallpaper(WallpaperUrlParameter)
case theme(String)
#if ENABLE_WALLET
case wallet(address: String, amount: Int64?, comment: String?)
#endif
case settings(ResolvedUrlSettingsSection)
case joinVoiceChat(PeerId, String?)
case importStickers
case startAttach(peerId: PeerId, payload: String?)
case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?)
case invoice(slug: String, invoice: TelegramMediaInvoice)
case premiumOffer(reference: String?)
}
public enum NavigateToChatKeepStack {
@ -656,6 +680,7 @@ public protocol SharedAccountContext: AnyObject {
var locationManager: DeviceLocationManager? { get }
var callManager: PresentationCallManager? { get }
var contactDataManager: DeviceContactDataManager? { get }
var inAppPurchaseManager: InAppPurchaseManager? { get }
var activeAccountContexts: Signal<(primary: AccountContext?, accounts: [(AccountRecordId, AccountContext, Int32)], currentAuth: UnauthorizedAccount?), NoError> { get }
var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> { get }
@ -695,8 +720,8 @@ public protocol SharedAccountContext: AnyObject {
func openStorageUsage(context: AccountContext)
func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController)
func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void)
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: EnginePeer.Id, messageIds: Set<EngineMessage.Id>) -> Signal<ChatAvailableMessageActions, NoError>
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: EnginePeer.Id, messageIds: Set<EngineMessage.Id>, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal<ChatAvailableMessageActions, NoError>
func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set<EngineMessage.Id>) -> Signal<ChatAvailableMessageActions, NoError>
func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set<EngineMessage.Id>, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal<ChatAvailableMessageActions, NoError>
func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?)
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
@ -711,6 +736,8 @@ public protocol SharedAccountContext: AnyObject {
func makeChatQrCodeScreen(context: AccountContext, peer: Peer) -> ViewController
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController
func navigateToCurrentCall()
var hasOngoingCall: ValuePromise<Bool> { get }
var immediateHasOngoingCall: Bool { get }
@ -722,6 +749,26 @@ public protocol SharedAccountContext: AnyObject {
func beginNewAuth(testingEnvironment: Bool)
}
public enum PremiumIntroSource {
case settings
case stickers
case reactions
case ads
case upload
case groupsAndChannels
case pinnedChats
case publicLinks
case savedGifs
case savedStickers
case folders
case chatsPerFolder
case accounts
case appIcons
case about
case deeplink(String?)
case profile(PeerId)
}
#if ENABLE_WALLET
private final class TonInstanceData {
var config: String?
@ -822,6 +869,10 @@ public protocol AccountContext: AnyObject {
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
var meshAnimationCache: MeshAnimationCache { get }
var animatedEmojiStickers: [String: [StickerPackItem]] { get }
var userLimits: EngineConfiguration.UserLimits { get }
func storeSecureIdPassword(password: String)
func getStoredSecureIdPassword() -> String?

View File

@ -28,8 +28,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let isCopyProtectionEnabled: Bool
public let availableReactions: AvailableReactions?
public let defaultReaction: String?
public let isPremium: Bool
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: String?) {
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: String?, isPremium: Bool) {
self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions
@ -43,6 +44,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.isCopyProtectionEnabled = isCopyProtectionEnabled
self.availableReactions = availableReactions
self.defaultReaction = defaultReaction
self.isPremium = isPremium
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -82,6 +84,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.availableReactions != rhs.availableReactions {
return false
}
if lhs.isPremium != rhs.isPremium {
return false
}
return true
}
}

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import Postbox
import Display
import TelegramCore
public protocol ChatListController: ViewController {
var context: AccountContext { get }
@ -11,5 +12,5 @@ public protocol ChatListController: ViewController {
func activateSearch(filter: ChatListSearchFilter, query: String?)
func deactivateSearch(animated: Bool)
func activateCompose()
func maybeAskForPeerChatRemoval(peer: RenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)
func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool, deleteGloballyIfPossible: Bool, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void)
}

View File

@ -56,8 +56,9 @@ public final class ContactMultiselectionControllerParams {
public let filters: [ContactListFilter]
public let alwaysEnabled: Bool
public let limit: Int32?
public let reachedLimit: ((Int32) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false, limit: Int32? = nil) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.mode = mode
@ -65,6 +66,7 @@ public final class ContactMultiselectionControllerParams {
self.filters = filters
self.alwaysEnabled = alwaysEnabled
self.limit = limit
self.reachedLimit = reachedLimit
}
}

View File

@ -148,8 +148,6 @@ public enum FetchManagerPriority: Comparable {
}
}
public let FetchCompleteRange = IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>)
public protocol FetchManager {
var queue: Queue { get }

View File

@ -12,7 +12,7 @@ public func freeMediaFileInteractiveFetched(account: Account, fileReference: Fil
public func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal<Void, NoError> {
let file = fileReference.media
let mediaReference = AnyMediaReference.standalone(media: fileReference.media)
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil)
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int64.max) as Range<Int>), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil)
}
public func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal<FetchResourceSourceType, FetchResourceError> {
@ -37,7 +37,7 @@ public func messageMediaFileInteractiveFetched(context: AccountContext, message:
return messageMediaFileInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), file: file, userInitiated: userInitiated, priority: .userInitiated)
}
public func messageMediaFileInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, file: TelegramMediaFile, ranges: IndexSet = IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>), userInitiated: Bool, priority: FetchManagerPriority) -> Signal<Void, NoError> {
public func messageMediaFileInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, file: TelegramMediaFile, ranges: IndexSet = IndexSet(integersIn: 0 ..< Int(Int64.max) as Range<Int>), userInitiated: Bool, priority: FetchManagerPriority) -> Signal<Void, NoError> {
let mediaReference = AnyMediaReference.message(message: messageReference, media: file)
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: ranges, statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: nil)
}
@ -46,17 +46,17 @@ public func messageMediaFileCancelInteractiveFetch(context: AccountContext, mess
context.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource)
}
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool = true, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int64>? = nil, userInitiated: Bool = true, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, range: range, userInitiated: userInitiated, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
}
public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, image: TelegramMediaImage, resource: MediaResource, range: Range<Int64>? = nil, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
let mediaReference = AnyMediaReference.message(message: messageReference, media: image)
let ranges: IndexSet
if let range = range {
ranges = IndexSet(integersIn: range)
ranges = IndexSet(integersIn: Int(range.lowerBound) ..< Int(range.upperBound))
} else {
ranges = FetchCompleteRange
ranges = IndexSet(integersIn: Int(0) ..< Int(Int64.max))
}
return fetchManager.interactivelyFetched(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), ranges: ranges, statsCategory: .image, elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: storeToDownloadsPeerType)
}

View File

@ -6,6 +6,7 @@ import UIKit
import AsyncDisplayKit
import TelegramAudio
import UniversalMediaPlayer
import RangeSet
public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId {
case peer(PeerId)
@ -200,7 +201,7 @@ public protocol UniversalVideoManager: AnyObject {
func addPlaybackCompleted(id: AnyHashable, _ f: @escaping () -> Void) -> Int
func removePlaybackCompleted(id: AnyHashable, index: Int)
func statusSignal(content: UniversalVideoContent) -> Signal<MediaPlayerStatus?, NoError>
func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(IndexSet, Int)?, NoError>
func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(RangeSet<Int64>, Int64)?, NoError>
}
public enum AudioRecordingState: Equatable {

View File

@ -30,6 +30,10 @@ public struct ChatListNodePeersFilter: OptionSet {
public static let excludeChannels = ChatListNodePeersFilter(rawValue: 1 << 12)
public static let onlyGroupsAndChannels = ChatListNodePeersFilter(rawValue: 1 << 13)
public static let excludeGroups = ChatListNodePeersFilter(rawValue: 1 << 14)
public static let excludeUsers = ChatListNodePeersFilter(rawValue: 1 << 15)
public static let excludeBots = ChatListNodePeersFilter(rawValue: 1 << 16)
}

View File

@ -8,11 +8,12 @@ import Display
import TelegramAudio
import UniversalMediaPlayer
import AVFoundation
import RangeSet
public protocol UniversalVideoContentNode: AnyObject {
var ready: Signal<Void, NoError> { get }
var status: Signal<MediaPlayerStatus, NoError> { get }
var bufferingStatus: Signal<(IndexSet, Int)?, NoError> { get }
var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> { get }
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
@ -63,10 +64,11 @@ public protocol UniversalVideoDecoration: AnyObject {
}
public enum UniversalVideoPriority: Int32, Comparable {
case secondaryOverlay = 0
case embedded = 1
case gallery = 2
case overlay = 3
case minimal = 0
case secondaryOverlay = 1
case embedded = 2
case gallery = 3
case overlay = 4
public static func <(lhs: UniversalVideoPriority, rhs: UniversalVideoPriority) -> Bool {
return lhs.rawValue < rhs.rawValue
@ -104,8 +106,8 @@ public final class UniversalVideoNode: ASDisplayNode {
return self._status.get()
}
private let _bufferingStatus = Promise<(IndexSet, Int)?>()
public var bufferingStatus: Signal<(IndexSet, Int)?, NoError> {
private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>()
public var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> {
return self._bufferingStatus.get()
}

View File

@ -1,39 +0,0 @@
import TelegramCore
public struct VideoCallsConfiguration: Equatable {
public enum VideoCallsSupport {
case disabled
case full
case onlyVideo
}
public var videoCallsSupport: VideoCallsSupport
public init(appConfiguration: AppConfiguration) {
var videoCallsSupport: VideoCallsSupport = .full
if let data = appConfiguration.data, let value = data["video_calls_support"] as? String {
switch value {
case "disabled":
videoCallsSupport = .disabled
case "full":
videoCallsSupport = .full
case "only_video":
videoCallsSupport = .onlyVideo
default:
videoCallsSupport = .full
}
}
self.videoCallsSupport = videoCallsSupport
}
}
public extension VideoCallsConfiguration {
var areVideoCallsEnabled: Bool {
switch self.videoCallsSupport {
case .disabled:
return false
case .full, .onlyVideo:
return true
}
}
}

View File

@ -562,23 +562,23 @@ private final class AnimatedStickerDirectFrameSourceCache {
}
final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
public final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
private let queue: Queue
private let data: Data
private let width: Int
private let height: Int
private let cache: AnimatedStickerDirectFrameSourceCache?
private let bytesPerRow: Int
let frameCount: Int
let frameRate: Int
public let frameCount: Int
public let frameRate: Int
fileprivate var currentFrame: Int
private let animation: LottieInstance
var frameIndex: Int {
public var frameIndex: Int {
return self.currentFrame % self.frameCount
}
init?(queue: Queue, data: Data, width: Int, height: Int, cachePathPrefix: String?, useMetalCache: Bool = false, fitzModifier: EmojiFitzModifier?) {
public init?(queue: Queue, data: Data, width: Int, height: Int, cachePathPrefix: String?, useMetalCache: Bool = false, fitzModifier: EmojiFitzModifier?) {
self.queue = queue
self.data = data
self.width = width
@ -604,7 +604,7 @@ final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
assert(self.queue.isCurrent())
}
func takeFrame(draw: Bool) -> AnimatedStickerFrame? {
public func takeFrame(draw: Bool) -> AnimatedStickerFrame? {
let frameIndex = self.currentFrame % self.frameCount
self.currentFrame += 1
if draw {
@ -630,11 +630,11 @@ final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
}
}
func skipToEnd() {
public func skipToEnd() {
self.currentFrame = self.frameCount - 1
}
func skipToFrameIndex(_ index: Int) {
public func skipToFrameIndex(_ index: Int) {
self.currentFrame = index
}
}

View File

@ -144,7 +144,7 @@ public protocol AnimatedStickerNodeSource {
var isVideo: Bool { get }
func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError>
func directDataPath() -> Signal<String, NoError>
func directDataPath(attemptSynchronously: Bool) -> Signal<String?, NoError>
}
public final class AnimatedStickerNode: ASDisplayNode {
@ -163,6 +163,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
public var completed: (Bool) -> Void = { _ in }
public var frameUpdated: (Int, Int) -> Void = { _, _ in }
public private(set) var currentFrameIndex: Int = 0
public private(set) var currentFrameCount: Int = 0
private var playFromIndex: Int?
private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil)
@ -303,9 +304,10 @@ public final class AnimatedStickerNode: ASDisplayNode {
strongSelf.play(firstFrame: true)
}
}
self.disposable.set((source.directDataPath()
self.disposable.set((source.directDataPath(attemptSynchronously: false)
|> filter { $0 != nil }
|> deliverOnMainQueue).start(next: { path in
f(path)
f(path!)
}))
case .cached:
self.disposable.set((source.cachedDataPath(width: width, height: height)
@ -373,6 +375,11 @@ public final class AnimatedStickerNode: ASDisplayNode {
}
private var isSetUpForPlayback = false
public func playOnce() {
self.playbackMode = .once
self.play()
}
public func play(firstFrame: Bool = false, fromIndex: Int? = nil) {
if !firstFrame {
@ -452,6 +459,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
strongSelf.frameUpdated(frame.index, frame.totalFrames)
strongSelf.currentFrameIndex = frame.index
strongSelf.currentFrameCount = frame.totalFrames
if frame.isLastFrame {
var stopped = false
@ -556,6 +564,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
strongSelf.frameUpdated(frame.index, frame.totalFrames)
strongSelf.currentFrameIndex = frame.index
strongSelf.currentFrameCount = frame.totalFrames;
if frame.isLastFrame {
var stopped = false

View File

@ -212,14 +212,12 @@ using AS::MutexLocker;
displayBlock = ^id{
CHECK_CANCELLED_AND_RETURN_NIL();
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
for (dispatch_block_t block in displayBlocks) {
CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext());
block();
}
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
UIImage *image = ASGraphicsCreateImage(self.primitiveTraitCollection, bounds.size, opaque, contentsScaleForDisplay, nil, isCancelledBlock, ^{
for (dispatch_block_t block in displayBlocks) {
if (isCancelledBlock()) return;
block();
}
});
ASDN_DELAY_FOR_DISPLAY();
return image;
@ -228,38 +226,35 @@ using AS::MutexLocker;
displayBlock = ^id{
CHECK_CANCELLED_AND_RETURN_NIL();
if (shouldCreateGraphicsContext) {
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
}
CGContextRef currentContext = UIGraphicsGetCurrentContext();
UIImage *image = nil;
if (shouldCreateGraphicsContext && !currentContext) {
//ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size));
return nil;
}
// For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or
// _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs.
[self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters];
if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock];
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
[self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
}
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
if (shouldCreateGraphicsContext) {
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
image = ASGraphicsGetImageAndEndCurrentContext();
}
ASDN_DELAY_FOR_DISPLAY();
return image;
__block UIImage *image = nil;
void (^workWithContext)() = ^{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
if (shouldCreateGraphicsContext && !currentContext) {
ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size));
return;
}
// For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or
// _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs.
[self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters];
if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock];
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
[self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
}
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
ASDN_DELAY_FOR_DISPLAY();
};
if (shouldCreateGraphicsContext) {
return ASGraphicsCreateImage(self.primitiveTraitCollection, bounds.size, opaque, contentsScaleForDisplay, nil, isCancelledBlock, workWithContext);
} else {
workWithContext();
return image;
}
};
}
@ -309,9 +304,6 @@ using AS::MutexLocker;
}
__instanceLock__.lock();
ASCornerRoundingType cornerRoundingType = _cornerRoundingType;
CGFloat cornerRadius = _cornerRadius;
CGFloat contentsScale = _contentsScaleForDisplay;
ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext;
__instanceLock__.unlock();
@ -320,48 +312,6 @@ using AS::MutexLocker;
didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
}
if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0f) {
CGRect bounds = CGRectZero;
if (context == NULL) {
bounds = self.threadSafeBounds;
bounds.size.width *= contentsScale;
bounds.size.height *= contentsScale;
CGFloat white = 0.0f, alpha = 0.0f;
[backgroundColor getWhite:&white alpha:&alpha];
ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale);
[*image drawInRect:bounds];
} else {
bounds = CGContextGetClipBoundingBox(context);
}
ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self);
UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds];
[roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]];
roundedHole.usesEvenOddFillRule = YES;
UIBezierPath *roundedPath = nil;
if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0
CGFloat strokeThickness = borderWidth * contentsScale;
CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f;
roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset)
cornerRadius:_cornerRadius * contentsScale];
roundedPath.lineWidth = strokeThickness;
[[UIColor colorWithCGColor:borderColor] setStroke];
}
// Punch out the corners by copying the backgroundColor over them.
// This works for everything from clearColor to opaque colors.
[backgroundColor setFill];
[roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f];
[roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil.
if (*image) {
*image = ASGraphicsGetImageAndEndCurrentContext();
}
}
}
- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously

View File

@ -1539,41 +1539,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}
}
- (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor:(UIColor *)backgroundColor
{
ASPerformBlockOnMainThread(^{
for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) {
// Layers are, in order: Top Left, Top Right, Bottom Right, Bottom Left.
// anchorPoint is Bottom Left at 0,0 and Top Right at 1,1.
BOOL isTop = (idx == 0 || idx == 1);
BOOL isRight = (idx == 1 || idx == 2);
CGSize size = CGSizeMake(radius + 1, radius + 1);
ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
CGContextRef ctx = UIGraphicsGetCurrentContext();
if (isRight == YES) {
CGContextTranslateCTM(ctx, -radius + 1, 0);
}
if (isTop == YES) {
CGContextTranslateCTM(ctx, 0, -radius + 1);
}
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius];
[roundedRect setUsesEvenOddFillRule:YES];
[roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]];
[backgroundColor setFill];
[roundedRect fill];
// No lock needed, as _clipCornerLayers is only modified on the main thread.
CALayer *clipCornerLayer = _clipCornerLayers[idx];
clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage);
clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height);
clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0);
}
[self _layoutClipCornersIfNeeded];
});
}
- (void)_setClipCornerLayersVisible:(BOOL)visible
{
}
@ -1634,7 +1599,6 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}
else if (newRoundingType == ASCornerRoundingTypeClipping) {
// Clip corners already exist, but the radius has changed.
[self _updateClipCornerLayerContentsWithRadius:newCornerRadius backgroundColor:self.backgroundColor];
}
}
}

View File

@ -7,161 +7,130 @@
//
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASCGImageBuffer.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASConfigurationInternal.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <UIKit/UIGraphics.h>
#import <UIKit/UIImage.h>
#import <objc/runtime.h>
#import <AsyncDisplayKit/ASAvailability.h>
/**
* Our version of the private CGBitmapGetAlignedBytesPerRow function.
*
* In both 32-bit and 64-bit, this function rounds up to nearest multiple of 32
* in iOS 9, 10, and 11. We'll try to catch if this ever changes by asserting that
* the bytes-per-row for a 1x1 context from the system is 32.
*/
static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) {
// Add 31 then zero out low 5 bits.
return (baseValue + 31) & ~0x1F;
}
/**
* A key used to associate CGContextRef -> NSMutableData, nonatomic retain.
*
* That way the data will be released when the context dies. If they pull an image,
* we will retain the data object (in a CGDataProvider) before releasing the context.
*/
static UInt8 __contextDataAssociationKey;
#if AS_AT_LEAST_IOS13
#define ASPerformBlockWithTraitCollection(work, traitCollection) \
if (@available(iOS 13.0, tvOS 13.0, *)) { \
UITraitCollection *uiTraitCollection = ASPrimitiveTraitCollectionToUITraitCollection(traitCollection); \
[uiTraitCollection performAsCurrentTraitCollection:^{ \
work(); \
}];\
} else { \
work(); \
}
#else
#define ASPerformBlockWithTraitCollection(work, traitCollection) work();
#endif
#pragma mark - Graphics Contexts
void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
NS_AVAILABLE_IOS(10)
NS_INLINE void ASConfigureExtendedRange(UIGraphicsImageRendererFormat *format)
{
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
return;
if (AS_AVAILABLE_IOS_TVOS(12, 12)) {
// nop. We always use automatic range on iOS >= 12.
} else {
// Currently we never do wide color. One day we could pipe this information through from the ASImageNode if it was worth it.
format.prefersExtendedRange = NO;
}
// We use "reference contexts" to get device-specific options that UIKit
// uses.
static dispatch_once_t onceToken;
static CGContextRef refCtxOpaque;
static CGContextRef refCtxTransparent;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 1);
refCtxOpaque = CGContextRetain(UIGraphicsGetCurrentContext());
ASDisplayNodeCAssert(CGBitmapContextGetBytesPerRow(refCtxOpaque) == 32, @"Expected bytes per row to be aligned to 32. Has CGBitmapGetAlignedBytesPerRow implementation changed?");
UIGraphicsEndImageContext();
// Make transparent ref context.
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1);
refCtxTransparent = CGContextRetain(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
});
// These options are taken from UIGraphicsBeginImageContext.
CGContextRef refCtx = opaque ? refCtxOpaque : refCtxTransparent;
CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(refCtx);
if (scale == 0) {
scale = ASScreenScale();
}
size_t intWidth = (size_t)ceil(size.width * scale);
size_t intHeight = (size_t)ceil(size.height * scale);
size_t bitsPerComponent = CGBitmapContextGetBitsPerComponent(refCtx);
size_t bytesPerRow = CGBitmapContextGetBitsPerPixel(refCtx) * intWidth / 8;
bytesPerRow = ASGraphicsGetAlignedBytesPerRow(bytesPerRow);
size_t bufferSize = bytesPerRow * intHeight;
CGColorSpaceRef colorspace = CGBitmapContextGetColorSpace(refCtx);
// We create our own buffer, and wrap the context around that. This way we can prevent
// the copy that usually gets made when you form a CGImage from the context.
ASCGImageBuffer *buffer = [[ASCGImageBuffer alloc] initWithLength:bufferSize];
CGContextRef context = CGBitmapContextCreate(buffer.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo);
// Transfer ownership of the data to the context. So that if the context
// is destroyed before we create an image from it, the data will be released.
objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, buffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Set the CTM to account for iOS orientation & specified scale.
// If only we could use CGContextSetBaseCTM. It doesn't
// seem like there are any consequences for our use case
// but we'll be on the look out. The internet hinted that it
// affects shadowing but I tested and shadowing works.
CGContextTranslateCTM(context, 0, intHeight);
CGContextScaleCTM(context, scale, -scale);
// Save the state so we can restore it and recover our scale in GetImageAndEnd
CGContextSaveGState(context);
// Transfer context ownership to the UIKit stack.
UIGraphicsPushContext(context);
CGContextRelease(context);
}
UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() NS_RETURNS_RETAINED
UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage *sourceImage,
asdisplaynode_iscancelled_block_t NS_NOESCAPE isCancelled,
void (^NS_NOESCAPE work)())
{
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// Pop the context and make sure we have one.
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) {
ASDisplayNodeCFailAssert(@"Can't end image context without having begun one.");
return nil;
}
// Read the device-specific ICC-based color space to use for the image.
// For DeviceRGB contexts (e.g. UIGraphics), CGBitmapContextCreateImage
// generates an image in a device-specific color space (for wide color support).
// We replicate that behavior, even though at this time CA does not
// require the image to be in this space. Plain DeviceRGB images seem
// to be treated exactly the same, but better safe than sorry.
static CGColorSpaceRef imageColorSpace;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext();
imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage));
ASDisplayNodeCAssertNotNil(imageColorSpace, nil);
UIGraphicsEndImageContext();
});
// Retrieve our buffer and create a CGDataProvider from it.
ASCGImageBuffer *buffer = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey);
ASDisplayNodeCAssertNotNil(buffer, nil);
CGDataProviderRef provider = [buffer createDataProviderAndInvalidate];
// Create the CGImage. Options taken from CGBitmapContextCreateImage.
CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(provider);
// We saved our GState right after setting the CTM so that we could restore it
// here and get the original scale back.
CGContextRestoreGState(context);
CGFloat scale = CGContextGetCTM(context).a;
// Note: popping from the UIKit stack will probably destroy the context.
context = NULL;
UIGraphicsPopContext();
UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp];
CGImageRelease(cgImg);
return result;
return ASGraphicsCreateImage(ASPrimitiveTraitCollectionMakeDefault(), size, opaque, scale, sourceImage, isCancelled, work);
}
void ASGraphicsEndImageContext()
{
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
UIGraphicsEndImageContext();
return;
UIImage *ASGraphicsCreateImage(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE isCancelled, void (NS_NOESCAPE ^work)()) {
if (@available(iOS 10.0, *)) {
if (true /*ASActivateExperimentalFeature(ASExperimentalDrawingGlobal)*/) {
// If they used default scale, reuse one of two preferred formats.
static UIGraphicsImageRendererFormat *defaultFormat;
static UIGraphicsImageRendererFormat *opaqueFormat;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
defaultFormat = [UIGraphicsImageRendererFormat preferredFormat];
opaqueFormat = [UIGraphicsImageRendererFormat preferredFormat];
} else {
defaultFormat = [UIGraphicsImageRendererFormat defaultFormat];
opaqueFormat = [UIGraphicsImageRendererFormat defaultFormat];
}
opaqueFormat.opaque = YES;
ASConfigureExtendedRange(defaultFormat);
ASConfigureExtendedRange(opaqueFormat);
});
UIGraphicsImageRendererFormat *format;
if (sourceImage) {
if (sourceImage.renderingMode == UIImageRenderingModeAlwaysTemplate) {
// Template images will be black and transparent, so if we use
// sourceImage.imageRenderFormat it will assume a grayscale color space.
// This is not good because a template image should be able to tint to any color,
// so we'll just use the default here.
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
format = [UIGraphicsImageRendererFormat preferredFormat];
} else {
format = [UIGraphicsImageRendererFormat defaultFormat];
}
} else {
format = sourceImage.imageRendererFormat;
}
// We only want the private bits (color space and bits per component) from the image.
// We have our own ideas about opacity and scale.
format.opaque = opaque;
format.scale = scale;
} else if (scale == 0 || scale == ASScreenScale()) {
format = opaque ? opaqueFormat : defaultFormat;
} else {
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
format = [UIGraphicsImageRendererFormat preferredFormat];
} else {
format = [UIGraphicsImageRendererFormat defaultFormat];
}
if (opaque) format.opaque = YES;
format.scale = scale;
ASConfigureExtendedRange(format);
}
// Avoid using the imageWithActions: method because it does not support cancellation at the
// last moment i.e. before actually creating the resulting image.
__block UIImage *image;
NSError *error;
[[[UIGraphicsImageRenderer alloc] initWithSize:size format:format]
runDrawingActions:^(UIGraphicsImageRendererContext *rendererContext) {
ASDisplayNodeCAssert(UIGraphicsGetCurrentContext(), @"Should have a context!");
ASPerformBlockWithTraitCollection(work, traitCollection);
}
completionActions:^(UIGraphicsImageRendererContext *rendererContext) {
if (isCancelled == nil || !isCancelled()) {
image = rendererContext.currentImage;
}
}
error:&error];
if (error) {
NSCAssert(NO, @"Error drawing: %@", error);
}
return image;
}
}
UIGraphicsPopContext();
// Bad OS or experiment flag. Use UIGraphics* API.
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
ASPerformBlockWithTraitCollection(work, traitCollection)
UIImage *image = nil;
if (isCancelled == nil || !isCancelled()) {
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
return image;
}
UIImage *ASGraphicsCreateImageWithTraitCollectionAndOptions(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * sourceImage, void (NS_NOESCAPE ^work)()) {
return ASGraphicsCreateImage(traitCollection, size, opaque, scale, sourceImage, nil, work);
}

View File

@ -8,11 +8,52 @@
//
#import <AsyncDisplayKit/ASTextKitComponents.h>
#import "ASTextKitContext.h"
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASMainThreadDeallocation.h>
#import <tgmath.h>
@implementation ASCustomTextContainer
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(nullable CGRect *)remainingRect {
CGRect result = [super lineFragmentRectForProposedRect:proposedRect atIndex:characterIndex writingDirection:baseWritingDirection remainingRect:remainingRect];
#if DEBUG
if (result.origin.y < 10.0f) {
result.size.width -= 21.0f;
if (result.size.width < 0.0f) {
result.size.width = 0.0f;
}
}
#endif
return result;
}
@end
@interface ASCustomLayoutManager : NSLayoutManager
@end
@implementation ASCustomLayoutManager
- (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
/*CGGlyph glyph = [self glyphAtIndex:glyphsToShow.location];
if (glyph) {
}
CGRect bounds = [self boundingRectForGlyphRange:glyphsToShow inTextContainer:[self textContainerForGlyphAtIndex:glyphsToShow.location effectiveRange:nil]];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor grayColor].CGColor);
CGContextFillRect(context, bounds);*/
[super drawGlyphsForGlyphRange:glyphsToShow atPoint:origin];
}
@end
@interface ASTextKitComponentsTextView () {
// Prevent UITextView from updating contentOffset while deallocating: https://github.com/TextureGroup/Texture/issues/860
BOOL _deallocating;
@ -83,7 +124,7 @@
return [self componentsWithTextStorage:textStorage
textContainerSize:textContainerSize
layoutManager:[[NSLayoutManager alloc] init]];
layoutManager:[[ASCustomLayoutManager alloc] init]];
}
+ (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage
@ -97,7 +138,8 @@
components.layoutManager = layoutManager;
[components.textStorage addLayoutManager:components.layoutManager];
components.textContainer = [[NSTextContainer alloc] initWithSize:textContainerSize];
components.textContainer = [[ASCustomTextContainer alloc] initWithSize:textContainerSize];
//components.textContainer.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(textContainerSize.width - 60.0, 0.0, 60.0, 40.0)]];
components.textContainer.lineFragmentPadding = 0.0; // We want the text laid out up to the very edges of the text-view.
[components.layoutManager addTextContainer:components.textContainer];

View File

@ -50,4 +50,8 @@ AS_SUBCLASSING_RESTRICTED
@end
@interface ASCustomTextContainer : NSTextContainer
@end
#endif

View File

@ -58,7 +58,7 @@
[_textStorage setAttributedString:attributedString];
}
_textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
_textContainer = [[ASCustomTextContainer alloc] initWithSize:constrainedSize];
// We want the text laid out up to the very edges of the container.
_textContainer.lineFragmentPadding = 0;
_textContainer.lineBreakMode = lineBreakMode;

View File

@ -11,7 +11,7 @@
#pragma once
#define AS_TLS_AVAILABLE 0
#define AS_TLS_AVAILABLE 1
#ifndef AS_ENABLE_TEXTNODE
#define AS_ENABLE_TEXTNODE 1 // Enable old TextNode by default

View File

@ -6,46 +6,60 @@
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <CoreGraphics/CoreGraphics.h>
@class UIImage;
/**
* Functions for creating one-shot graphics contexts that do not have to copy
* their contents when an image is generated from them. This is efficient
* for our use, since we do not reuse graphics contexts.
*
* The API mirrors the UIGraphics API, with the exception that forming an image
* ends the context as well.
*
* Note: You must not mix-and-match between ASGraphics* and UIGraphics* functions
* within the same drawing operation.
*/
#import <AsyncDisplayKit/ASBlockTypes.h>
#import <AsyncDisplayKit/ASTraitCollection.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Creates a one-shot context.
* A wrapper for the UIKit drawing APIs. If you are in ASExperimentalDrawingGlobal, and you have iOS >= 10, we will create
* a UIGraphicsRenderer with an appropriate format. Otherwise, we will use UIGraphicsBeginImageContext et al.
*
* Behavior is the same as UIGraphicsBeginImageContextWithOptions.
* @param size The size of the context.
* @param opaque Whether the context should be opaque or not.
* @param scale The scale of the context. 0 uses main screen scale.
* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its
* preferred renderer format if we are using UIGraphicsImageRenderer.
* @param isCancelled An optional block for canceling the drawing before forming the image. Only takes effect under
* the legacy code path, as UIGraphicsRenderer does not support cancellation.
* @param work A block, wherein the current UIGraphics context is set based on the arguments.
*
* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext.
*/
AS_EXTERN void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE _Nullable isCancelled, void (NS_NOESCAPE ^work)(void)) ASDISPLAYNODE_DEPRECATED_MSG("Use ASGraphicsCreateImageWithTraitCollectionAndOptions instead");
/**
* Generates and image and ends the current one-shot context.
*
* Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext.
*/
AS_EXTERN UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void) NS_RETURNS_RETAINED;
* A wrapper for the UIKit drawing APIs. If you are in ASExperimentalDrawingGlobal, and you have iOS >= 10, we will create
* a UIGraphicsRenderer with an appropriate format. Otherwise, we will use UIGraphicsBeginImageContext et al.
*
* @param traitCollection Trait collection. The `work` block will be executed with this trait collection, so it will affect dynamic colors, etc.
* @param size The size of the context.
* @param opaque Whether the context should be opaque or not.
* @param scale The scale of the context. 0 uses main screen scale.
* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its
* preferred renderer format if we are using UIGraphicsImageRenderer.
* @param isCancelled An optional block for canceling the drawing before forming the image.
* @param work A block, wherein the current UIGraphics context is set based on the arguments.
*
* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext.
*/
UIImage *ASGraphicsCreateImage(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t _Nullable NS_NOESCAPE isCancelled, void (NS_NOESCAPE ^work)(void));
/**
* Call this if you want to end the current context without making an image.
*
* Behavior is the same as UIGraphicsEndImageContext.
*/
AS_EXTERN void ASGraphicsEndImageContext(void);
* A wrapper for the UIKit drawing APIs.
*
* @param traitCollection Trait collection. The `work` block will be executed with this trait collection, so it will affect dynamic colors, etc.
* @param size The size of the context.
* @param opaque Whether the context should be opaque or not.
* @param scale The scale of the context. 0 uses main screen scale.
* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its
* preferred renderer format if we are using UIGraphicsImageRenderer.
* @param work A block, wherein the current UIGraphics context is set based on the arguments.
*
* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext.
*/
UIImage *ASGraphicsCreateImageWithTraitCollectionAndOptions(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, void (NS_NOESCAPE ^work)(void)) ASDISPLAYNODE_DEPRECATED_MSG("Use ASGraphicsCreateImage instead");
NS_ASSUME_NONNULL_END

View File

@ -212,6 +212,7 @@ namespace AS {
if (AS_AVAILABLE_IOS_TVOS(10, 10)) {
gMutex_unfair = ASActivateExperimentalFeature(ASExperimentalUnfairLock);
}
gMutex_unfair = true;
});
if (recursive) {

View File

@ -28,6 +28,7 @@ swift_library(
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
"//submodules/Pasteboard:Pasteboard",
"//submodules/ContextUI:ContextUI",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
],
visibility = [
"//visibility:public",

View File

@ -18,6 +18,7 @@ import InvisibleInkDustNode
import TextInputMenu
import ChatPresentationInterfaceState
import Pasteboard
import EmojiTextAttachmentView
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
private let minInputFontSize: CGFloat = 5.0
@ -208,7 +209,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
}
textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed)
textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
self.updatingInputState = false
self.updateTextNodeText(animated: animated)
@ -241,6 +242,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private var spoilersRevealed = false
private var emojiViewProvider: ((String) -> UIView)?
private var maxCaptionLength: Int32?
public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, isCaption: Bool = false, isAttachment: Bool = false, presentController: @escaping (ViewController) -> Void) {
self.context = context
self.presentationInterfaceState = presentationInterfaceState
@ -311,7 +316,32 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
self.textInputBackgroundNode.view.addGestureRecognizer(recognizer)
/*self.emojiViewProvider = { [weak self] emoji in
guard let strongSelf = self, let file = strongSelf.context.animatedEmojiStickers[emoji]?.first?.file else {
return UIView()
}
return EmojiTextAttachmentView(context: context, file: file)
}*/
self.updateSendButtonEnabled(isCaption || isAttachment, animated: false)
if self.isCaption || self.isAttachment {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> mapToSignal { peer -> Signal<Int32, NoError> in
if let peer = peer {
return self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.UserLimits.init(isPremium: peer.isPremium))
|> map { limits in
return limits.maxCaptionLength
}
} else {
return .complete()
}
}
|> deliverOnMainQueue).start(next: { [weak self] maxCaptionLength in
self?.maxCaptionLength = maxCaptionLength
})
}
}
public var sendPressed: ((NSAttributedString?) -> Void)?
@ -322,7 +352,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
guard let presentationInterfaceState = self.presentationInterfaceState else {
return 0.0
}
return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact))
return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact))
}
public func setCaption(_ caption: NSAttributedString?) {
@ -466,7 +496,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
return minimalHeight
}
public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
let hadLayout = self.validLayout != nil
let previousAdditionalSideInsets = self.validLayout?.3
self.validLayout = (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary)
@ -741,7 +771,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
@objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed)
refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputTypingAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize)
self.updateSpoiler()
@ -876,9 +906,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
textInputNode.textView.isScrollEnabled = false
refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed)
refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed)
textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
if textInputNode.textView.subviews.count > 1, animated {
let containerView = textInputNode.textView.subviews[1]
@ -920,8 +950,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
private func updateCounterTextNode(transition: ContainedViewLayoutTransition) {
let inputTextMaxLength: Int32?
if self.isCaption || self.isAttachment {
inputTextMaxLength = self.context.currentLimitsConfiguration.with { $0 }.maxMediaCaptionLength
if let maxCaptionLength = self.maxCaptionLength {
inputTextMaxLength = maxCaptionLength
} else {
inputTextMaxLength = nil
}
@ -968,7 +998,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let textFont = Font.regular(baseFontSize)
let accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
let attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false)
let attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
let range = (attributedText.string as NSString).range(of: "\n")
if range.location != NSNotFound {
@ -1080,7 +1110,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.focusUpdated?(true)
if self.isCaption, let (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) = self.validLayout, let presentationInterfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics)
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics)
}
}
@ -1091,7 +1121,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
self.focusUpdated?(false)
if self.isCaption, let (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) = self.validLayout, let presentationInterfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics)
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics)
}
}
@ -1234,7 +1264,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
}
}
if cleanText != text {
let string = NSMutableAttributedString(attributedString: editableTextNode.attributedText ?? NSAttributedString())
var textColor: UIColor = .black
@ -1245,7 +1275,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
}
let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed)
let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
string.replaceCharacters(in: range, with: cleanReplacementString)
self.textInputNode?.attributedText = string
self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0)
@ -1290,8 +1320,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
@objc func sendButtonPressed() {
let inputTextMaxLength: Int32?
if self.isCaption || self.isAttachment {
inputTextMaxLength = self.context.currentLimitsConfiguration.with { $0 }.maxMediaCaptionLength
if let maxCaptionLength = self.maxCaptionLength {
inputTextMaxLength = maxCaptionLength
} else {
inputTextMaxLength = nil
}

View File

@ -32,6 +32,7 @@ swift_library(
"//submodules/PhotoResources:PhotoResources",
"//submodules/MediaResources:MediaResources",
"//submodules/SemanticStatusNode:SemanticStatusNode",
"//submodules/MoreButtonNode:MoreButtonNode",
"//submodules/Components/AnimatedStickerComponent:AnimatedStickerComponent",
],
visibility = [

View File

@ -380,6 +380,8 @@ public class AttachmentController: ViewController {
override func didLoad() {
super.didLoad()
self.view.disablesInteractiveModalDismiss = true
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
if let controller = self.controller {
@ -567,7 +569,7 @@ public class AttachmentController: ViewController {
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
let targetPosition = self.container.position
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height)
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
self.container.position = startPosition
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
@ -783,7 +785,9 @@ public class AttachmentController: ViewController {
}
let controllers = self.currentControllers
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size))
if !self.animating {
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size))
}
let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets)
@ -832,11 +836,7 @@ public class AttachmentController: ViewController {
}
}
}
deinit {
print()
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

@ -1096,7 +1096,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
if textInputPanelNode.frame.width.isZero {
panelTransition = .immediate
}
let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics)
let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics)
let panelFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: panelHeight)
if textInputPanelNode.frame.width.isZero {
textInputPanelNode.frame = panelFrame

View File

@ -22,7 +22,6 @@ swift_library(
"//submodules/AlertUI:AlertUI",
"//submodules/AppBundle:AppBundle",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/AnimationUI:AnimationUI",
"//submodules/PresentationDataUtils:PresentationDataUtils",

View File

@ -5,7 +5,6 @@ import AsyncDisplayKit
import Display
import SolidRoundedButtonNode
import SwiftSignalKit
import OverlayStatusController
import AnimationUI
import AccountContext
import TelegramPresentationData

View File

@ -18,7 +18,9 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, st
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor)
return parseMarkdownIntoAttributedString(strings.Login_ShortCallTitle, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)
case .call, .flashCall:
case .call:
return NSAttributedString(string: strings.Login_CodeSentCall, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center)
case .flashCall:
return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center)
}
}
@ -35,7 +37,13 @@ public func authorizationNextOptionText(currentType: SentAuthorizationCodeType,
let timeString = NSString(format: "%d:%.02d", Int(minutes), Int(seconds))
return (NSAttributedString(string: strings.Login_WillSendSms(timeString as String).string, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
}
case .call, .flashCall, .missedCall:
case .call:
if timeout <= 0 {
return (NSAttributedString(string: strings.Login_CodeSentCall, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
} else {
return (NSAttributedString(string: String(format: strings.ChangePhoneNumberCode_CallTimer(String(format: "%d:%.2d", minutes, seconds)).string, minutes, seconds), font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
}
case .flashCall, .missedCall:
if timeout <= 0 {
return (NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false)
} else {

View File

@ -55,21 +55,6 @@ private class AvatarNodeParameters: NSObject {
}
}
private func generateGradientFilledCircleImage(diameter: CGFloat, colors: NSArray) -> UIImage? {
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.addEllipse(in: bounds)
context.clip()
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: bounds.size.height), options: CGGradientDrawingOptions())
})
}
private let grayscaleColors: NSArray = [
UIColor(rgb: 0xb1b1b1).cgColor, UIColor(rgb: 0xcdcdcd).cgColor
]
@ -117,7 +102,7 @@ public enum AvatarNodeImageOverride: Equatable {
case savedMessagesIcon
case repliesIcon
case archivedChatsIcon(hiddenByDefault: Bool)
case editAvatarIcon
case editAvatarIcon(forceNone: Bool)
case deletedIcon
case phoneIcon
}
@ -322,8 +307,8 @@ public final class AvatarNode: ASDisplayNode {
case let .archivedChatsIcon(hiddenByDefault):
representation = nil
icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault)
case .editAvatarIcon:
representation = peer?.smallProfileImage
case let .editAvatarIcon(forceNone):
representation = forceNone ? nil : peer?.smallProfileImage
icon = .editAvatarIcon
case .deletedIcon:
representation = nil

View File

@ -25,6 +25,9 @@ swift_library(
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/CheckNode:CheckNode",
"//submodules/TextFormat:TextFormat",
"//submodules/Markdown:Markdown",
],
visibility = [
"//visibility:public",

View File

@ -6,8 +6,8 @@ import PassKit
import ShimmerEffect
enum BotCheckoutActionButtonState: Equatable {
case active(String)
case applePay
case active(text: String, isEnabled: Bool)
case applePay(isEnabled: Bool)
case placeholder
}
@ -17,6 +17,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
static var height: CGFloat = 52.0
private var activeFillColor: UIColor
private var inactiveFillColor: UIColor
private var foregroundColor: UIColor
private let activeBackgroundNode: ASImageNode
@ -28,17 +29,23 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
private var placeholderNode: ShimmerEffectNode?
init(activeFillColor: UIColor, foregroundColor: UIColor) {
private var activeImage: UIImage?
private var inactiveImage: UIImage?
init(activeFillColor: UIColor, inactiveFillColor: UIColor, foregroundColor: UIColor) {
self.activeFillColor = activeFillColor
self.inactiveFillColor = inactiveFillColor
self.foregroundColor = foregroundColor
let diameter: CGFloat = 20.0
self.activeImage = generateStretchableFilledCircleImage(diameter: diameter, color: activeFillColor)
self.inactiveImage = generateStretchableFilledCircleImage(diameter: diameter, color: inactiveFillColor)
self.activeBackgroundNode = ASImageNode()
self.activeBackgroundNode.displaysAsynchronously = false
self.activeBackgroundNode.displayWithoutProcessing = true
self.activeBackgroundNode.isLayerBacked = true
self.activeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: diameter, color: activeFillColor)
self.activeBackgroundNode.image = self.activeImage
self.labelNode = TextNode()
self.labelNode.displaysAsynchronously = false
@ -75,7 +82,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
var labelSize = self.labelNode.bounds.size
if let state = self.state {
switch state {
case let .active(title):
case let .active(title, isEnabled):
if let applePayButton = self.applePayButton {
self.applePayButton = nil
applePayButton.removeFromSuperview()
@ -85,12 +92,20 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
self.placeholderNode = nil
placeholderNode.removeFromSupernode()
}
let image = isEnabled ? self.activeImage : self.inactiveImage
if let image = image, let currentImage = self.activeBackgroundNode.image, currentImage !== image {
self.activeBackgroundNode.image = image
self.activeBackgroundNode.layer.animate(from: currentImage.cgImage! as AnyObject, to: image.cgImage! as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
} else {
self.activeBackgroundNode.image = image
}
let makeLayout = TextNode.asyncLayout(self.labelNode)
let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: size, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let _ = labelApply()
labelSize = labelLayout.size
case .applePay:
case let .applePay(isEnabled):
if self.applePayButton == nil {
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
let applePayButton: PKPaymentButton
@ -102,6 +117,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode {
applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside)
self.view.addSubview(applePayButton)
self.applePayButton = applePayButton
applePayButton.isEnabled = isEnabled
}
}

View File

@ -15,16 +15,19 @@ public final class BotCheckoutController: ViewController {
let form: BotPaymentForm
let validatedFormInfo: BotPaymentValidatedFormInfo?
let botPeer: EnginePeer?
private init(
form: BotPaymentForm,
validatedFormInfo: BotPaymentValidatedFormInfo?
validatedFormInfo: BotPaymentValidatedFormInfo?,
botPeer: EnginePeer?
) {
self.form = form
self.validatedFormInfo = validatedFormInfo
self.botPeer = botPeer
}
public static func fetch(context: AccountContext, messageId: EngineMessage.Id) -> Signal<InputData, FetchError> {
public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal<InputData, FetchError> {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let themeParams: [String: Any] = [
"bg_color": Int32(bitPattern: presentationData.theme.list.plainBackgroundColor.argb),
@ -34,33 +37,40 @@ public final class BotCheckoutController: ViewController {
"button_text_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.foregroundColor.argb)
]
return context.engine.payments.fetchBotPaymentForm(messageId: messageId, themeParams: themeParams)
return context.engine.payments.fetchBotPaymentForm(source: source, themeParams: themeParams)
|> mapError { _ -> FetchError in
return .generic
}
|> mapToSignal { paymentForm -> Signal<InputData, FetchError> in
if let current = paymentForm.savedInfo {
return context.engine.payments.validateBotPaymentForm(saveInfo: true, messageId: messageId, formInfo: current)
|> mapError { _ -> FetchError in
return .generic
}
|> map { result -> InputData in
return InputData(
form: paymentForm,
validatedFormInfo: result
)
}
|> `catch` { _ -> Signal<InputData, FetchError> in
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: paymentForm.paymentBotId))
|> castError(FetchError.self)
|> mapToSignal { botPeer -> Signal<InputData, FetchError> in
if let current = paymentForm.savedInfo {
return context.engine.payments.validateBotPaymentForm(saveInfo: true, source: source, formInfo: current)
|> mapError { _ -> FetchError in
return .generic
}
|> map { result -> InputData in
return InputData(
form: paymentForm,
validatedFormInfo: result,
botPeer: botPeer
)
}
|> `catch` { _ -> Signal<InputData, FetchError> in
return .single(InputData(
form: paymentForm,
validatedFormInfo: nil,
botPeer: botPeer
))
}
} else {
return .single(InputData(
form: paymentForm,
validatedFormInfo: nil
validatedFormInfo: nil,
botPeer: botPeer
))
}
} else {
return .single(InputData(
form: paymentForm,
validatedFormInfo: nil
))
}
}
}
@ -77,8 +87,11 @@ public final class BotCheckoutController: ViewController {
private let context: AccountContext
private let invoice: TelegramMediaInvoice
private let messageId: EngineMessage.Id
private let source: BotPaymentInvoiceSource
private let completed: (String, EngineMessage.Id?) -> Void
private let pending: () -> Void
private let cancelled: () -> Void
private let failed: () -> Void
private var presentationData: PresentationData
@ -86,12 +99,15 @@ public final class BotCheckoutController: ViewController {
private let inputData: Promise<BotCheckoutController.InputData?>
public init(context: AccountContext, invoice: TelegramMediaInvoice, messageId: EngineMessage.Id, inputData: Promise<BotCheckoutController.InputData?>, completed: @escaping (String, EngineMessage.Id?) -> Void) {
public init(context: AccountContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Promise<BotCheckoutController.InputData?>, completed: @escaping (String, EngineMessage.Id?) -> Void, pending: @escaping () -> Void = {}, cancelled: @escaping () -> Void = {}, failed: @escaping () -> Void = {}) {
self.context = context
self.invoice = invoice
self.messageId = messageId
self.source = source
self.inputData = inputData
self.completed = completed
self.pending = pending
self.cancelled = cancelled
self.failed = failed
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -113,7 +129,7 @@ public final class BotCheckoutController: ViewController {
}
override public func loadDisplayNode() {
let displayNode = BotCheckoutControllerNode(controller: self, navigationBar: self.navigationBar!, context: self.context, invoice: self.invoice, messageId: self.messageId, inputData: self.inputData, present: { [weak self] c, a in
let displayNode = BotCheckoutControllerNode(controller: self, navigationBar: self.navigationBar!, context: self.context, invoice: self.invoice, source: self.source, inputData: self.inputData, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}, dismissAnimated: { [weak self] in
self?.dismiss()
@ -122,6 +138,12 @@ public final class BotCheckoutController: ViewController {
displayNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
displayNode.pending = { [weak self] in
self?.pending()
}
displayNode.failed = { [weak self] in
self?.failed()
}
self.displayNode = displayNode
super.displayNodeDidLoad()
@ -146,6 +168,7 @@ public final class BotCheckoutController: ViewController {
}
@objc private func cancelPressed() {
self.cancelled()
self.dismiss()
}
}

Some files were not shown because too many files have changed in this diff Show More