Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2025-03-07 21:56:59 +04:00
commit 1483207919
52 changed files with 578 additions and 1274 deletions

View File

@ -15,7 +15,7 @@ import ItemListStickerPackItem
private struct ArchivedStickersNoticeEntry: Comparable, Identifiable {
let index: Int
let info: StickerPackCollectionInfo
let info: StickerPackCollectionInfo.Accessor
let topItem: StickerPackItem?
let count: String
@ -135,7 +135,7 @@ private final class ArchivedStickersNoticeAlertContentNode: AlertContentNode {
} else {
countTitle = presentationData.strings.StickerPack_StickerCount(pack.0.count)
}
entries.append(ArchivedStickersNoticeEntry(index: index, info: pack.0, topItem: pack.1, count: countTitle))
entries.append(ArchivedStickersNoticeEntry(index: index, info: StickerPackCollectionInfo.Accessor(pack.0), topItem: pack.1, count: countTitle))
index += 1
}

View File

@ -30,7 +30,7 @@ public final class TrendingPaneInteraction {
public final class TrendingPanePackEntry: Identifiable, Comparable {
public let index: Int
public let info: StickerPackCollectionInfo
public let info: StickerPackCollectionInfo.Accessor
public let theme: PresentationTheme
public let strings: PresentationStrings
public let topItems: [StickerPackItem]
@ -38,7 +38,7 @@ public final class TrendingPanePackEntry: Identifiable, Comparable {
public let unread: Bool
public let topSeparator: Bool
public init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool) {
public init(index: Int, info: StickerPackCollectionInfo.Accessor, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool) {
self.index = index
self.info = info
self.theme = theme
@ -88,9 +88,9 @@ public final class TrendingPanePackEntry: Identifiable, Comparable {
public func item(context: AccountContext, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem {
let info = self.info
return StickerPaneSearchGlobalItem(context: context, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: false, installed: self.installed, unread: self.unread, open: {
interaction.openPack(info)
interaction.openPack(info._parse())
}, install: {
interaction.installPack(info)
interaction.installPack(info._parse())
}, getItemIsPreviewed: { item in
return interaction.getItemIsPreviewed(item)
}, itemContext: interaction.itemContext)
@ -272,16 +272,17 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane {
if installed {
return .complete()
} else {
let parsedInfo = info._parse()
return preloadedStickerPackThumbnail(account: context.account, info: info, items: items)
|> filter { $0 }
|> ignoreValues
|> then(
context.engine.stickers.addStickerPackInteractively(info: info, items: items)
context.engine.stickers.addStickerPackInteractively(info: parsedInfo, items: items)
|> ignoreValues
)
|> mapToSignal { _ -> Signal<(StickerPackCollectionInfo, [StickerPackItem]), NoError> in
}
|> then(.single((info, items)))
|> then(.single((parsedInfo, items)))
}
case .fetching:
break

View File

@ -35,7 +35,7 @@ private final class FeaturedInteraction {
private final class FeaturedPackEntry: Identifiable, Comparable {
let index: Int
let info: StickerPackCollectionInfo
let info: StickerPackCollectionInfo.Accessor
let theme: PresentationTheme
let strings: PresentationStrings
let topItems: [StickerPackItem]
@ -44,7 +44,7 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
let topSeparator: Bool
let regularInsets: Bool
init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool, regularInsets: Bool = false) {
init(index: Int, info: StickerPackCollectionInfo.Accessor, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool, regularInsets: Bool = false) {
self.index = index
self.info = info
self.theme = theme
@ -98,9 +98,9 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
func item(context: AccountContext, interaction: FeaturedInteraction, isOther: Bool) -> GridItem {
let info = self.info
return StickerPaneSearchGlobalItem(context: context, theme: self.theme, strings: self.strings, listAppearance: true, fillsRow: false, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: self.regularInsets, installed: self.installed, unread: self.unread, open: {
interaction.openPack(info)
interaction.openPack(info._parse())
}, install: {
interaction.installPack(info, !self.installed)
interaction.installPack(info._parse(), !self.installed)
}, getItemIsPreviewed: { item in
return interaction.getItemIsPreviewed(item)
}, itemContext: interaction.itemContext, sectionTitle: isOther ? self.strings.FeaturedStickers_OtherSection : nil)
@ -1041,7 +1041,7 @@ private enum FeaturedSearchEntryId: Equatable, Hashable {
private enum FeaturedSearchEntry: Identifiable, Comparable {
case sticker(index: Int, code: String?, stickerItem: FoundStickerItem, theme: PresentationTheme)
case global(index: Int, info: StickerPackCollectionInfo, topItems: [StickerPackItem], installed: Bool, topSeparator: Bool)
case global(index: Int, info: StickerPackCollectionInfo.Accessor, topItems: [StickerPackItem], installed: Bool, topSeparator: Bool)
var stableId: FeaturedSearchEntryId {
switch self {
@ -1108,9 +1108,9 @@ private enum FeaturedSearchEntry: Identifiable, Comparable {
})
case let .global(_, info, topItems, installed, topSeparator):
return StickerPaneSearchGlobalItem(context: context, theme: theme, strings: strings, listAppearance: true, fillsRow: true, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
interaction.open(info)
interaction.open(info._parse())
}, install: {
interaction.install(info, topItems, !installed)
interaction.install(info._parse(), topItems, !installed)
}, getItemIsPreviewed: { item in
return interaction.getItemIsPreviewed(item)
}, itemContext: itemContext)
@ -1436,7 +1436,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
}
}
}
entries.append(.global(index: index, info: info, topItems: topItems, installed: installed, topSeparator: !isFirstGlobal))
entries.append(.global(index: index, info: StickerPackCollectionInfo.Accessor(info), topItems: topItems, installed: installed, topSeparator: !isFirstGlobal))
isFirstGlobal = false
index += 1
}

View File

@ -84,7 +84,7 @@ public final class StickerPaneSearchGlobalItem: GridItem {
public let strings: PresentationStrings
public let listAppearance: Bool
public let fillsRow: Bool
public let info: StickerPackCollectionInfo
public let info: StickerPackCollectionInfo.Accessor
public let topItems: [StickerPackItem]
public let topSeparator: Bool
public let regularInsets: Bool
@ -111,7 +111,7 @@ public final class StickerPaneSearchGlobalItem: GridItem {
return (128.0 + additionalHeight, self.fillsRow)
}
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, fillsRow: Bool = true, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) {
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, fillsRow: Bool = true, info: StickerPackCollectionInfo.Accessor, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) {
self.context = context
self.theme = theme
self.strings = strings

View File

@ -40,7 +40,7 @@ public enum ItemListStickerPackItemControl: Equatable {
public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let context: AccountContext
let packInfo: StickerPackCollectionInfo
let packInfo: StickerPackCollectionInfo.Accessor
let itemCount: String
let topItem: StickerPackItem?
let unread: Bool
@ -56,7 +56,7 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let removePack: () -> Void
let toggleSelected: () -> Void
public init(presentationData: ItemListPresentationData, context: AccountContext, packInfo: StickerPackCollectionInfo, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) {
public init(presentationData: ItemListPresentationData, context: AccountContext, packInfo: StickerPackCollectionInfo.Accessor, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) {
self.presentationData = presentationData
self.context = context
self.packInfo = packInfo
@ -487,7 +487,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
var thumbnailItem: StickerPackThumbnailItem?
var resourceReference: MediaResourceReference?
if let thumbnail = item.packInfo.thumbnail {
if item.packInfo.hasThumbnail, let thumbnail = item.packInfo._parse().thumbnail {
if thumbnail.typeHint != .generic {
thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, thumbnail.typeHint == .video, item.packInfo.flags.contains(.isCustomTemplateEmoji))
} else {
@ -845,7 +845,12 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
var imageSize = PixelDimensions(width: 512, height: 512)
var immediateThumbnailData: Data?
if let data = item.packInfo.immediateThumbnailData {
if item.packInfo.thumbnail?.typeHint == .video || item.topItem?.file.isVideoSticker == true {
var isVideoTypeHint = false
if item.packInfo.hasThumbnail, let thumbnail = item.packInfo._parse().thumbnail {
isVideoTypeHint = thumbnail.typeHint == .video
}
if isVideoTypeHint || item.topItem?.file.isVideoSticker == true {
imageSize = PixelDimensions(width: 100, height: 100)
}
immediateThumbnailData = data

View File

@ -1,110 +0,0 @@
// AFHTTPOperation.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <MtProtoKit/AFURLConnectionOperation.h>
/**
`AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request.
*/
@interface AFHTTPRequestOperation : AFURLConnectionOperation
///----------------------------------------------
/// @name Getting HTTP URL Connection Information
///----------------------------------------------
/**
The last HTTP response received by the operation's connection.
*/
@property (readonly, nonatomic, strong) NSHTTPURLResponse *response;
///----------------------------------------------------------
/// @name Managing And Checking For Acceptable HTTP Responses
///----------------------------------------------------------
/**
Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
By default, this is the range 200 to 299, inclusive.
*/
@property (nonatomic, strong) NSIndexSet *acceptableStatusCodes;
/**
A Boolean value that corresponds to whether the status code of the response is within the specified set of acceptable status codes. Returns `YES` if `acceptableStatusCodes` is `nil`.
*/
@property (readonly) BOOL hasAcceptableStatusCode;
/**
Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
By default, this is `nil`.
*/
@property (nonatomic, strong) NSSet *acceptableContentTypes;
/**
A Boolean value that corresponds to whether the MIME type of the response is among the specified set of acceptable content types. Returns `YES` if `acceptableContentTypes` is `nil`.
*/
@property (readonly) BOOL hasAcceptableContentType;
/**
The callback dispatch queue on success. If `NULL` (default), the main queue is used.
*/
@property (nonatomic, strong) dispatch_queue_t successCallbackQueue;
/**
The callback dispatch queue on failure. If `NULL` (default), the main queue is used.
*/
@property (nonatomic, strong) dispatch_queue_t failureCallbackQueue;
/**
The dispatch group on which to call the completion/failure block
*/
@property (nonatomic, strong) dispatch_group_t dispatchGroup;
/**
A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`.
@param urlRequest The request that is determined to be supported or not supported for this class.
*/
+ (BOOL)canProcessRequest:(NSURLRequest *)urlRequest;
///-----------------------------------------------------------
/// @name Setting Completion Block Success / Failure Callbacks
///-----------------------------------------------------------
/**
Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed.
@param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request.
@param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occured during the request.
@discussion This method should be overridden in subclasses in order to specify the response object passed into the success block.
*/
- (void)setCompletionBlockWithSuccess:(void (^)(NSOperation *operation, id responseObject))success
failure:(void (^)(NSOperation *operation, NSError *error))failure;
@end

View File

@ -1,208 +0,0 @@
// AFURLConnectionOperation.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
/**
Indicates an error occured in AFNetworking.
@discussion Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain.
*/
extern NSString * const AFNetworkingErrorDomain;
/**
Posted when an operation begins executing.
*/
extern NSString * const AFNetworkingOperationDidStartNotification;
/**
Posted when an operation finishes.
*/
extern NSString * const AFNetworkingOperationDidFinishNotification;
/**
`AFURLConnectionOperation` is an `NSOperation` that implements NSURLConnection delegate methods.
## Subclassing Notes
This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors.
If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes.
## NSURLConnection Delegate Methods
`AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods:
- `connection:didReceiveResponse:`
- `connection:didReceiveData:`
- `connectionDidFinishLoading:`
- `connection:didFailWithError:`
- `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:`
- `connection:willCacheResponse:`
- `connection:canAuthenticateAgainstProtectionSpace:`
- `connection:didReceiveAuthenticationChallenge:`
If any of these methods are overriden in a subclass, they _must_ call the `super` implementation first.
## Class Constructors
Class constructors, or methods that return an unowned (zero retain count) instance, are the preferred way for subclasses to encapsulate any particular logic for handling the setup or parsing of response data. For instance, `AFJSONRequestOperation` provides `JSONRequestOperationWithRequest:success:failure:`, which takes block arguments, whose parameter on for a successful request is the JSON object initialized from the `response data`.
## Callbacks and Completion Blocks
The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (e.g. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this.
@warning Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as "The Deallocation Problem" (See http://developer.apple.com/library/ios/technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11)
@warning Attempting to load a `file://` URL in iOS 4 may result in an `NSInvalidArgumentException`, caused by the connection returning `NSURLResponse` rather than `NSHTTPURLResponse`, which is the behavior as of iOS 5.
*/
@interface AFURLConnectionOperation : NSOperation
///-------------------------------
/// @name Accessing Run Loop Modes
///-------------------------------
/**
The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`.
*/
@property (nonatomic, strong) NSSet *runLoopModes;
///-----------------------------------------
/// @name Getting URL Connection Information
///-----------------------------------------
/**
The request used by the operation's connection.
*/
@property (readonly, nonatomic, strong) NSURLRequest *request;
/**
The last response received by the operation's connection.
*/
@property (readonly, nonatomic, strong) NSURLResponse *response;
/**
The error, if any, that occured in the lifecycle of the request.
*/
@property (readonly, nonatomic, strong) NSError *error;
///----------------------------
/// @name Getting Response Data
///----------------------------
/**
The data received during the request.
*/
@property (readonly, nonatomic, strong) NSData *responseData;
/**
The string representation of the response data.
@discussion This method uses the string encoding of the response, or if UTF-8 if not specified, to construct a string from the response data.
*/
@property (readonly, nonatomic, copy) NSString *responseString;
///------------------------
/// @name Accessing Streams
///------------------------
/**
The input stream used to read data to be sent during the request.
@discussion This property acts as a proxy to the `HTTPBodyStream` property of `request`.
*/
@property (nonatomic, strong) NSInputStream *inputStream;
/**
The output stream that is used to write data received until the request is finished.
@discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`.
*/
@property (nonatomic, strong) NSOutputStream *outputStream;
///------------------------------------------------------
/// @name Initializing an AFURLConnectionOperation Object
///------------------------------------------------------
/**
Initializes and returns a newly allocated operation object with a url connection configured with the specified url request.
@param urlRequest The request object to be used by the operation connection.
@discussion This is the designated initializer.
*/
- (id)initWithRequest:(NSURLRequest *)urlRequest;
///---------------------------------
/// @name Setting Progress Callbacks
///---------------------------------
/**
Sets a callback to be called when an undetermined number of bytes have been uploaded to the server.
@param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times.
@see setDownloadProgressBlock
*/
- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block;
/**
Sets a callback to be called when an undetermined number of bytes have been downloaded from the server.
@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times.
@see setUploadProgressBlock
*/
- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block;
///-------------------------------------------------
/// @name Setting NSURLConnection Delegate Callbacks
///-------------------------------------------------
/**
Sets a block to be executed to determine whether the connection should be able to respond to a protection space's form of authentication, as handled by the `NSURLConnectionDelegate` method `connection:canAuthenticateAgainstProtectionSpace:`.
@param block A block object to be executed to determine whether the connection should be able to respond to a protection space's form of authentication. The block has a `BOOL` return type and takes two arguments: the URL connection object, and the protection space to authenticate against.
@discussion If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is defined, `connection:canAuthenticateAgainstProtectionSpace:` will accept invalid SSL certificates, returning `YES` if the protection space authentication method is `NSURLAuthenticationMethodServerTrust`.
*/
- (void)setAuthenticationAgainstProtectionSpaceBlock:(BOOL (^)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace))block;
/**
Sets a block to be executed when the connection must authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:didReceiveAuthenticationChallenge:`.
@param block A block object to be executed when the connection must authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated.
@discussion If `_AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_` is defined, `connection:didReceiveAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates.
*/
- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
/**
Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`.
@param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request.
*/
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;
@end

View File

@ -1,8 +1,6 @@
#import <Foundation/Foundation.h>
#import <MtProtoKit/MTDatacenterAuthInfo.h>
#import <MtProtoKit/AFHTTPRequestOperation.h>
#import <MtProtoKit/AFURLConnectionOperation.h>
#import <MtProtoKit/MTApiEnvironment.h>
#import <MtProtoKit/MTAtomic.h>
#import <MtProtoKit/MTBackupAddressSignals.h>

View File

@ -1,212 +0,0 @@
// AFHTTPOperation.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <MtProtoKit/AFHTTPRequestOperation.h>
static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
NSMutableString *string = [NSMutableString string];
NSRange range = NSMakeRange([indexSet firstIndex], 1);
while (range.location != NSNotFound) {
NSUInteger nextIndex = [indexSet indexGreaterThanIndex:range.location];
while (nextIndex == range.location + range.length) {
range.length++;
nextIndex = [indexSet indexGreaterThanIndex:nextIndex];
}
if (string.length) {
[string appendString:@","];
}
if (range.length == 1) {
[string appendFormat:@"%lu", (unsigned long)range.location];
} else {
NSUInteger firstIndex = range.location;
NSUInteger lastIndex = firstIndex + range.length - 1;
[string appendFormat:@"%lu-%lu", (unsigned long)firstIndex, (unsigned long)lastIndex];
}
range.location = nextIndex;
range.length = 1;
}
return string;
}
#pragma mark -
@interface AFHTTPRequestOperation ()
@property (readwrite, nonatomic, strong) NSError *HTTPError;
@property (nonatomic) dispatch_once_t onceToken;
@property (atomic) dispatch_semaphore_t dispatchSemaphore;
@end
@implementation AFHTTPRequestOperation
@synthesize acceptableStatusCodes = _acceptableStatusCodes;
@synthesize acceptableContentTypes = _acceptableContentTypes;
@synthesize HTTPError = _HTTPError;
@synthesize successCallbackQueue = _successCallbackQueue;
@synthesize failureCallbackQueue = _failureCallbackQueue;
@synthesize dispatchGroup = _dispatchGroup;
@synthesize onceToken = _onceToken;
@synthesize dispatchSemaphore = _dispatchSemaphore;
- (id)initWithRequest:(NSURLRequest *)request {
self = [super initWithRequest:request];
if (!self) {
return nil;
}
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.dispatchSemaphore = dispatch_semaphore_create(1);
self.completionBlock = NULL;
return self;
}
- (NSHTTPURLResponse *)response {
return (NSHTTPURLResponse *)[super response];
}
- (NSError *)error {
if (self.response && !self.HTTPError) {
if (![self hasAcceptableStatusCode]) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %ld", nil), AFStringFromIndexSet(self.acceptableStatusCodes), (long)[self.response statusCode]] forKey:NSLocalizedDescriptionKey];
[userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo];
} else if ([self.responseData length] > 0 && ![self hasAcceptableContentType]) { // Don't invalidate content type if there is no content
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), self.acceptableContentTypes, [self.response MIMEType]] forKey:NSLocalizedDescriptionKey];
[userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
}
if (self.HTTPError) {
return self.HTTPError;
} else {
return [super error];
}
}
- (BOOL)hasAcceptableStatusCode {
return !self.acceptableStatusCodes || [self.acceptableStatusCodes containsIndex:(NSUInteger)[self.response statusCode]];
}
- (BOOL)hasAcceptableContentType {
return !self.acceptableContentTypes || [self.acceptableContentTypes containsObject:[self.response MIMEType]];
}
- (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue {
if (successCallbackQueue != _successCallbackQueue) {
_successCallbackQueue = successCallbackQueue;
}
}
- (void)setFailureCallbackQueue:(dispatch_queue_t)failureCallbackQueue {
if (failureCallbackQueue != _failureCallbackQueue) {
_failureCallbackQueue = failureCallbackQueue;
}
}
- (void)setDispatchGroup:(dispatch_group_t)dispatchGroup {
dispatch_semaphore_wait(self.dispatchSemaphore, DISPATCH_TIME_FOREVER);
if (dispatchGroup != _dispatchGroup) {
if (_dispatchGroup) {
dispatch_group_leave(_dispatchGroup);
_dispatchGroup = NULL;
}
if (dispatchGroup) {
_dispatchGroup = dispatchGroup;
dispatch_group_enter(_dispatchGroup);
}
}
dispatch_semaphore_signal(self.dispatchSemaphore);
}
- (dispatch_group_t)dispatchGroup {
dispatch_semaphore_wait(self.dispatchSemaphore, DISPATCH_TIME_FOREVER);
if(_dispatchGroup == NULL) {
_dispatchGroup = dispatch_group_create();
dispatch_group_enter(_dispatchGroup);
}
dispatch_semaphore_signal(self.dispatchSemaphore);
return _dispatchGroup;
}
- (void)setCompletionBlock:(void (^)(void))block {
__block id _blockSelf = self;
dispatch_once_t *blockOnceToken = &_onceToken;
[super setCompletionBlock:^{
if(block) {
block();
}
// Dispatch once is used to ensure that setting the block with this block will not cause multiple calls to 'dispatch_group_leave'
dispatch_once(blockOnceToken, ^{
dispatch_group_leave([_blockSelf dispatchGroup]);
});
}];
}
- (void)setCompletionBlockWithSuccess:(void (^)(NSOperation *operation, id responseObject))success
failure:(void (^)(NSOperation *operation, NSError *error))failure
{
__weak typeof(self) weakSelf = self;
self.completionBlock = ^ {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if ([strongSelf isCancelled]) {
return;
}
if (strongSelf.error) {
if (failure) {
dispatch_group_async(strongSelf.dispatchGroup, strongSelf.failureCallbackQueue ? strongSelf.failureCallbackQueue : dispatch_get_main_queue(), ^{
failure(strongSelf, strongSelf.error);
});
}
} else {
if (success) {
dispatch_group_async(strongSelf.dispatchGroup, strongSelf.successCallbackQueue ? strongSelf.successCallbackQueue : dispatch_get_main_queue(), ^{
success(strongSelf, strongSelf.responseData);
});
}
}
};
}
#pragma mark - AFHTTPClientOperation
+ (BOOL)canProcessRequest:(NSURLRequest *)__unused request {
return YES;
}
@end

View File

@ -1,528 +0,0 @@
// AFURLConnectionOperation.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <MtProtoKit/AFURLConnectionOperation.h>
//#import "AFImageRequestOperation.h"
//#define AF_DEBUGLOCK
typedef enum {
AFHTTPOperationReadyState = 1,
AFHTTPOperationExecutingState = 2,
AFHTTPOperationFinishedState = 3,
} _AFOperationState;
typedef unsigned short AFOperationState;
static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024;
static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8;
static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock";
NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error";
NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start";
NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish";
typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, NSInteger totalBytes, NSInteger totalBytesExpected);
typedef BOOL (^AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace);
typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge);
typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse);
static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
switch (state) {
case AFHTTPOperationReadyState:
return @"isReady";
case AFHTTPOperationExecutingState:
return @"isExecuting";
case AFHTTPOperationFinishedState:
return @"isFinished";
default:
return @"state";
}
}
static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) {
switch (fromState) {
case AFHTTPOperationReadyState:
switch (toState) {
case AFHTTPOperationExecutingState:
return YES;
case AFHTTPOperationFinishedState:
return isCancelled;
default:
return NO;
}
case AFHTTPOperationExecutingState:
switch (toState) {
case AFHTTPOperationFinishedState:
return YES;
default:
return NO;
}
case AFHTTPOperationFinishedState:
return NO;
default:
return YES;
}
}
@interface AFURLConnectionOperation ()
@property (readwrite, nonatomic, assign) AFOperationState state;
@property (readwrite, nonatomic, assign, getter = isCancelled) BOOL cancelled;
@property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
@property (readwrite, nonatomic, strong) NSURLConnection *connection;
@property (readwrite, nonatomic, strong) NSURLRequest *request;
@property (readwrite, nonatomic, strong) NSURLResponse *response;
@property (readwrite, nonatomic, strong) NSError *error;
@property (readwrite, nonatomic, strong) NSData *responseData;
@property (readwrite, nonatomic, copy) NSString *responseString;
@property (readwrite, nonatomic, assign) NSInteger totalBytesRead;
@property (readwrite, nonatomic, strong) NSMutableData *dataAccumulator;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock authenticationAgainstProtectionSpace;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse;
- (void)operationDidStart;
- (void)finish;
@end
@implementation AFURLConnectionOperation
@synthesize state = _state;
@synthesize cancelled = _cancelled;
@synthesize connection = _connection;
@synthesize runLoopModes = _runLoopModes;
@synthesize request = _request;
@synthesize response = _response;
@synthesize error = _error;
@synthesize responseData = _responseData;
@synthesize responseString = _responseString;
@synthesize totalBytesRead = _totalBytesRead;
@synthesize dataAccumulator = _dataAccumulator;
@dynamic inputStream;
@synthesize outputStream = _outputStream;
@synthesize uploadProgress = _uploadProgress;
@synthesize downloadProgress = _downloadProgress;
@synthesize authenticationAgainstProtectionSpace = _authenticationAgainstProtectionSpace;
@synthesize authenticationChallenge = _authenticationChallenge;
@synthesize cacheResponse = _cacheResponse;
@synthesize lock = _lock;
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
do {
@autoreleasepool {
[NSThread currentThread].threadPriority = 0.2;
NSException *caughtException = nil;
@try {
@autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
}
@catch(NSException *e) { caughtException = e; }
if(caughtException) {
NSLog(NSLocalizedString(@"Unhandled exception on %@ networking thread: %@, userInfo: %@", nil), NSStringFromClass([self class]), caughtException, [caughtException userInfo]);
}
}
} while (YES);
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
- (id)initWithRequest:(NSURLRequest *)urlRequest {
self = [super init];
if (!self) {
return nil;
}
self.lock = [[NSRecursiveLock alloc] init];
self.lock.name = kAFNetworkingLockName;
self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
self.request = urlRequest;
self.state = AFHTTPOperationReadyState;
return self;
}
- (void)dealloc {
#ifdef AF_DEBUGLOCK
if ([NSThread currentThread] != [AFURLConnectionOperation networkRequestThread])
NSLog(@"0x%x: dealloc thread %@", (int)self, [NSThread currentThread]);
#endif
if (_outputStream) {
[_outputStream close];
}
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response];
}
- (void)setCompletionBlock:(void (^)(void))block {
#ifdef AF_DEBUGLOCK
if ([NSThread currentThread] != [AFURLConnectionOperation networkRequestThread])
NSLog(@"0x%x: setCompletionBlock thread %@", (int)self, [NSThread currentThread]);
#endif
[self.lock lock];
if (!block) {
[super setCompletionBlock:nil];
} else {
__block id _blockSelf = self;
[super setCompletionBlock:^ {
//TGLog(@"===== Completed %@", [_blockSelf request].URL);
block();
[_blockSelf setCompletionBlock:nil];
}];
}
[self.lock unlock];
}
- (NSInputStream *)inputStream {
return self.request.HTTPBodyStream;
}
- (void)setInputStream:(NSInputStream *)inputStream {
NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
mutableRequest.HTTPBodyStream = inputStream;
self.request = mutableRequest;
}
- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block {
self.uploadProgress = block;
}
- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block {
self.downloadProgress = block;
}
- (void)setAuthenticationAgainstProtectionSpaceBlock:(BOOL (^)(NSURLConnection *, NSURLProtectionSpace *))block {
self.authenticationAgainstProtectionSpace = block;
}
- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block {
self.authenticationChallenge = block;
}
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block {
self.cacheResponse = block;
}
- (void)setState:(AFOperationState)state {
#ifdef AF_DEBUGLOCK
if ([NSThread currentThread] != [AFURLConnectionOperation networkRequestThread])
NSLog(@"0x%x: setState thread %@", (int)self, [NSThread currentThread]);
#endif
[self.lock lock];
if (AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
NSString *newStateKey = AFKeyPathFromOperationState(state);
[self willChangeValueForKey:newStateKey];
[self willChangeValueForKey:oldStateKey];
_state = state;
[self didChangeValueForKey:oldStateKey];
[self didChangeValueForKey:newStateKey];
switch (state) {
case AFHTTPOperationExecutingState:
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
break;
case AFHTTPOperationFinishedState:
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
break;
default:
break;
}
}
[self.lock unlock];
}
- (NSString *)responseString {
#ifdef AF_DEBUGLOCK
if ([NSThread currentThread] != [AFURLConnectionOperation networkRequestThread])
NSLog(@"0x%x: responseString thread %@", (int)self, [NSThread currentThread]);
#endif
[self.lock lock];
if (!_responseString && self.response && self.responseData) {
NSStringEncoding textEncoding = NSUTF8StringEncoding;
if (self.response.textEncodingName) {
textEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)self.response.textEncodingName));
}
self.responseString = [[NSString alloc] initWithData:self.responseData encoding:textEncoding];
}
[self.lock unlock];
return _responseString;
}
#pragma mark - NSOperation
- (BOOL)isReady {
return self.state == AFHTTPOperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
return self.state == AFHTTPOperationExecutingState;
}
- (BOOL)isFinished {
return self.state == AFHTTPOperationFinishedState;
}
- (BOOL)isConcurrent {
return YES;
}
- (void)start {
[self.lock lock];
if ([self isReady]) {
self.state = AFHTTPOperationExecutingState;
//TGLog(@"----- Start url %@", self.request.URL);
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
- (void)operationDidStart {
#ifdef AF_DEBUGLOCK
if ([NSThread currentThread] != [AFURLConnectionOperation networkRequestThread])
NSLog(@"0x%x: operationDidStart thread %@", (int)self, [NSThread currentThread]);
#endif
[self.lock lock];
if ([self isCancelled]) {
[self finish];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
#pragma clang diagnostic pop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for (NSString *runLoopMode in self.runLoopModes) {
[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
}
[self.connection start];
}
[self.lock unlock];
}
- (void)finish {
self.state = AFHTTPOperationFinishedState;
}
- (void)cancel {
#ifdef AF_DEBUGLOCK
if ([NSThread currentThread] != [AFURLConnectionOperation networkRequestThread])
NSLog(@"0x%x: cancel thread %@", (int)self, [NSThread currentThread]);
#endif
[self.lock lock];
if (![self isFinished] && ![self isCancelled]) {
[self willChangeValueForKey:@"isCancelled"];
_cancelled = YES;
[super cancel];
[self didChangeValueForKey:@"isCancelled"];
// Cancel the connection on the thread it runs on to prevent race conditions
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
- (void)cancelConnection {
if (self.connection) {
[self.connection cancel];
// Manually send this delegate message since `[self.connection cancel]` causes the connection to never send another message to its delegate
NSDictionary *userInfo = nil;
if ([self.request URL]) {
userInfo = [NSDictionary dictionaryWithObject:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
}
[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]];
}
}
#pragma mark - NSURLConnectionDelegate
- (BOOL)connection:(NSURLConnection *)connection
canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_
if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
return YES;
}
#endif
if (self.authenticationAgainstProtectionSpace) {
return self.authenticationAgainstProtectionSpace(connection, protectionSpace);
} else if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
return NO;
} else {
return YES;
}
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
return;
}
#endif
if (self.authenticationChallenge) {
self.authenticationChallenge(connection, challenge);
} else {
if ([challenge previousFailureCount] == 0) {
NSURLCredential *credential = nil;
NSString *username = CFBridgingRelease(CFURLCopyUserName((CFURLRef)[self.request URL]));
NSString *password = CFBridgingRelease(CFURLCopyPassword((CFURLRef)[self.request URL]));
if (username && password) {
credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceNone];
} else if (username) {
credential = [[[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:[challenge protectionSpace]] objectForKey:username];
} else {
credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:[challenge protectionSpace]];
}
if (credential) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
}
- (void)connection:(NSURLConnection *)__unused connection
didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
if (self.uploadProgress) {
self.uploadProgress(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}
- (void)connection:(NSURLConnection *)__unused connection
didReceiveResponse:(NSURLResponse *)response
{
self.response = (NSHTTPURLResponse *)response;
if (self.outputStream) {
[self.outputStream open];
} else {
NSUInteger maxCapacity = MAX((NSUInteger)llabs(response.expectedContentLength), kAFHTTPMinimumInitialDataCapacity);
NSUInteger capacity = MIN(maxCapacity, kAFHTTPMaximumInitialDataCapacity);
self.dataAccumulator = [NSMutableData dataWithCapacity:capacity];
}
}
- (void)connection:(NSURLConnection *)__unused connection
didReceiveData:(NSData *)data
{
self.totalBytesRead += [data length];
if (self.outputStream) {
if ([self.outputStream hasSpaceAvailable]) {
const uint8_t *dataBuffer = (uint8_t *) [data bytes];
[self.outputStream write:&dataBuffer[0] maxLength:[data length]];
}
} else {
[self.dataAccumulator appendData:data];
}
if (self.downloadProgress) {
self.downloadProgress([data length], self.totalBytesRead, (NSInteger)self.response.expectedContentLength);
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection {
if (self.outputStream) {
[self.outputStream close];
} else {
self.responseData = [NSData dataWithData:self.dataAccumulator];
_dataAccumulator = nil;
}
[self finish];
self.connection = nil;
}
- (void)connection:(NSURLConnection *)__unused connection
didFailWithError:(NSError *)error
{
self.error = error;
if (self.outputStream) {
[self.outputStream close];
} else {
_dataAccumulator = nil;
}
[self finish];
self.connection = nil;
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
if (self.cacheResponse) {
return self.cacheResponse(connection, cachedResponse);
} else {
if ([self isCancelled]) {
return nil;
}
return cachedResponse;
}
}
@end

View File

@ -1261,16 +1261,25 @@ static void copyKeychainDictionaryKey(NSString * _Nonnull group, NSString * _Non
}
}] take:1];
_transportSchemeDisposableByDatacenterId[@(datacenterId)] = [[filteredSignal onDispose:^
MTMetaDisposable *disposable = [[MTMetaDisposable alloc] init];
_transportSchemeDisposableByDatacenterId[@(datacenterId)] = disposable;
__weak MTMetaDisposable *weakDisposable = disposable;
[disposable setDisposable:[[filteredSignal onDispose:^
{
__strong MTContext *strongSelf = weakSelf;
if (strongSelf != nil)
{
[[MTContext contextQueue] dispatchOnQueue:^
{
id<MTDisposable> disposable = strongSelf->_transportSchemeDisposableByDatacenterId[@(datacenterId)];
[strongSelf->_transportSchemeDisposableByDatacenterId removeObjectForKey:@(datacenterId)];
[disposable dispose];
__strong MTMetaDisposable *strongDisposable = weakDisposable;
if (strongDisposable) {
id<MTDisposable> disposable = strongSelf->_transportSchemeDisposableByDatacenterId[@(datacenterId)];
if (disposable == strongDisposable) {
[strongSelf->_transportSchemeDisposableByDatacenterId removeObjectForKey:@(datacenterId)];
[disposable dispose];
}
}
}];
}
}] startWithNextStrict:^(MTTransportScheme *next)
@ -1289,7 +1298,7 @@ static void copyKeychainDictionaryKey(NSString * _Nonnull group, NSString * _Non
} completed:^
{
} file:__FILE_NAME__ line:__LINE__];
} file:__FILE_NAME__ line:__LINE__]];
}
}];
}

View File

@ -1,6 +1,5 @@
#import <MtProtoKit/MTHttpRequestOperation.h>
#import <MtProtoKit/AFHTTPRequestOperation.h>
#import <MtProtoKit/MTDisposable.h>
#import <MtProtoKit/MTSignal.h>
@ -29,30 +28,30 @@
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) {
[request setValue:value forHTTPHeaderField:key];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setSuccessCallbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
[operation setFailureCallbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
[operation setCompletionBlockWithSuccess:^(__unused NSOperation *operation, __unused id responseObject)
{
AFHTTPRequestOperation *concreteOperation = (AFHTTPRequestOperation *)operation;
MTHttpResponse *result = [[MTHttpResponse alloc] initWithHeaders:[concreteOperation response].allHeaderFields data:[concreteOperation responseData]];
[subscriber putNext:result];
[subscriber putCompletion];
} failure:^(__unused NSOperation *operation, __unused NSError *error)
{
[subscriber putError:error];
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse * response, NSError *error) {
if (error) {
[subscriber putError:error];
} else {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
MTHttpResponse *result = [[MTHttpResponse alloc] initWithHeaders:httpResponse.allHeaderFields data:data];
[subscriber putNext:result];
[subscriber putCompletion];
} else {
[subscriber putError:nil];
}
}
}];
[dataTask resume];
[operation start];
__weak AFHTTPRequestOperation *weakOperation = operation;
__weak NSURLSessionDataTask *weakDataTask = dataTask;
return [[MTBlockDisposable alloc] initWithBlock:^
{
__strong AFHTTPRequestOperation *strongOperation = weakOperation;
[strongOperation cancel];
__strong NSURLSessionDataTask *strongDataTask = weakDataTask;
if (strongDataTask) {
[strongDataTask cancel];
}
}];
}];
}

