mirror of
https://github.com/danog/Telegram.git
synced 2024-12-02 17:38:07 +01:00
327 lines
13 KiB
Objective-C
327 lines
13 KiB
Objective-C
#import "TGLocationThumbnailDataSource.h"
|
|
|
|
#import "ASQueue.h"
|
|
|
|
#import "TGWorkerPool.h"
|
|
#import "TGWorkerTask.h"
|
|
#import "TGMediaPreviewTask.h"
|
|
|
|
#import "TGMemoryImageCache.h"
|
|
|
|
#import "TGImageUtils.h"
|
|
#import "TGStringUtils.h"
|
|
#import "TGLocationUtils.h"
|
|
|
|
#import "TGImageBlur.h"
|
|
#import "UIImage+TG.h"
|
|
#import "NSObject+TGLock.h"
|
|
|
|
#import "TGMapSnapshotterActor.h"
|
|
#import "TGMediaStoreContext.h"
|
|
|
|
static TGWorkerPool *workerPool()
|
|
{
|
|
static TGWorkerPool *instance = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^
|
|
{
|
|
instance = [[TGWorkerPool alloc] init];
|
|
});
|
|
|
|
return instance;
|
|
}
|
|
|
|
static ASQueue *taskManagementQueue()
|
|
{
|
|
static ASQueue *queue = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^
|
|
{
|
|
queue = [[ASQueue alloc] initWithName:"org.telegram.mapThumbnailTaskManagementQueue"];
|
|
});
|
|
|
|
return queue;
|
|
}
|
|
|
|
@implementation TGLocationThumbnailDataSource
|
|
|
|
+ (void)load
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
[TGImageDataSource registerDataSource:[[self alloc] init]];
|
|
}
|
|
}
|
|
|
|
- (bool)canHandleUri:(NSString *)uri
|
|
{
|
|
return [uri hasPrefix:@"map-thumbnail://"];
|
|
}
|
|
|
|
- (bool)canHandleAttributeUri:(NSString *)uri
|
|
{
|
|
return [uri hasPrefix:@"map-thumbnail://"];
|
|
}
|
|
|
|
+ (TGMapSnapshotOptions *)snapshotOptionsForUri:(NSString *)uri size:(out CGSize *)size
|
|
{
|
|
NSDictionary *args = [TGStringUtils argumentDictionaryInUrlString:[uri substringFromIndex:@"map-thumbnail://?".length]];
|
|
|
|
CGSize imageSize = CGSizeMake([args[@"width"] intValue], [args[@"height"] intValue]);
|
|
|
|
if (size != NULL)
|
|
*size = imageSize;
|
|
|
|
TGMapSnapshotOptions *options = [[TGMapSnapshotOptions alloc] init];
|
|
|
|
CLLocationDegrees latitude = [TGLocationUtils adjustGMapLatitude:[args[@"latitude"] doubleValue] withPixelOffset:-10 zoom:15];
|
|
options.region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(latitude, [args[@"longitude"] doubleValue]), MKCoordinateSpanMake(0.003, 0.003));
|
|
options.imageSize = CGSizeMake(imageSize.width + 1, imageSize.height + 24);
|
|
|
|
return options;
|
|
}
|
|
|
|
+ (NSString *)mapAddressForUri:(NSString *)uri size:(out CGSize *)size
|
|
{
|
|
NSDictionary *args = [TGStringUtils argumentDictionaryInUrlString:[uri substringFromIndex:@"map-thumbnail://?".length]];
|
|
|
|
CGSize imageSize = CGSizeMake([args[@"width"] intValue], [args[@"height"] intValue]);
|
|
|
|
if (size != NULL)
|
|
*size = imageSize;
|
|
|
|
CLLocationDegrees latitude = [TGLocationUtils adjustGMapLatitude:[args[@"latitude"] doubleValue] withPixelOffset:-10 zoom:15];
|
|
|
|
return [[NSString alloc] initWithFormat:@"https://maps.googleapis.com/maps/api/staticmap?center=%.5f,%.5f&zoom=15&size=%dx%d&sensor=false&scale=%d&format=jpg&mobile=true", latitude, [args[@"longitude"] doubleValue], (int)(imageSize.width), (int)(imageSize.height + 24), 2];
|
|
}
|
|
|
|
+ (NSString *)sourceMapIdentifierForUri:(NSString *)uri size:(out CGSize *)size
|
|
{
|
|
if (iosMajorVersion() >= 7)
|
|
return [[self snapshotOptionsForUri:uri size:size] uniqueIdentifier];
|
|
else
|
|
return [self mapAddressForUri:uri size:size];
|
|
}
|
|
|
|
- (id)loadDataAsyncWithUri:(NSString *)uri progress:(void (^)(float))progress partialCompletion:(void (^)(TGDataResource *resource))__unused partialCompletion completion:(void (^)(TGDataResource *))completion
|
|
{
|
|
TGMediaPreviewTask *previewTask = [[TGMediaPreviewTask alloc] init];
|
|
|
|
NSDictionary *args = [TGStringUtils argumentDictionaryInUrlString:[uri substringFromIndex:@"map-thumbnail://?".length]];
|
|
bool isFlat = [args[@"flat"] boolValue];
|
|
|
|
[taskManagementQueue() dispatchOnQueue:^
|
|
{
|
|
TGWorkerTask *workerTask = [[TGWorkerTask alloc] initWithBlock:^(bool (^isCancelled)())
|
|
{
|
|
TGDataResource *result = [TGLocationThumbnailDataSource _performLoad:uri isCancelled:isCancelled];
|
|
|
|
if (result != nil && progress != nil)
|
|
progress(1.0f);
|
|
|
|
if (isCancelled != nil && isCancelled())
|
|
return;
|
|
|
|
if (completion != nil)
|
|
completion(result != nil ? result : [TGLocationThumbnailDataSource resultForUnavailableImage:isFlat]);
|
|
}];
|
|
|
|
if ([TGLocationThumbnailDataSource _isDataLocallyAvailableForUri:uri])
|
|
{
|
|
[previewTask executeWithWorkerTask:workerTask workerPool:workerPool()];
|
|
}
|
|
else
|
|
{
|
|
if (iosMajorVersion() >= 7)
|
|
{
|
|
CGSize size = CGSizeZero;
|
|
[previewTask executeWithMapSnapshotOptions:[TGLocationThumbnailDataSource snapshotOptionsForUri:uri size:&size] completionWithImage:^(UIImage *image)
|
|
{
|
|
if (image != nil)
|
|
{
|
|
TGWorkerTask *modernWorkerTask = [[TGWorkerTask alloc] initWithBlock:^(bool (^isCancelled)())
|
|
{
|
|
TGDataResource *result = [TGLocationThumbnailDataSource _performLoad:uri image:image size:size isCancelled:isCancelled];
|
|
|
|
if (result != nil && progress != nil)
|
|
progress(1.0f);
|
|
|
|
if (isCancelled != nil && isCancelled())
|
|
return;
|
|
|
|
if (completion != nil)
|
|
completion(result != nil ? result : [TGLocationThumbnailDataSource resultForUnavailableImage:isFlat]);
|
|
}];
|
|
|
|
[previewTask executeWithWorkerTask:modernWorkerTask workerPool:workerPool()];
|
|
}
|
|
else
|
|
{
|
|
if (completion != nil)
|
|
completion([TGLocationThumbnailDataSource resultForUnavailableImage:isFlat]);
|
|
}
|
|
} workerTask:workerTask];
|
|
}
|
|
else
|
|
{
|
|
[previewTask executeWithTargetFilePath:nil uri:[TGLocationThumbnailDataSource mapAddressForUri:uri size:NULL] completion:^(bool success)
|
|
{
|
|
if (success)
|
|
{
|
|
[previewTask executeWithWorkerTask:workerTask workerPool:workerPool()];
|
|
}
|
|
else
|
|
{
|
|
if (completion != nil)
|
|
completion([TGLocationThumbnailDataSource resultForUnavailableImage:isFlat]);
|
|
}
|
|
} workerTask:workerTask];
|
|
}
|
|
}
|
|
}];
|
|
|
|
return previewTask;
|
|
}
|
|
|
|
- (void)cancelTaskById:(id)taskId
|
|
{
|
|
[taskManagementQueue() dispatchOnQueue:^
|
|
{
|
|
if ([taskId isKindOfClass:[TGMediaPreviewTask class]])
|
|
{
|
|
TGMediaPreviewTask *previewTask = taskId;
|
|
[previewTask cancel];
|
|
}
|
|
}];
|
|
}
|
|
|
|
+ (TGDataResource *)resultForUnavailableImage:(bool)isFlat
|
|
{
|
|
static TGDataResource *normalData = nil;
|
|
static TGDataResource *flatData = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^
|
|
{
|
|
normalData = [[TGDataResource alloc] initWithImage:TGAverageColorAttachmentImage([UIColor whiteColor], true) decoded:true];
|
|
flatData = [[TGDataResource alloc] initWithImage:TGAverageColorAttachmentImage([UIColor whiteColor], false) decoded:true];
|
|
});
|
|
|
|
return isFlat ? flatData : normalData;
|
|
}
|
|
|
|
- (id)loadAttributeSyncForUri:(NSString *)uri attribute:(NSString *)attribute
|
|
{
|
|
if ([attribute isEqualToString:@"placeholder"])
|
|
{
|
|
NSDictionary *args = [TGStringUtils argumentDictionaryInUrlString:[uri substringFromIndex:@"map-thumbnail://?".length]];
|
|
bool isFlat = [args[@"flat"] boolValue];
|
|
|
|
static NSMutableDictionary *placeholderBySize = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^
|
|
{
|
|
placeholderBySize = [[NSMutableDictionary alloc] init];
|
|
});
|
|
|
|
CGSize size = CGSizeZero;
|
|
[TGLocationThumbnailDataSource mapAddressForUri:uri size:&size];
|
|
NSString *sizeString = [[NSString alloc] initWithFormat:@"%@-%@", NSStringFromCGSize(size), isFlat ? @"flat" : @"normal"];
|
|
UIImage *placeholder = placeholderBySize[sizeString];
|
|
if (placeholder != nil)
|
|
return placeholder;
|
|
|
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0f);
|
|
[TGAverageColorAttachmentImage([UIColor whiteColor], !isFlat) drawInRect:CGRectMake(0.0f, 0.0f, size.width, size.height) blendMode:kCGBlendModeCopy alpha:1.0f];
|
|
CGRect imageRect = CGRectMake(0.0f, 0.0f, size.width, size.height);
|
|
UIImage *pinImage = [UIImage imageNamed:@"ModernMessageLocationPin.png"];
|
|
[pinImage drawInRect:CGRectMake(imageRect.origin.x + CGFloor((imageRect.size.width - pinImage.size.width) / 2.0f) + 1.0f, imageRect.origin.y + CGFloor((imageRect.size.height - pinImage.size.height) / 2.0f) + 6, pinImage.size.width, pinImage.size.height)];
|
|
placeholder = UIGraphicsGetImageFromCurrentImageContext();
|
|
if (placeholder != nil)
|
|
placeholderBySize[sizeString] = placeholder;
|
|
UIGraphicsEndImageContext();
|
|
|
|
return placeholder;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (TGDataResource *)loadDataSyncWithUri:(NSString *)uri canWait:(bool)canWait acceptPartialData:(bool)__unused acceptPartialData asyncTaskId:(__autoreleasing id *)__unused asyncTaskId progress:(void (^)(float))__unused progress partialCompletion:(void (^)(TGDataResource *))__unused partialCompletion completion:(void (^)(TGDataResource *))__unused completion
|
|
{
|
|
if (uri == nil)
|
|
return nil;
|
|
|
|
UIImage *cachedImage = [[TGMediaStoreContext instance] mediaImage:uri attributes:nil];
|
|
if (cachedImage != nil)
|
|
return [[TGDataResource alloc] initWithImage:cachedImage decoded:true];
|
|
|
|
if (!canWait)
|
|
return nil;
|
|
|
|
return [TGLocationThumbnailDataSource _performLoad:uri isCancelled:nil];
|
|
}
|
|
|
|
+ (bool)_isDataLocallyAvailableForUri:(NSString *)uri
|
|
{
|
|
NSString *mapAddress = [self sourceMapIdentifierForUri:uri size:NULL];
|
|
return [[[TGMediaStoreContext instance] temporaryFilesCache] containsValueForKey:[mapAddress dataUsingEncoding:NSUTF8StringEncoding]];
|
|
}
|
|
|
|
+ (TGDataResource *)_performLoad:(NSString *)uri isCancelled:(bool (^)())isCancelled
|
|
{
|
|
if (isCancelled && isCancelled())
|
|
return nil;
|
|
|
|
CGSize size = CGSizeZero;
|
|
NSString *imageUrl = [TGLocationThumbnailDataSource sourceMapIdentifierForUri:uri size:&size];
|
|
|
|
NSData *thumbnailSourceData = [[[TGMediaStoreContext instance] temporaryFilesCache] getValueForKey:[imageUrl dataUsingEncoding:NSUTF8StringEncoding]];
|
|
UIImage *image = [[UIImage alloc] initWithData:thumbnailSourceData];
|
|
|
|
return [self _performLoad:uri image:image size:size isCancelled:isCancelled];
|
|
}
|
|
|
|
+ (TGDataResource *)_performLoad:(NSString *)uri image:(UIImage *)image size:(CGSize)size isCancelled:(bool (^)())isCancelled
|
|
{
|
|
if (isCancelled && isCancelled())
|
|
return nil;
|
|
|
|
NSDictionary *args = [TGStringUtils argumentDictionaryInUrlString:[uri substringFromIndex:@"map-thumbnail://?".length]];
|
|
|
|
UIGraphicsBeginImageContextWithOptions(size, true, 0.0f);
|
|
|
|
CGRect imageRect = CGRectMake(0.0f, -12.0f, size.width + 1.0f, size.height + 24.0f);
|
|
[image drawInRect:imageRect blendMode:kCGBlendModeCopy alpha:1.0f];
|
|
|
|
UIImage *pinImage = [UIImage imageNamed:@"ModernMessageLocationPin.png"];
|
|
[pinImage drawInRect:CGRectMake(imageRect.origin.x + CGFloor((imageRect.size.width - pinImage.size.width) / 2.0f) + 1.0f, imageRect.origin.y + CGFloor((imageRect.size.height - pinImage.size.height) / 2.0f) + 6, pinImage.size.width, pinImage.size.height)];
|
|
|
|
image = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
|
|
bool isFlat = [args[@"flat"] boolValue];
|
|
|
|
if (image != nil)
|
|
{
|
|
UIImage *thumbnailImage = nil;
|
|
|
|
NSNumber *averageColor = [[TGMediaStoreContext instance] mediaImageAverageColor:uri];
|
|
bool needsAverageColor = averageColor == nil;
|
|
uint32_t averageColorValue = [averageColor intValue];
|
|
|
|
thumbnailImage = TGLoadedAttachmentImage(image, size, needsAverageColor ? &averageColorValue : NULL, !isFlat);
|
|
|
|
if (thumbnailImage != nil)
|
|
{
|
|
[[TGMediaStoreContext instance] setMediaImageAverageColorForKey:uri averageColor:@(averageColorValue)];
|
|
[[TGMediaStoreContext instance] setMediaImageForKey:uri image:thumbnailImage attributes:nil];
|
|
|
|
return [[TGDataResource alloc] initWithImage:thumbnailImage decoded:true];
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
@end
|