mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
399 lines
17 KiB
Objective-C
399 lines
17 KiB
Objective-C
#import <LegacyComponents/TGMediaAssetsUtils.h>
|
|
|
|
#import "LegacyComponentsInternal.h"
|
|
#import "TGStringUtils.h"
|
|
#import "TGDateUtils.h"
|
|
|
|
#import <LegacyComponents/UICollectionView+Utils.h>
|
|
|
|
#import <LegacyComponents/TGMediaSelectionContext.h>
|
|
|
|
#import <LegacyComponents/TGMediaAssetsLibrary.h>
|
|
#import <LegacyComponents/TGProgressWindow.h>
|
|
|
|
@interface TGMediaAssetsPreheatMixin ()
|
|
{
|
|
UICollectionView *_collectionView;
|
|
UICollectionViewScrollDirection _scrollDirection;
|
|
|
|
CGRect _previousPreheatRect;
|
|
}
|
|
@end
|
|
|
|
@implementation TGMediaAssetsPreheatMixin
|
|
|
|
- (instancetype)initWithCollectionView:(UICollectionView *)collectionView scrollDirection:(UICollectionViewScrollDirection)scrollDirection
|
|
{
|
|
self = [super init];
|
|
if (self != nil)
|
|
{
|
|
_collectionView = collectionView;
|
|
_scrollDirection = scrollDirection;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)update
|
|
{
|
|
CGRect preheatRect = _collectionView.bounds;
|
|
CGFloat delta = 0.0f;
|
|
CGFloat threshold = 0.0f;
|
|
switch (_scrollDirection)
|
|
{
|
|
case UICollectionViewScrollDirectionHorizontal:
|
|
preheatRect = CGRectInset(preheatRect, -0.5f * preheatRect.size.width, 0.0f);
|
|
delta = (CGFloat)(fabs(CGRectGetMidX(preheatRect) - CGRectGetMidX(_previousPreheatRect)));
|
|
threshold = _collectionView.bounds.size.width / 3.0f;
|
|
break;
|
|
|
|
case UICollectionViewScrollDirectionVertical:
|
|
preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * preheatRect.size.height);
|
|
delta = (CGFloat)(fabs(CGRectGetMidY(preheatRect) - CGRectGetMidY(_previousPreheatRect)));
|
|
threshold = _collectionView.bounds.size.height / 3.0f;
|
|
break;
|
|
}
|
|
|
|
if (delta > threshold)
|
|
{
|
|
NSMutableArray *addedIndexPaths = [[NSMutableArray alloc] init];
|
|
NSMutableArray *removedIndexPaths = [[NSMutableArray alloc] init];
|
|
|
|
__weak TGMediaAssetsPreheatMixin *weakSelf = self;
|
|
[_collectionView computeDifferenceBetweenRect:_previousPreheatRect andRect:preheatRect direction:_scrollDirection removedHandler:^(CGRect removedRect)
|
|
{
|
|
__strong TGMediaAssetsPreheatMixin *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
NSArray *indexPaths = [strongSelf->_collectionView indexPathsForElementsInRect:removedRect];
|
|
[removedIndexPaths addObjectsFromArray:indexPaths];
|
|
} addedHandler:^(CGRect addedRect)
|
|
{
|
|
__strong TGMediaAssetsPreheatMixin *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
NSArray *indexPaths = [strongSelf->_collectionView indexPathsForElementsInRect:addedRect];
|
|
[addedIndexPaths addObjectsFromArray:indexPaths];
|
|
}];
|
|
|
|
NSArray *assetsToCache = [self _assetsAtIndexPaths:addedIndexPaths];
|
|
NSArray *assetsToInvalidate = [self _assetsAtIndexPaths:removedIndexPaths];
|
|
|
|
CGSize imageSize = self.imageSize;
|
|
[TGMediaAssetImageSignals startCachingImagesForAssets:assetsToCache imageType:self.imageType size:imageSize];
|
|
[TGMediaAssetImageSignals stopCachingImagesForAssets:assetsToInvalidate imageType:self.imageType size:imageSize];
|
|
|
|
_previousPreheatRect = preheatRect;
|
|
}
|
|
}
|
|
|
|
- (void)stop
|
|
{
|
|
[TGMediaAssetImageSignals stopCachingImagesForAllAssets];
|
|
_previousPreheatRect = CGRectZero;
|
|
}
|
|
|
|
- (NSArray *)_assetsAtIndexPaths:(NSArray *)indexPaths
|
|
{
|
|
if (indexPaths.count == 0)
|
|
return nil;
|
|
|
|
NSInteger assetCount = self.assetCount();
|
|
|
|
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
|
|
for (NSIndexPath *indexPath in indexPaths)
|
|
{
|
|
NSInteger index = indexPath.row;
|
|
|
|
if (index < assetCount)
|
|
[assets addObject:self.assetAtIndexPath(indexPath)];
|
|
}
|
|
|
|
return assets;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGMediaAssetsCollectionViewIncrementalUpdater
|
|
|
|
+ (void)updateCollectionView:(UICollectionView *)collectionView withChange:(TGMediaAssetFetchResultChange *)__unused change completion:(void (^)(bool incremental))completion
|
|
{
|
|
[collectionView reloadData];
|
|
if (completion != nil)
|
|
completion(false);
|
|
|
|
if (true)
|
|
return;
|
|
|
|
/*if (!change.hasIncrementalChanges)
|
|
{
|
|
[collectionView reloadData];
|
|
if (completion != nil)
|
|
completion(false);
|
|
return;
|
|
}
|
|
|
|
NSMutableArray *removedIndexPaths = [[NSMutableArray alloc] init];
|
|
[change.removedIndexes enumerateIndexesUsingBlock:^(NSUInteger index, __unused BOOL *stop)
|
|
{
|
|
[removedIndexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
|
|
}];
|
|
|
|
NSMutableArray *insertedIndexPaths = [[NSMutableArray alloc] init];
|
|
[change.insertedIndexes enumerateIndexesUsingBlock:^(NSUInteger index, __unused BOOL *stop)
|
|
{
|
|
[insertedIndexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
|
|
}];
|
|
|
|
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] init];
|
|
[change.updatedIndexes enumerateIndexesUsingBlock:^(NSUInteger index, __unused BOOL *stop)
|
|
{
|
|
[updatedIndexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
|
|
}];
|
|
|
|
[collectionView performBatchUpdates:^
|
|
{
|
|
[collectionView deleteItemsAtIndexPaths:removedIndexPaths];
|
|
[collectionView insertItemsAtIndexPaths:insertedIndexPaths];
|
|
} completion:^(__unused BOOL finished)
|
|
{
|
|
if (updatedIndexPaths.count > 0 || change.hasMoves)
|
|
{
|
|
[collectionView performBatchUpdates:^
|
|
{
|
|
[collectionView reloadItemsAtIndexPaths:updatedIndexPaths];
|
|
|
|
[change enumerateMovesWithBlock:^(NSUInteger fromIndex, NSUInteger toIndex)
|
|
{
|
|
NSIndexPath *fromIndexPath = [NSIndexPath indexPathForRow:fromIndex inSection:0];
|
|
NSIndexPath *toIndexPath = [NSIndexPath indexPathForRow:toIndex inSection:0];
|
|
|
|
[collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
|
|
}];
|
|
} completion:^(__unused BOOL finished)
|
|
{
|
|
if (completion != nil)
|
|
completion(true);
|
|
}];
|
|
}
|
|
else if (completion != nil)
|
|
{
|
|
completion(true);
|
|
}
|
|
}];*/
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGMediaAssetsSaveToCameraRoll
|
|
|
|
+ (void)saveImageAtURL:(NSURL *)url
|
|
{
|
|
if (![[[LegacyComponentsGlobals provider] accessChecker] checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentSave alertDismissCompletion:nil])
|
|
return;
|
|
|
|
TGProgressWindow *progressWindow = [[TGProgressWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
[progressWindow show:true];
|
|
|
|
[[[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImageAtUrl:url] deliverOn:[SQueue mainQueue]] startWithNext:nil error:^(__unused id error)
|
|
{
|
|
[[[LegacyComponentsGlobals provider] accessChecker] checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentSave alertDismissCompletion:nil];
|
|
[progressWindow dismiss:true];
|
|
} completed:^
|
|
{
|
|
[progressWindow dismissWithSuccess];
|
|
}];
|
|
}
|
|
|
|
+ (void)saveImageWithData:(NSData *)imageData silentlyFail:(bool)silentlyFail completionBlock:(void (^)(bool))completionBlock
|
|
{
|
|
TGProgressWindow *progressWindow = nil;
|
|
if (!silentlyFail)
|
|
{
|
|
if (![[[LegacyComponentsGlobals provider] accessChecker] checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentSave alertDismissCompletion:nil])
|
|
return;
|
|
|
|
progressWindow = [[TGProgressWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
[progressWindow show:true];
|
|
}
|
|
|
|
[[[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImageData:imageData] deliverOn:[SQueue mainQueue]] startWithNext:nil error:^(__unused id error)
|
|
{
|
|
if (!silentlyFail)
|
|
{
|
|
[[[LegacyComponentsGlobals provider] accessChecker] checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentSave alertDismissCompletion:nil];
|
|
[progressWindow dismiss:true];
|
|
}
|
|
|
|
if (completionBlock != nil)
|
|
completionBlock(false);
|
|
} completed:^
|
|
{
|
|
if (!silentlyFail)
|
|
[progressWindow dismissWithSuccess];
|
|
|
|
if (completionBlock != nil)
|
|
completionBlock(true);
|
|
}];
|
|
}
|
|
|
|
+ (void)saveVideoAtURL:(NSURL *)url
|
|
{
|
|
if (![[[LegacyComponentsGlobals provider] accessChecker] checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentSave alertDismissCompletion:nil])
|
|
return;
|
|
|
|
TGProgressWindow *progressWindow = [[TGProgressWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
[progressWindow show:true];
|
|
|
|
[[[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithVideoAtUrl:url] deliverOn:[SQueue mainQueue]] startWithNext:nil error:^(__unused id error)
|
|
{
|
|
[[[LegacyComponentsGlobals provider] accessChecker] checkPhotoAuthorizationStatusForIntent:TGPhotoAccessIntentSave alertDismissCompletion:nil];
|
|
[progressWindow dismiss:true];
|
|
} completed:^
|
|
{
|
|
[progressWindow dismissWithSuccess];
|
|
}];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGMediaAssetsDateUtils
|
|
|
|
static bool TGMediaAssetsDateUtilsInitialized = false;
|
|
|
|
static NSString *value_dateFormat = nil;
|
|
static NSString *value_dateYearFormat = nil;
|
|
|
|
static NSString *value_dateRangeFormat = nil;
|
|
static NSString *value_dateRangeYearFormat = nil;
|
|
static NSString *value_dateRangeSameMonthFormat = nil;
|
|
static NSString *value_dateRangeSameMonthYearFormat = nil;
|
|
|
|
static void initializeTGMediaAssetsDateUtils()
|
|
{
|
|
TGMediaAssetsDateUtilsInitialized = true;
|
|
|
|
value_dateFormat = [[TGLocalized(@"MediaPicker.MomentsDateFormat") stringByReplacingOccurrencesOfString:@"{month}" withString:@"%2$@"] stringByReplacingOccurrencesOfString:@"{day}" withString:@"%1$d"];
|
|
value_dateYearFormat = [[[TGLocalized(@"MediaPicker.MomentsDateYearFormat") stringByReplacingOccurrencesOfString:@"{month}" withString:@"%2$@"] stringByReplacingOccurrencesOfString:@"{day}" withString:@"%1$d"] stringByReplacingOccurrencesOfString:@"{year}" withString:@"%3$d"];
|
|
value_dateRangeFormat = [[TGLocalized(@"MediaPicker.MomentsDateRangeFormat") stringByReplacingOccurrencesOfString:@"{date1}" withString:@"%1$@"] stringByReplacingOccurrencesOfString:@"{date2}" withString:@"%2$@"];
|
|
value_dateRangeYearFormat = [[[TGLocalized(@"MediaPicker.MomentsDateRangeYearFormat") stringByReplacingOccurrencesOfString:@"{date1}" withString:@"%1$@"] stringByReplacingOccurrencesOfString:@"{date2}" withString:@"%2$@"] stringByReplacingOccurrencesOfString:@"{year}" withString:@"%3$@"];
|
|
value_dateRangeSameMonthFormat = [[[TGLocalized(@"MediaPicker.MomentsDateRangeSameMonthFormat") stringByReplacingOccurrencesOfString:@"{day1}" withString:@"%1$d"] stringByReplacingOccurrencesOfString:@"{day2}" withString:@"%2$d"] stringByReplacingOccurrencesOfString:@"{month}" withString:@"%3$@"];
|
|
value_dateRangeSameMonthYearFormat = [[[[TGLocalized(@"MediaPicker.MomentsDateRangeSameMonthYearFormat") stringByReplacingOccurrencesOfString:@"{date1}" withString:@"%1$d"] stringByReplacingOccurrencesOfString:@"{date2}" withString:@"%2$d"] stringByReplacingOccurrencesOfString:@"{month}" withString:@"%3$@"] stringByReplacingOccurrencesOfString:@"{year}" withString:@"%4$@"];
|
|
}
|
|
|
|
+ (NSString *)formattedDateRangeWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate currentDate:(NSDate *)currentDate shortDate:(bool)shortDate
|
|
{
|
|
if (!TGMediaAssetsDateUtilsInitialized)
|
|
initializeTGMediaAssetsDateUtils();
|
|
|
|
if (endDate == nil)
|
|
endDate = startDate;
|
|
|
|
time_t t_start = (long)startDate.timeIntervalSince1970;
|
|
struct tm timeinfo_start;
|
|
localtime_r(&t_start, &timeinfo_start);
|
|
|
|
time_t t_end = (long)endDate.timeIntervalSince1970;
|
|
struct tm timeinfo_end;
|
|
localtime_r(&t_end, &timeinfo_end);
|
|
|
|
time_t t_now = (long)currentDate.timeIntervalSince1970;
|
|
struct tm timeinfo_now;
|
|
localtime_r(&t_now, &timeinfo_now);
|
|
|
|
int yearOffset = 2000 - 100;
|
|
|
|
static NSString *(^monthString)(int, bool) = ^(int number, bool shortDate)
|
|
{
|
|
if (shortDate)
|
|
return TGMonthNameShort(number);
|
|
else
|
|
return TGMonthNameFull(number);
|
|
};
|
|
|
|
if (timeinfo_end.tm_year != timeinfo_now.tm_year)
|
|
{
|
|
if (timeinfo_start.tm_year == timeinfo_end.tm_year)
|
|
{
|
|
if (timeinfo_start.tm_mon == timeinfo_end.tm_mon)
|
|
{
|
|
if (timeinfo_start.tm_yday == timeinfo_end.tm_yday)
|
|
{
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateYearFormat, timeinfo_start.tm_mday, monthString(timeinfo_start.tm_mon, shortDate), timeinfo_start.tm_year + yearOffset]];
|
|
}
|
|
else
|
|
{
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateRangeSameMonthYearFormat, timeinfo_start.tm_mday, timeinfo_end.tm_mday, monthString(timeinfo_start.tm_mon, shortDate), timeinfo_start.tm_year + yearOffset]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSString *startDateString = [NSString stringWithFormat:value_dateFormat, timeinfo_start.tm_mday, monthString(timeinfo_start.tm_mon, shortDate)];
|
|
NSString *endDateString = [NSString stringWithFormat:value_dateFormat, timeinfo_end.tm_mday, monthString(timeinfo_end.tm_mon, shortDate)];
|
|
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateRangeYearFormat, startDateString, endDateString, timeinfo_start.tm_year + yearOffset]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSString *startDateString = [NSString stringWithFormat:value_dateYearFormat, timeinfo_start.tm_mday, monthString(timeinfo_start.tm_mon, shortDate), timeinfo_start.tm_year + yearOffset];
|
|
NSString *endDateString = [NSString stringWithFormat:value_dateYearFormat, timeinfo_end.tm_mday, monthString(timeinfo_end.tm_mon, shortDate), timeinfo_end.tm_year + yearOffset];
|
|
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateRangeFormat, startDateString, endDateString]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int dayDiff = timeinfo_start.tm_yday - timeinfo_now.tm_yday;
|
|
|
|
if (dayDiff < -7)
|
|
{
|
|
if (timeinfo_start.tm_year == timeinfo_end.tm_year)
|
|
{
|
|
if (timeinfo_start.tm_mon == timeinfo_end.tm_mon)
|
|
{
|
|
if (timeinfo_start.tm_yday == timeinfo_end.tm_yday)
|
|
{
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateFormat, timeinfo_start.tm_mday, monthString(timeinfo_start.tm_mon, shortDate)]];
|
|
}
|
|
else
|
|
{
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateRangeSameMonthFormat, timeinfo_start.tm_mday, timeinfo_end.tm_mday, monthString(timeinfo_start.tm_mon, shortDate)]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSString *startDateString = [NSString stringWithFormat:value_dateFormat, timeinfo_start.tm_mday, monthString(timeinfo_start.tm_mon, shortDate)];
|
|
NSString *endDateString = [NSString stringWithFormat:value_dateFormat, timeinfo_end.tm_mday, monthString(timeinfo_end.tm_mon, shortDate)];
|
|
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateRangeFormat, startDateString, endDateString]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSString *startDateString = [NSString stringWithFormat:value_dateYearFormat, timeinfo_start.tm_mday, monthString(timeinfo_start.tm_mon, shortDate), timeinfo_start.tm_year + yearOffset];
|
|
NSString *endDateString = [NSString stringWithFormat:value_dateYearFormat, timeinfo_end.tm_mday, monthString(timeinfo_end.tm_mon, shortDate), timeinfo_end.tm_year + yearOffset];
|
|
|
|
return [TGStringUtils stringWithLocalizedNumberCharacters:[NSString stringWithFormat:value_dateRangeFormat, startDateString, endDateString]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dayDiff == 0)
|
|
return TGLocalized(@"Weekday.Today");
|
|
else if (dayDiff == -1)
|
|
return TGLocalized(@"Weekday.Yesterday");
|
|
if (dayDiff > -7 && dayDiff <= -2)
|
|
return TGWeekdayNameFull(timeinfo_start.tm_wday);
|
|
}
|
|
}
|
|
|
|
return @"";
|
|
}
|
|
|
|
@end
|