View File

@ -6,6 +6,8 @@
#import <MtProtoKit/MTAtomic.h>
#import <MtProtoKit/MTBag.h>
#import <os/lock.h>
@interface MTSubscriberDisposable : NSObject <MTDisposable>
{
__weak MTSubscriber *_subscriber;
@ -124,7 +126,7 @@
@interface MTSignalQueueState : NSObject <MTDisposable>
{
pthread_mutex_t _lock;
os_unfair_lock _lock;
bool _executingSignal;
bool _terminated;
@ -145,8 +147,6 @@
self = [super init];
if (self != nil)
{
pthread_mutex_init(&_lock, nil);
_subscriber = subscriber;
_currentDisposable = [[MTMetaDisposable alloc] init];
_queuedSignals = queueMode ? [[NSMutableArray alloc] init] : nil;
@ -155,10 +155,6 @@
return self;
}
- (void)dealloc {
pthread_mutex_destroy(&_lock);
}
- (void)beginWithDisposable:(id<MTDisposable>)disposable
{
_disposable = disposable;
@ -167,9 +163,8 @@
- (void)enqueueSignal:(MTSignal *)signal
{
bool startSignal = false;
pthread_mutex_lock(&_lock);
if (_queueMode && _executingSignal)
{
os_unfair_lock_lock(&_lock);
if (_queueMode && _executingSignal) {
[_queuedSignals addObject:signal];
}
else
@ -177,17 +172,29 @@
_executingSignal = true;
startSignal = true;
}
pthread_mutex_unlock(&_lock);
os_unfair_lock_unlock(&_lock);
if (startSignal)
{
__weak MTSignalQueueState *weakSelf = self;
id<MTDisposable> disposable = [signal startWithNext:^(id next)
{
[_subscriber putNext:next];
__strong MTSignalQueueState *strongSelf = weakSelf;
if (strongSelf) {
#if DEBUG
assert(strongSelf->_subscriber != nil);
#endif
[strongSelf->_subscriber putNext:next];
}
} error:^(id error)
{
[_subscriber putError:error];
__strong MTSignalQueueState *strongSelf = weakSelf;
if (strongSelf) {
#if DEBUG
assert(strongSelf->_subscriber != nil);
#endif
[strongSelf->_subscriber putError:error];
}
} completed:^
{
__strong MTSignalQueueState *strongSelf = weakSelf;
@ -205,7 +212,7 @@
MTSignal *nextSignal = nil;
bool terminated = false;
pthread_mutex_lock(&_lock);
os_unfair_lock_lock(&_lock);
_executingSignal = false;
if (_queueMode)
@ -221,26 +228,38 @@
}
else
terminated = _terminated;
pthread_mutex_unlock(&_lock);
os_unfair_lock_unlock(&_lock);
if (terminated)
if (terminated) {
[_subscriber putCompletion];
else if (nextSignal != nil)
{
_subscriber = nil;
} else if (nextSignal != nil) {
__weak MTSignalQueueState *weakSelf = self;
id<MTDisposable> disposable = [nextSignal startWithNext:^(id next)
{
[_subscriber putNext:next];
__strong MTSignalQueueState *strongSelf = weakSelf;
if (strongSelf) {
#if DEBUG
assert(strongSelf->_subscriber != nil);
#endif
[strongSelf->_subscriber putNext:next];
}
} error:^(id error)
{
[_subscriber putError:error];
__strong MTSignalQueueState *strongSelf = weakSelf;
if (strongSelf) {
#if DEBUG
assert(strongSelf->_subscriber != nil);
#endif
[strongSelf->_subscriber putError:error];
}
} completed:^
{
__strong MTSignalQueueState *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf headCompleted];
}
}];
}];
[_currentDisposable setDisposable:disposable];
}
@ -249,19 +268,21 @@
- (void)beginCompletion
{
bool executingSignal = false;
pthread_mutex_lock(&_lock);
os_unfair_lock_lock(&_lock);
executingSignal = _executingSignal;
_terminated = true;
pthread_mutex_unlock(&_lock);
os_unfair_lock_unlock(&_lock);
if (!executingSignal)
if (!executingSignal) {
[_subscriber putCompletion];
}
}
- (void)dispose
{
[_currentDisposable dispose];
[_disposable dispose];
_subscriber = nil;
}
@end
@ -426,7 +447,7 @@
MTMetaDisposable *startDisposable = [[MTMetaDisposable alloc] init];
MTMetaDisposable *timerDisposable = [[MTMetaDisposable alloc] init];
MTTimer *timer = [[MTTimer alloc] initWithTimeout:seconds repeat:false completion:^{
MTTimer *timer = [[MTTimer alloc] initWithTimeout:seconds repeat:false completion:^() {
[startDisposable setDisposable:[self startWithNext:^(id next)
{
[subscriber putNext:next];
@ -499,15 +520,16 @@
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable> (MTSubscriber *subscriber)
{
MTDisposableSet *disposable = [[MTDisposableSet alloc] init];
MTMetaDisposable *mainDisposable = [[MTMetaDisposable alloc] init];
MTMetaDisposable *alternativeDisposable = [[MTMetaDisposable alloc] init];
[disposable add:[self startWithNext:^(id next)
[mainDisposable setDisposable:[self startWithNext:^(id next)
{
[subscriber putNext:next];
} error:^(id error)
{
MTSignal *signal = f(error);
[disposable add:[signal startWithNext:^(id next)
[alternativeDisposable setDisposable:[signal startWithNext:^(id next)
{
[subscriber putNext:next];
} error:^(id error)
@ -522,7 +544,10 @@
[subscriber putCompletion];
}]];
return disposable;
return [[MTBlockDisposable alloc] initWithBlock:^{
[mainDisposable dispose];
[alternativeDisposable dispose];
}];
}];
}

View File

@ -3014,8 +3014,8 @@ final class PostboxImpl {
let endTime = CFAbsoluteTimeGetCurrent()
let transactionDuration = endTime - startTime
if transactionDuration > 0.1 {
postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)")
if transactionDuration > 0.01 {
postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file):\(line)")
}
let _ = self.isInTransaction.swap(false)
@ -3028,7 +3028,7 @@ final class PostboxImpl {
return (result, updatedTransactionState, updatedMasterClientId)
}
public func transactionSignal<T, E>(userInteractive: Bool = false, _ f: @escaping(Subscriber<T, E>, Transaction) -> Disposable) -> Signal<T, E> {
public func transactionSignal<T, E>(userInteractive: Bool = false, _ f: @escaping(Subscriber<T, E>, Transaction) -> Disposable, file: String = #file, line: Int = #line) -> Signal<T, E> {
return Signal { subscriber in
let disposable = MetaDisposable()
@ -3036,7 +3036,7 @@ final class PostboxImpl {
self.beginInternalTransaction {
let (_, updatedTransactionState, updatedMasterClientId) = self.internalTransaction({ transaction in
disposable.set(f(subscriber, transaction))
})
}, file: file, line: line)
if updatedTransactionState != nil || updatedMasterClientId != nil {
//self.pipeNotifier.notify()

View File

@ -1741,7 +1741,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
if let strongSelf = self {
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: true))
}
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info._parse(), items: featuredEmojiPack.topItems).start()
break
}

View File

@ -3,14 +3,15 @@ import Foundation
public func `catch`<T, E, R>(_ f: @escaping(E) -> Signal<T, R>) -> (Signal<T, E>) -> Signal<T, R> {
return { signal in
return Signal<T, R> { subscriber in
let disposable = DisposableSet()
let mainDisposable = MetaDisposable()
let alternativeDisposable = MetaDisposable()
disposable.add(signal.start(next: { next in
mainDisposable.set(signal.start(next: { next in
subscriber.putNext(next)
}, error: { error in
let anotherSignal = f(error)
disposable.add(anotherSignal.start(next: { next in
alternativeDisposable.set(anotherSignal.start(next: { next in
subscriber.putNext(next)
}, error: { error in
subscriber.putError(error)
@ -21,7 +22,10 @@ public func `catch`<T, E, R>(_ f: @escaping(E) -> Signal<T, R>) -> (Signal<T, E>
subscriber.putCompletion()
}))
return disposable
return ActionDisposable {
mainDisposable.dispose()
alternativeDisposable.dispose()
}
}
}
}

View File

@ -246,3 +246,25 @@ public func deferred<T, E>(_ generator: @escaping() -> Signal<T, E>) -> Signal<T
})
}
}
public func debug_measureTimeToFirstEvent<T, E>(label: String) -> (Signal<T, E>) -> Signal<T, E> {
return { signal in
#if DEBUG || true
if "".isEmpty {
var isFirst = true
return Signal { subscriber in
let startTimestamp = CFAbsoluteTimeGetCurrent()
return signal.start(next: { value in
if isFirst {
isFirst = false
let deltaTime = (CFAbsoluteTimeGetCurrent() - startTimestamp) * 1000.0
print("measureTimeToFirstEvent(\(label): \(deltaTime) ms")
}
subscriber.putNext(value)
}, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
#endif
return signal
}
}

View File

@ -141,7 +141,7 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry {
case let .info(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .pack(_, _, _, info, topItem, count, animatedStickers, enabled, editing, selected):
return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .installation(installed: false), editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .installation(installed: false), editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info)
}, setPackIdWithRevealedOptions: { current, previous in
arguments.setPackIdWithRevealedOptions(current, previous)
@ -323,11 +323,12 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
let parsedInfo = info._parse()
return context.engine.stickers.addStickerPackInteractively(info: parsedInfo, items: items)
|> ignoreValues
|> mapToSignal { _ -> Signal<(StickerPackCollectionInfo, [StickerPackItem]), NoError> in
}
|> then(.single((info, items)))
|> then(.single((parsedInfo, items)))
}
case .fetching:
break
@ -489,11 +490,12 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
let parsedInfo = info._parse()
return context.engine.stickers.addStickerPackInteractively(info: parsedInfo, items: items)
|> ignoreValues
|> mapToSignal { _ -> Signal<(StickerPackCollectionInfo, [StickerPackItem]), NoError> in
}
|> then(.single((info, items)))
|> then(.single((parsedInfo, items)))
}
case .fetching:
break

View File

@ -34,7 +34,7 @@ private enum FeaturedStickerPacksEntryId: Hashable {
}
private enum FeaturedStickerPacksEntry: ItemListNodeEntry {
case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, Bool, StickerPackItem?, String, Bool, Bool)
case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo.Accessor, Bool, StickerPackItem?, String, Bool, Bool)
var section: ItemListSectionId {
switch self {
@ -103,10 +103,10 @@ private enum FeaturedStickerPacksEntry: ItemListNodeEntry {
switch self {
case let .pack(_, _, _, info, unread, topItem, count, playAnimatedStickers, installed):
return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: playAnimatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info)
arguments.openStickerPack(info._parse())
}, setPackIdWithRevealedOptions: { _, _ in
}, addPack: {
arguments.addPack(info)
arguments.addPack(info._parse())
}, removePack: {
}, toggleSelected: {
})
@ -178,7 +178,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
}
case .fetching:
break

View File

@ -433,7 +433,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case let .packsTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .pack(_, _, _, info, topItem, count, animatedStickers, enabled, editing, selected):
return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .none, editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .none, editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info)
}, setPackIdWithRevealedOptions: { current, previous in
arguments.setPackIdWithRevealedOptions(current, previous)
@ -853,7 +853,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
}
case .fetching:
break

View File

@ -196,6 +196,7 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
case let .result(info, items, _):
var preloadSignals: [Signal<Bool, NoError>] = []
let info = info._parse()
if let thumbnail = info.thumbnail {
let signal = Signal<Bool, NoError> { subscriber in
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start()

View File

@ -539,11 +539,12 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, ASScrol
guard let strongSelf = self, let (positionInList, _) = indexAndItems else {
return
}
strongSelf.actionPerformed?(info, items, .remove(positionInList: positionInList))
strongSelf.actionPerformed?(info._parse(), items, .remove(positionInList: positionInList))
})
} else {
let _ = self.context.engine.stickers.addStickerPackInteractively(info: info, items: items).start()
self.actionPerformed?(info, items, .add)
let parsedInfo = info._parse()
let _ = self.context.engine.stickers.addStickerPackInteractively(info: parsedInfo, items: items).start()
self.actionPerformed?(parsedInfo, items, .add)
}
self.cancelButtonPressed()
default:

View File

@ -459,7 +459,7 @@ private final class StickerPackContainer: ASDisplayNode {
var contents: [LoadedStickerPack] = []
for (info, items, isInstalled) in strongSelf.currentStickerPacks {
contents.append(.result(info: info, items: items, installed: isInstalled))
contents.append(.result(info: StickerPackCollectionInfo.Accessor(info), items: items, installed: isInstalled))
}
strongSelf.updateStickerPackContents(contents, hasPremium: false)
@ -476,7 +476,7 @@ private final class StickerPackContainer: ASDisplayNode {
var contents: [LoadedStickerPack] = []
for (info, items, isInstalled) in strongSelf.currentStickerPacks {
contents.append(.result(info: info, items: items, installed: isInstalled))
contents.append(.result(info: StickerPackCollectionInfo.Accessor(info), items: items, installed: isInstalled))
}
strongSelf.updateStickerPackContents(contents, hasPremium: false)
@ -1534,7 +1534,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.requestDismiss()
dismissed = true
case .navigatedNext, .ignored:
self.updateStickerPackContents([.result(info: info, items: items, installed: !installed)], hasPremium: false)
self.updateStickerPackContents([.result(info: StickerPackCollectionInfo.Accessor(info), items: items, installed: !installed)], hasPremium: false)
}
let actionPerformed = self.controller?.actionPerformed
@ -1707,11 +1707,11 @@ private final class StickerPackContainer: ASDisplayNode {
var installedCount = 0
for content in contents {
if case let .result(info, items, isInstalled) = content {
entries.append(.emojis(index: index, stableId: index, info: info, items: items, title: info.title, isInstalled: isInstalled))
entries.append(.emojis(index: index, stableId: index, info: info._parse(), items: items, title: info.title, isInstalled: isInstalled))
if isInstalled {
installedCount += 1
}
currentStickerPacks.append((info, items, isInstalled))
currentStickerPacks.append((info._parse(), items, isInstalled))
}
index += 1
}
@ -1772,6 +1772,9 @@ private final class StickerPackContainer: ASDisplayNode {
case let .result(info, items, installed):
isEditable = info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)
self.onReady()
let info = info._parse()
if !items.isEmpty && self.currentStickerPack == nil {
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false)

View File

@ -587,7 +587,8 @@ public func chatMessageAnimatedSticker(postbox: Postbox, userLocation: MediaReso
}
}
public func preloadedStickerPackThumbnail(account: Account, info: StickerPackCollectionInfo, items: [ItemCollectionItem]) -> Signal<Bool, NoError> {
public func preloadedStickerPackThumbnail(account: Account, info: StickerPackCollectionInfo.Accessor, items: [ItemCollectionItem]) -> Signal<Bool, NoError> {
let info = info._parse()
if let thumbnail = info.thumbnail {
let signal = Signal<Bool, NoError> { subscriber in
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start()

View File

@ -0,0 +1,6 @@
namespace TelegramCore;
struct ItemCollectionId {
namespace: int;
id: int64;
}

View File

@ -0,0 +1,19 @@
include "ItemCollectionId.fbs";
include "TelegramMediaImageRepresentation.fbs";
namespace TelegramCore;
table StickerPackCollectionInfo {
id:ItemCollectionId (id: 0, required);
flags:int32 (id: 1);
accessHash:int64 (id: 2);
title:string (id: 3, required);
shortName:string (id: 4, required);
thumbnail:TelegramMediaImageRepresentation (id: 5);
thumbnailFileId:int64 (id: 6);
immediateThumbnailData:[ubyte] (id: 7);
hash:int32 (id: 8);
count:int32 (id: 9);
}
root_type StickerPackCollectionInfo;

View File

@ -159,7 +159,7 @@ func updatedFeaturedStickerPacks(network: Network, postbox: Postbox, category: F
items = Array(items.prefix(5))
}
}
updatedPacks.append(FeaturedStickerPackItem(info: info, topItems: items, unread: unreadIds.contains(info.id.id)))
updatedPacks.append(FeaturedStickerPackItem(info: StickerPackCollectionInfo.Accessor(info), topItems: items, unread: unreadIds.contains(info.id.id)))
}
let isPremium = flags & (1 << 0) != 0
return .content(FeaturedListContent(
@ -194,7 +194,7 @@ func updatedFeaturedStickerPacks(network: Network, postbox: Postbox, category: F
items = previousPack.topItems
}
}
updatedPacks.append(FeaturedStickerPackItem(info: info, topItems: items, unread: unreadIds.contains(info.id.id)))
updatedPacks.append(FeaturedStickerPackItem(info: StickerPackCollectionInfo.Accessor(info), topItems: items, unread: unreadIds.contains(info.id.id)))
}
let isPremium = flags & (1 << 0) != 0
return .content(FeaturedListContent(
@ -247,7 +247,7 @@ public func requestOldFeaturedStickerPacks(network: Network, postbox: Postbox, o
var updatedPacks: [FeaturedStickerPackItem] = []
for set in sets {
let (info, items) = parsePreviewStickerSet(set, namespace: Namespaces.ItemCollection.CloudStickerPacks)
updatedPacks.append(FeaturedStickerPackItem(info: info, topItems: items, unread: unreadIds.contains(info.id.id)))
updatedPacks.append(FeaturedStickerPackItem(info: StickerPackCollectionInfo.Accessor(info), topItems: items, unread: unreadIds.contains(info.id.id)))
}
return updatedPacks
}

View File

@ -1,12 +1,15 @@
import Foundation
import Postbox
import FlatBuffers
import FlatSerialization
public final class CachedStickerPack: Codable {
public let info: StickerPackCollectionInfo?
public let info: StickerPackCollectionInfo.Accessor?
public let items: [StickerPackItem]
public let hash: Int32
public init(info: StickerPackCollectionInfo?, items: [StickerPackItem], hash: Int32) {
self.info = info
self.info = info.flatMap(StickerPackCollectionInfo.Accessor.init)
self.items = items
self.hash = hash
}
@ -14,8 +17,12 @@ public final class CachedStickerPack: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
if let infoData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "in") {
self.info = StickerPackCollectionInfo(decoder: PostboxDecoder(buffer: MemoryBuffer(data: infoData.data)))
if let serializedInfoData = try container.decodeIfPresent(Data.self, forKey: "ind") {
var byteBuffer = ByteBuffer(data: serializedInfoData)
self.info = StickerPackCollectionInfo.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_StickerPackCollectionInfo, serializedInfoData)
} else if let infoData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "in") {
let info = StickerPackCollectionInfo(decoder: PostboxDecoder(buffer: MemoryBuffer(data: infoData.data)))
self.info = StickerPackCollectionInfo.Accessor(info)
} else {
self.info = nil
}
@ -31,7 +38,17 @@ public final class CachedStickerPack: Codable {
var container = encoder.container(keyedBy: StringCodingKey.self)
if let info = self.info {
try container.encode(PostboxEncoder().encodeObjectToRawData(info), forKey: "in")
if let infoData = info._wrappedData {
try container.encode(infoData, forKey: "ind")
} else if let info = info._wrappedObject {
var builder = FlatBufferBuilder(initialSize: 1024)
let value = info.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: value)
let serializedInstantPage = builder.data
try container.encode(serializedInstantPage, forKey: "ind")
} else {
preconditionFailure()
}
} else {
try container.encodeNil(forKey: "in")
}

View File

@ -1,5 +1,7 @@
import Foundation
import Postbox
import FlatBuffers
import FlatSerialization
public struct FeaturedStickerPackItemId {
public let rawValue: MemoryBuffer
@ -22,11 +24,11 @@ public struct FeaturedStickerPackItemId {
}
public final class FeaturedStickerPackItem: Codable {
public let info: StickerPackCollectionInfo
public let info: StickerPackCollectionInfo.Accessor
public let topItems: [StickerPackItem]
public let unread: Bool
public init(info: StickerPackCollectionInfo, topItems: [StickerPackItem], unread: Bool) {
public init(info: StickerPackCollectionInfo.Accessor, topItems: [StickerPackItem], unread: Bool) {
self.info = info
self.topItems = topItems
self.unread = unread
@ -35,8 +37,13 @@ public final class FeaturedStickerPackItem: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
let infoData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: "i")
self.info = StickerPackCollectionInfo(decoder: PostboxDecoder(buffer: MemoryBuffer(data: infoData.data)))
if let serializedInfoData = try container.decodeIfPresent(Data.self, forKey: "infd") {
var byteBuffer = ByteBuffer(data: serializedInfoData)
self.info = StickerPackCollectionInfo.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_StickerPackCollectionInfo, serializedInfoData)
} else {
let infoData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: "i")
self.info = StickerPackCollectionInfo.Accessor(StickerPackCollectionInfo(decoder: PostboxDecoder(buffer: MemoryBuffer(data: infoData.data))))
}
self.topItems = (try container.decode([AdaptedPostboxDecoder.RawObjectData].self, forKey: "t")).map { itemData in
return StickerPackItem(decoder: PostboxDecoder(buffer: MemoryBuffer(data: itemData.data)))
@ -48,7 +55,17 @@ public final class FeaturedStickerPackItem: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.info), forKey: "i")
if let infoData = self.info._wrappedData {
try container.encode(infoData, forKey: "infd")
} else if let info = self.info._wrappedObject {
var builder = FlatBufferBuilder(initialSize: 1024)
let value = info.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: value)
let serializedInstantPage = builder.data
try container.encode(serializedInstantPage, forKey: "infd")
} else {
preconditionFailure()
}
try container.encode(self.topItems.map { item in
return PostboxEncoder().encodeObjectToRawData(item)
}, forKey: "t")

View File

@ -1,5 +1,7 @@
import Foundation
import Postbox
import FlatBuffers
import FlatSerialization
public struct StickerPackCollectionInfoFlags: OptionSet {
public var rawValue: Int32
@ -39,7 +41,6 @@ public struct StickerPackCollectionInfoFlags: OptionSet {
public static let isCreator = StickerPackCollectionInfoFlags(rawValue: 1 << 7)
}
public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
public let id: ItemCollectionId
public let flags: StickerPackCollectionInfoFlags
@ -102,6 +103,56 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
encoder.encodeInt32(self.hash, forKey: "h")
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
encoder.encodeInt32(self.count, forKey: "n")
#if DEBUG
var builder = FlatBufferBuilder(initialSize: 1024)
let offset = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset)
let serializedData = builder.data
var byteBuffer = ByteBuffer(data: serializedData)
let deserializedValue = FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_StickerPackCollectionInfo
let parsedValue = try! StickerPackCollectionInfo(flatBuffersObject: deserializedValue)
assert(self == parsedValue)
#endif
}
public init(flatBuffersObject: TelegramCore_StickerPackCollectionInfo) throws {
self.id = ItemCollectionId(flatBuffersObject.id)
self.flags = StickerPackCollectionInfoFlags(rawValue: flatBuffersObject.flags)
self.accessHash = flatBuffersObject.accessHash
self.title = flatBuffersObject.title
self.shortName = flatBuffersObject.shortName
self.thumbnail = try flatBuffersObject.thumbnail.flatMap(TelegramMediaImageRepresentation.init(flatBuffersObject:))
self.thumbnailFileId = flatBuffersObject.thumbnailFileId == Int64.min ? nil : flatBuffersObject.thumbnailFileId
self.immediateThumbnailData = flatBuffersObject.immediateThumbnailData.isEmpty ? nil : Data(flatBuffersObject.immediateThumbnailData)
self.hash = flatBuffersObject.hash
self.count = flatBuffersObject.count
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let titleOffset = builder.create(string: self.title)
let shortNameOffset = builder.create(string: self.shortName)
let thumbnailOffset = self.thumbnail.flatMap { $0.encodeToFlatBuffers(builder: &builder) }
let immediateThumbnailDataOffset = self.immediateThumbnailData.flatMap { builder.createVector(bytes: $0) }
let start = TelegramCore_StickerPackCollectionInfo.startStickerPackCollectionInfo(&builder)
TelegramCore_StickerPackCollectionInfo.add(id: self.id.asFlatBuffersObject(), &builder)
TelegramCore_StickerPackCollectionInfo.add(flags: self.flags.rawValue, &builder)
TelegramCore_StickerPackCollectionInfo.add(accessHash: self.accessHash, &builder)
TelegramCore_StickerPackCollectionInfo.add(title: titleOffset, &builder)
TelegramCore_StickerPackCollectionInfo.add(shortName: shortNameOffset, &builder)
if let thumbnailOffset {
TelegramCore_StickerPackCollectionInfo.add(thumbnail: thumbnailOffset, &builder)
}
TelegramCore_StickerPackCollectionInfo.add(thumbnailFileId: self.thumbnailFileId ?? Int64.min, &builder)
if let immediateThumbnailDataOffset {
TelegramCore_StickerPackCollectionInfo.addVectorOf(immediateThumbnailData: immediateThumbnailDataOffset, &builder)
}
TelegramCore_StickerPackCollectionInfo.add(hash: self.hash, &builder)
TelegramCore_StickerPackCollectionInfo.add(count: self.count, &builder)
return TelegramCore_StickerPackCollectionInfo.endStickerPackCollectionInfo(&builder, start: start)
}
public static func ==(lhs: StickerPackCollectionInfo, rhs: StickerPackCollectionInfo) -> Bool {
@ -129,6 +180,141 @@ public final class StickerPackCollectionInfo: ItemCollectionInfo, Equatable {
if lhs.count != rhs.count {
return false
}
if lhs.thumbnail != rhs.thumbnail {
return false
}
return true
}
}
public extension StickerPackCollectionInfo {
struct Accessor: Equatable {
let _wrappedObject: StickerPackCollectionInfo?
let _wrapped: TelegramCore_StickerPackCollectionInfo?
let _wrappedData: Data?
public init(_ wrapped: TelegramCore_StickerPackCollectionInfo, _ _wrappedData: Data) {
self._wrapped = wrapped
self._wrappedData = _wrappedData
self._wrappedObject = nil
}
public init(_ wrapped: StickerPackCollectionInfo) {
self._wrapped = nil
self._wrappedData = nil
self._wrappedObject = wrapped
}
public func _parse() -> StickerPackCollectionInfo {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject
} else {
return try! StickerPackCollectionInfo(flatBuffersObject: self._wrapped!)
}
}
public static func ==(lhs: StickerPackCollectionInfo.Accessor, rhs: StickerPackCollectionInfo.Accessor) -> Bool {
if let lhsWrappedObject = lhs._wrappedObject, let rhsWrappedObject = rhs._wrappedObject {
return lhsWrappedObject == rhsWrappedObject
} else if let lhsWrappedData = lhs._wrappedData, let rhsWrappedData = rhs._wrappedData {
return lhsWrappedData == rhsWrappedData
} else {
return lhs._parse() == rhs._parse()
}
}
}
}
public extension StickerPackCollectionInfo.Accessor {
var id: ItemCollectionId {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.id
}
return ItemCollectionId(self._wrapped!.id)
}
var accessHash: Int64 {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.accessHash
}
return self._wrapped!.accessHash
}
var flags: StickerPackCollectionInfoFlags {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.flags
}
return StickerPackCollectionInfoFlags(rawValue: self._wrapped!.flags)
}
var title: String {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.title
}
return self._wrapped!.title
}
var shortName: String {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.shortName
}
return self._wrapped!.shortName
}
var hash: Int32 {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.hash
}
return self._wrapped!.hash
}
var immediateThumbnailData: Data? {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.immediateThumbnailData
}
return self._wrapped!.immediateThumbnailData.isEmpty ? nil : Data(self._wrapped!.immediateThumbnailData)
}
var thumbnailFileId: Int64? {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.thumbnailFileId
}
return self._wrapped!.thumbnailFileId == Int64.min ? nil : self._wrapped!.thumbnailFileId
}
var count: Int32 {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.count
}
return self._wrapped!.count
}
var hasThumbnail: Bool {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.thumbnail != nil
}
return self._wrapped!.thumbnail != nil
}
var thumbnailDimensions: PixelDimensions? {
if let _wrappedObject = self._wrappedObject {
return _wrappedObject.thumbnail?.dimensions
}
if let thumbnail = self._wrapped!.thumbnail {
return PixelDimensions(width: thumbnail.width, height: thumbnail.height)
} else {
return nil
}
}
}

View File

@ -12,7 +12,7 @@ private struct WrappedStickerPackCollectionInfo: Equatable {
}
public struct PeerSpecificStickerPackData {
public let packInfo: (StickerPackCollectionInfo, [ItemCollectionItem])?
public let packInfo: (StickerPackCollectionInfo.Accessor, [ItemCollectionItem])?
public let canSetup: Bool
}

View File

@ -6,7 +6,7 @@ import MurMurHash32
public enum CachedStickerPackResult {
case none
case fetching
case result(StickerPackCollectionInfo, [StickerPackItem], Bool)
case result(StickerPackCollectionInfo.Accessor, [StickerPackItem], Bool)
}
func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo, items: [StickerPackItem], reference: StickerPackReference? = nil) {
@ -228,7 +228,7 @@ func _internal_cachedStickerPack(postbox: Postbox, network: Network, reference:
if let result = result {
cacheStickerPack(transaction: transaction, info: result.0, items: result.1, reference: reference)
let currentInfo = transaction.getItemCollectionInfo(collectionId: result.0.id) as? StickerPackCollectionInfo
return .result(result.0, result.1, currentInfo != nil)
return .result(StickerPackCollectionInfo.Accessor(result.0), result.1, currentInfo != nil)
} else {
return .none
}
@ -244,7 +244,7 @@ func _internal_cachedStickerPack(postbox: Postbox, network: Network, reference:
}
}
func cachedStickerPack(transaction: Transaction, reference: StickerPackReference) -> (StickerPackCollectionInfo, [StickerPackItem], Bool)? {
func cachedStickerPack(transaction: Transaction, reference: StickerPackReference) -> (StickerPackCollectionInfo.Accessor, [StickerPackItem], Bool)? {
let namespaces: [Int32] = [Namespaces.ItemCollection.CloudStickerPacks, Namespaces.ItemCollection.CloudMaskPacks, Namespaces.ItemCollection.CloudEmojiPacks]
switch reference {
case let .id(id, _):
@ -252,7 +252,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -267,7 +267,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if info.shortName.lowercased() == shortName {
let items = transaction.getItemCollectionItems(collectionId: info.id)
if !items.isEmpty {
return (info, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(info), items.compactMap { $0 as? StickerPackItem }, true)
}
}
}
@ -282,7 +282,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -294,7 +294,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -306,7 +306,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -318,7 +318,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -330,7 +330,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -342,7 +342,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -354,7 +354,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {
@ -366,7 +366,7 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
if !items.isEmpty {
return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true)
return (StickerPackCollectionInfo.Accessor(currentInfo), items.compactMap { $0 as? StickerPackItem }, true)
}
}
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info {

View File

@ -37,7 +37,7 @@ extension StickerPackReference {
public enum LoadedStickerPack {
case fetching
case none
case result(info: StickerPackCollectionInfo, items: [StickerPackItem], installed: Bool)
case result(info: StickerPackCollectionInfo.Accessor, items: [StickerPackItem], installed: Bool)
}
func updatedRemoteStickerPack(postbox: Postbox, network: Network, reference: StickerPackReference) -> Signal<(StickerPackCollectionInfo, [StickerPackItem])?, NoError> {

View File

@ -52,3 +52,13 @@ public extension PixelDimensions {
return TelegramCore_PixelDimensions(width: self.width, height: self.height)
}
}
public extension ItemCollectionId {
init(_ id: TelegramCore_ItemCollectionId) {
self.init(namespace: id.namespace, id: id.id)
}
func asFlatBuffersObject() -> TelegramCore_ItemCollectionId {
return TelegramCore_ItemCollectionId(namespace: self.namespace, id: self.id)
}
}

View File

@ -556,7 +556,7 @@ final class AvatarEditorScreenComponent: Component {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
}
case .fetching:
break
@ -691,7 +691,7 @@ final class AvatarEditorScreenComponent: Component {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
}
case .fetching:
break

View File

@ -1050,6 +1050,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate,
}
public var toggleQuoteCollapse: ((NSRange) -> Void)?
public var onUpdateLayout: (() -> Void)?
private let displayInternal: ChatInputTextInternal
private let measureInternal: ChatInputTextInternal
@ -1385,6 +1386,8 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate,
for id in removedBlockQuotes {
self.blockQuotes.removeValue(forKey: id)
}
self.onUpdateLayout?()
}
override public func caretRect(for position: UITextPosition) -> CGRect {

View File

@ -546,7 +546,11 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
public func updatePeer(peer: Peer) {
if let previousPeer = self.peer, previousPeer.nameColor != peer.nameColor {
self.peer = peer
self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: 38.0, height: 38.0))
if peer.smallProfileImage != nil {
self.avatarNode.setPeerV2(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: 38.0, height: 38.0))
} else {
self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: 38.0, height: 38.0))
}
}
}
@ -561,7 +565,11 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
if peer.isDeleted {
overrideImage = .deletedIcon
}
self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 38.0, height: 38.0))
if peer.smallProfileImage != nil {
self.avatarNode.setPeerV2(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 38.0, height: 38.0))
} else {
self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 38.0, height: 38.0))
}
if peer.isPremium && context.sharedContext.energyUsageSettings.autoplayVideo {
self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id)

View File

@ -787,7 +787,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
if let strongSelf = self {
strongSelf.scheduledContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: scrollToGroup))
}
let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info._parse(), items: featuredEmojiPack.topItems).start()
break
}
@ -1300,7 +1300,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
}
case .fetching:
break

View File

@ -95,7 +95,7 @@ private enum StickerSearchEntry: Identifiable, Comparable {
case let .global(_, info, topItems, installed, topSeparator):
let itemContext = StickerPaneSearchGlobalItemContext()
itemContext.canPlayMedia = true
return StickerPaneSearchGlobalItem(context: context, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
return StickerPaneSearchGlobalItem(context: context, theme: theme, strings: strings, listAppearance: false, info: StickerPackCollectionInfo.Accessor(info), topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
interaction.open(info)
}, install: {
interaction.install(info, topItems, !installed)
@ -255,10 +255,11 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|> mapToSignal { result -> Signal<(StickerPackCollectionInfo, [StickerPackItem]), NoError> in
switch result {
case let .result(info, items, installed):
let info = info._parse()
if installed {
return .complete()
} else {
return preloadedStickerPackThumbnail(account: context.account, info: info, items: items)
return preloadedStickerPackThumbnail(account: context.account, info: StickerPackCollectionInfo.Accessor(info), items: items)
|> filter { $0 }
|> ignoreValues
|> then(

View File

@ -452,7 +452,7 @@ public final class EmojiStatusSelectionController: ViewController {
if let strongSelf = self {
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: true))
}
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info._parse(), items: featuredEmojiPack.topItems).start()
break
}

View File

@ -62,12 +62,15 @@ public final class EntityKeyboardAnimationData: Equatable {
public enum Resource: Equatable {
case resource(MediaResourceReference)
case stickerPackThumbnail(id: Int64, accessHash: Int64, info: StickerPackCollectionInfo.Accessor)
case file(PartialMediaReference?, TelegramMediaFile.Accessor)
func _parse() -> MediaResourceReference {
switch self {
case let .resource(resource):
return resource
case let .stickerPackThumbnail(id, accessHash, info):
return .stickerPackThumbnail(stickerPack: .id(id: id, accessHash: accessHash), resource: info._parse().thumbnail!.resource)
case let .file(partialReference, file):
let file = file._parse()
if let partialReference {

View File

@ -73,7 +73,7 @@ public extension EmojiPagerContentComponent {
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
struct PeerSpecificPackData: Equatable {
var info: StickerPackCollectionInfo
var info: StickerPackCollectionInfo.Accessor
var items: [StickerPackItem]
var peer: EnginePeer
@ -282,7 +282,7 @@ public extension EmojiPagerContentComponent {
let animationData: EntityKeyboardAnimationData
if let thumbnail = featuredEmojiPack.info.thumbnail {
if let thumbnailDimensions = featuredEmojiPack.info.thumbnailDimensions {
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
@ -295,8 +295,8 @@ public extension EmojiPagerContentComponent {
animationData = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(featuredEmojiPack.info.id),
type: type,
resource: .resource(.stickerPackThumbnail(stickerPack: .id(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash), resource: thumbnail.resource)),
dimensions: thumbnail.dimensions.cgSize,
resource: .stickerPackThumbnail(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash, info: featuredEmojiPack.info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: featuredEmojiPack.info.immediateThumbnailData,
isReaction: false,
isTemplate: false
@ -1471,7 +1471,7 @@ public extension EmojiPagerContentComponent {
var headerItem: EntityKeyboardAnimationData?
if let thumbnailFileId = featuredEmojiPack.info.thumbnailFileId, let file = featuredEmojiPack.topItems.first(where: { $0.file.fileId.id == thumbnailFileId }) {
headerItem = EntityKeyboardAnimationData(file: file.file)
} else if let thumbnail = featuredEmojiPack.info.thumbnail {
} else if let thumbnailDimensions = featuredEmojiPack.info.thumbnailDimensions {
let info = featuredEmojiPack.info
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
@ -1485,8 +1485,8 @@ public extension EmojiPagerContentComponent {
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .resource(.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)),
dimensions: thumbnail.dimensions.cgSize,
resource: .stickerPackThumbnail(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash, info: featuredEmojiPack.info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData,
isReaction: false,
isTemplate: false
@ -1632,7 +1632,7 @@ public extension EmojiPagerContentComponent {
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
struct PeerSpecificPackData: Equatable {
var info: StickerPackCollectionInfo
var info: StickerPackCollectionInfo.Accessor
var items: [StickerPackItem]
var peer: EnginePeer
@ -1769,7 +1769,7 @@ public extension EmojiPagerContentComponent {
let animationData: EntityKeyboardAnimationData
if let thumbnail = featuredStickerPack.info.thumbnail {
if let thumbnailDimensions = featuredStickerPack.info.thumbnailDimensions {
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
@ -1782,8 +1782,8 @@ public extension EmojiPagerContentComponent {
animationData = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(featuredStickerPack.info.id),
type: type,
resource: .resource(.stickerPackThumbnail(stickerPack: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), resource: thumbnail.resource)),
dimensions: thumbnail.dimensions.cgSize,
resource: .stickerPackThumbnail(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash, info: featuredStickerPack.info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: featuredStickerPack.info.immediateThumbnailData,
isReaction: false,
isTemplate: false
@ -2084,7 +2084,7 @@ public extension EmojiPagerContentComponent {
if let thumbnailFileId = featuredStickerPack.info.thumbnailFileId, let file = featuredStickerPack.topItems.first(where: { $0.file.fileId.id == thumbnailFileId }) {
headerItem = EntityKeyboardAnimationData(file: file.file)
} else if let thumbnail = featuredStickerPack.info.thumbnail {
} else if let thumbnailDimensions = featuredStickerPack.info.thumbnailDimensions {
let info = featuredStickerPack.info
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
@ -2098,8 +2098,8 @@ public extension EmojiPagerContentComponent {
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .resource(.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)),
dimensions: thumbnail.dimensions.cgSize,
resource: .stickerPackThumbnail(id: info.id.id, accessHash: info.accessHash, info: info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData,
isReaction: false,
isTemplate: false

View File

@ -910,7 +910,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
}
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId {
let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info._parse(), items: featuredEmojiPack.topItems).start()
break
}

View File

@ -220,7 +220,7 @@ private enum GroupStickerPackEntry: ItemListNodeEntry {
case let .packsTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .pack(_, _, _, info, topItem, count, playAnimatedStickers, selected):
return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: selected ? .selection : .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: playAnimatedStickers, sectionId: self.section, action: {
return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: selected ? .selection : .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: playAnimatedStickers, sectionId: self.section, action: {
if selected {
arguments.openStickerPack(info)
} else {
@ -337,7 +337,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
case .fetching:
return nil
case let .result(info, items, _):
return InitialStickerPackData.data(StickerPackData(info: info, item: items.first))
return InitialStickerPackData.data(StickerPackData(info: info._parse(), item: items.first))
}
})
} else {
@ -389,7 +389,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres
case .none:
return .single((searchText, .notFound))
case let .result(info, items, _):
return .single((searchText, .found(StickerPackData(info: info, item: items.first))))
return .single((searchText, .found(StickerPackData(info: info._parse(), item: items.first))))
}
}
|> afterNext { value in

View File

@ -48,7 +48,7 @@ private final class GroupStickerSearchEntry: Comparable, Identifiable {
let pack = self.pack
let count = presentationData.strings.StickerPack_EmojiCount(pack.count)
return ItemListStickerPackItem(presentationData: ItemListPresentationData(presentationData), context: context, packInfo: pack, itemCount: count, topItem: self.topItem as? StickerPackItem, unread: false, control: .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: true, style: .plain, sectionId: 0, action: {
return ItemListStickerPackItem(presentationData: ItemListPresentationData(presentationData), context: context, packInfo: StickerPackCollectionInfo.Accessor(pack), itemCount: count, topItem: self.topItem as? StickerPackItem, unread: false, control: .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: true, style: .plain, sectionId: 0, action: {
interaction.packSelected(pack)
}, setPackIdWithRevealedOptions: { _, _ in
}, addPack: {

View File

@ -955,7 +955,7 @@ public class StickerPickerScreen: ViewController {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
}
case .fetching:
break
@ -1370,7 +1370,7 @@ public class StickerPickerScreen: ViewController {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info, items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
}
case .fetching:
break

View File

@ -1901,6 +1901,14 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
extendNow = false
}
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 2.0, extendNow: extendNow)
let _ = (sharedApplicationContext.sharedContext.activeAccountContexts
|> take(1)
|> deliverOnMainQueue).start(next: { activeAccounts in
for (_, context, _) in activeAccounts.accounts {
context.account.postbox.clearCaches()
}
})
})
self.isInForegroundValue = false

View File

@ -552,7 +552,6 @@ extension ChatControllerImpl {
return .single(true)
} else {
return .single(false)
|> delay(0.1, queue: .mainQueue())
}
}
|> distinctUntilChanged
@ -717,15 +716,15 @@ extension ChatControllerImpl {
}
)
self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages,
hasPendingMessages,
isTopReplyThreadMessageShown,
topPinnedMessage,
customEmojiAvailable,
isForum,
threadData,
forumTopicData,
premiumGiftOptions
self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages |> debug_measureTimeToFirstEvent(label: "cachedData_cachedPeerDataAndMessages"),
hasPendingMessages |> debug_measureTimeToFirstEvent(label: "cachedData_hasPendingMessages"),
isTopReplyThreadMessageShown |> debug_measureTimeToFirstEvent(label: "cachedData_isTopReplyThreadMessageShown"),
topPinnedMessage |> debug_measureTimeToFirstEvent(label: "cachedData_topPinnedMessage"),
customEmojiAvailable |> debug_measureTimeToFirstEvent(label: "cachedData_customEmojiAvailable"),
isForum |> debug_measureTimeToFirstEvent(label: "cachedData_isForum"),
threadData |> debug_measureTimeToFirstEvent(label: "cachedData_threadData"),
forumTopicData |> debug_measureTimeToFirstEvent(label: "cachedData_forumTopicData"),
premiumGiftOptions |> debug_measureTimeToFirstEvent(label: "cachedData_premiumGiftOptions")
).startStrict(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable, isForum, threadData, forumTopicData, premiumGiftOptions in
if let strongSelf = self {
let (cachedData, messages) = cachedDataAndMessages
@ -990,21 +989,26 @@ extension ChatControllerImpl {
if case .replyThread = self.chatLocation {
effectiveCachedDataReady = self.cachedDataReady.get()
} else {
//effectiveCachedDataReady = .single(true)
effectiveCachedDataReady = self.cachedDataReady.get()
}
var measure_isFirstTime = true
let initTimestamp = self.initTimestamp
let mapped_chatLocationInfoReady = self._chatLocationInfoReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "chatLocationInfoReady")
let mapped_effectiveCachedDataReady = effectiveCachedDataReady |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "effectiveCachedDataReady")
let mapped_initialDataReady = initialData |> map { $0 != nil } |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "initialDataReady")
let mapped_wallpaperReady = self.wallpaperReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "wallpaperReady")
let mapped_presentationReady = self.presentationReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "presentationReady")
self.ready.set(combineLatest(queue: .mainQueue(),
self.chatDisplayNode.historyNode.historyState.get(),
self._chatLocationInfoReady.get(),
effectiveCachedDataReady,
initialData,
self.wallpaperReady.get(),
self.presentationReady.get()
mapped_chatLocationInfoReady,
mapped_effectiveCachedDataReady,
mapped_initialDataReady,
mapped_wallpaperReady,
mapped_presentationReady
)
|> map { _, chatLocationInfoReady, cachedDataReady, _, wallpaperReady, presentationReady in
return chatLocationInfoReady && cachedDataReady && wallpaperReady && presentationReady
|> map { chatLocationInfoReady, cachedDataReady, initialData, wallpaperReady, presentationReady in
return chatLocationInfoReady && cachedDataReady && initialData && wallpaperReady && presentationReady
}
|> distinctUntilChanged
|> beforeNext { value in
@ -1016,6 +1020,9 @@ extension ChatControllerImpl {
#endif
}
})
#if DEBUG
//self.ready.set(.single(true))
#endif
if self.context.sharedContext.immediateExperimentalUISettings.crashOnLongQueries {
let _ = (self.ready.get()

View File

@ -1720,33 +1720,33 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
let startTime = CFAbsoluteTimeGetCurrent()
var measure_isFirstTime = true
let messageViewQueue = Queue.mainQueue()
let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue,
historyViewUpdate,
self.chatPresentationDataPromise.get(),
selectedMessages,
updatingMedia,
automaticDownloadNetworkType,
preferredStoryHighQuality,
animatedEmojiStickers,
additionalAnimatedEmojiStickers,
customChannelDiscussionReadState,
customThreadOutgoingReadState,
availableReactions,
availableMessageEffects,
savedMessageTags,
defaultReaction,
accountPeer,
audioTranscriptionSuggestion,
promises,
topicAuthorId,
translationState,
maxReadStoryId,
recommendedChannels,
audioTranscriptionTrial,
chatThemes,
deviceContactsNumbers,
contentSettings
).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings in
let historyViewTransitionDisposable = (combineLatest(queue: messageViewQueue,
historyViewUpdate |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_historyViewUpdate"),
self.chatPresentationDataPromise.get() |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_chatPresentationData"),
selectedMessages |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_selectedMessages"),
updatingMedia |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_updatingMedia"),
automaticDownloadNetworkType |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_automaticDownloadNetworkType"),
preferredStoryHighQuality |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_preferredStoryHighQuality"),
animatedEmojiStickers |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_animatedEmojiStickers"),
additionalAnimatedEmojiStickers |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_additionalAnimatedEmojiStickers"),
customChannelDiscussionReadState |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_customChannelDiscussionReadState"),
customThreadOutgoingReadState |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_customThreadOutgoingReadState"),
availableReactions |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_availableReactions"),
availableMessageEffects |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_availableMessageEffects"),
savedMessageTags |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_savedMessageTags"),
defaultReaction |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_defaultReaction"),
accountPeer |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_accountPeer"),
audioTranscriptionSuggestion |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_audioTranscriptionSuggestion"),
promises |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_promises"),
topicAuthorId |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_topicAuthorId"),
translationState |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_translationState"),
maxReadStoryId |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_maxReadStoryId"),
recommendedChannels |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_recommendedChannels"),
audioTranscriptionTrial |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_audioTranscriptionTrial"),
chatThemes |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_chatThemes"),
deviceContactsNumbers |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_deviceContactsNumbers"),
contentSettings |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_contentSettings")
) |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_firstChatHistoryTransition")).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings in
let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises
if measure_isFirstTime {
@ -2311,8 +2311,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
var didSetPresentationData = false
self.presentationDataDisposable = (combineLatest(queue: .mainQueue(),
updated,
appConfiguration
updated |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_beginPresentationDataManagement_updated"),
appConfiguration |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_beginPresentationDataManagement_appConfiguration")
)
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData, appConfiguration in
if let strongSelf = self {

View File

@ -2853,7 +2853,7 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
}
|> map { result -> StickerPackCollectionInfo? in
if case let .result(info, _, _) = result {
return info
return info._parse()
} else {
return nil
}

View File

@ -1214,6 +1214,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
self.textInputBackgroundNode.isUserInteractionEnabled = !textInputNode.isUserInteractionEnabled
//self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0])
textInputNode.textView.onUpdateLayout = { [weak self] in
guard let self else {
return
}
self.updateSpoiler()
}
textInputNode.textView.toggleQuoteCollapse = { [weak self] range in
guard let self else {
return

@ -1 +1 @@
Subproject commit 1ea67f88b8d9fd04fc151d164669f6c229d651d3
Subproject commit 4d122958a6bd8aa94437cf26750442b88cc0f5c0