Files
2025-08-06 13:49:11 +08:00

400 lines
16 KiB
Objective-C

//
// ShareViewController.m
// PhotoShareExtension
//
// Created by Russell on 27/4/15.
//
//
#import "ImageCaptionViewController.h"
#import <ImageIO/ImageIO.h>
#import <SDWebImage/UIImageView+WebCache.h>
NSString *const kNixAppUrlScheme = @"nixplay";
NSString *const kNixAppUrlActionPhotoShare = @"photo_share";
NSString *const kNixAppUrlQueryKeyImageUrl = @"image_url";
NSString *const kNixAppUrlQueryKeyCaption = @"caption";
NSString *const kNixAppUrlQueryKeyFrameId = @"frame_id";
NSUInteger const kNixCaptionMaxLength = 160;
NSUInteger const kNixInputFieldCells = 0;
@interface ImageCaptionViewController ()
@property (nonatomic, strong) NSMutableArray *imageErrors;
@property (nonatomic, strong) NSArray *imageUrls;
@property (nonatomic, strong) NSMutableArray *captions;
@property (nonatomic, strong) NSNumber *selectedFrameId;
@property (nonatomic, strong) NSString *selectedFrameName;
@property (nonatomic, strong) IBOutlet UITableView *tableView;
@property (nonatomic, strong) NSIndexPath *activeCellIndexPath;
@property (nonatomic, assign) BOOL keyboardShown;
@property (nonatomic, assign) CGFloat keyboardOverlap;
- (void)loadExtensionItems;
- (void)validateImages;
- (void)loadImagesIntoCache;
@end
@interface FrameTableViewCell : UITableViewCell
@end
@interface ImageCaptionTableViewCell : UITableViewCell
@property (nonatomic, strong) NSError *error;
@property (nonatomic, strong) IBOutlet UIImageView *thumbnailView;
//@property (nonatomic, strong) IBOutlet UITextView *textView;
@property (nonatomic, strong) IBOutlet UITextView *textView;
@property (nonatomic, strong) IBOutlet UILabel *errorLabel;
@property (nonatomic, strong) IBOutlet UIActivityIndicatorView *activityIndicatorView;
@end
@implementation ImageCaptionViewController
- (ContainerViewController*)containerViewController {
if (_containerViewController == nil) {
_containerViewController = [[ContainerViewController alloc] init];
}
return _containerViewController;
}
#pragma mark - IB
- (IBAction)cancel:(id)sender {
[self.containerViewController cancel];
}
#pragma mark - UIViewController
- (void)loadView {
[super loadView];
[self loadExtensionItems];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationItem.rightBarButtonItem.enabled = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
[self verifyUser];
}
-(void)appWillBecomeActive:(NSNotification*)note {
NSLog(@"appWillBecomeActive: %@", note);
// re-validate again if the user is active logged-in
[self verifyUser];
}
-(void)appWillResignActive:(NSNotification*)note {
NSLog(@"appWillResignActive: %@", note);
}
-(void)appWillTerminate:(NSNotification*)note {
NSLog(@"appWillTerminate: %@", note);
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
/*
* This works around a quirk on iOS version < 8.3 where the contents of the table do not load (after the VC has been
* loaded once) on subsequent loads until the user scrolls the tableview.
*/
[self.tableView reloadData];
[self loadImagesIntoCache];
// [self.containerViewController verifyServiceAvailableWithCompletionHandler:^(BOOL success) {
// dispatch_async(dispatch_get_main_queue(), ^{
// self.navigationItem.rightBarButtonItem.enabled = success;
// });
// }]; // must be in viewWillAppear (doesn't work in viewDidAppear)
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"pushToPlaylistsTableView"]) {
[self.view endEditing:YES];
ReceiverTableViewController *playlistTableVC = (ReceiverTableViewController *)segue.destinationViewController;
playlistTableVC.containerViewController = self.containerViewController;
self.containerViewController.imageErrors = [self.imageErrors copy];
self.containerViewController.imageUrls = [self.imageUrls copy];
self.containerViewController.captions = [self.captions copy];
}
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:@"\n"])
{
[textView resignFirstResponder];
return NO;
}
if ([text isEqualToString:@""])
return YES;
return textView.text.length < kNixCaptionMaxLength;
}
- (void)textViewDidEndEditing:(UITextView *)textView {
NSUInteger index = textView.tag;
self.captions[index] = textView.text;
self.activeCellIndexPath = nil;
}
- (void)textViewDidBeginEditing:(UITextView *)textView {
self.activeCellIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.imageUrls count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
NSUInteger imageIndex = indexPath.row;
cell = [tableView dequeueReusableCellWithIdentifier:@"ImageCaptionCell"
forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
NSURL *imageUrl = self.imageUrls[indexPath.row];
ImageCaptionTableViewCell *imageCaptionCell = (ImageCaptionTableViewCell *)cell;
imageCaptionCell.thumbnailView.contentMode = UIViewContentModeScaleAspectFit;
imageCaptionCell.thumbnailView.image = [[SDImageCache sharedImageCache]
imageFromMemoryCacheForKey:[imageUrl absoluteString]];
if (imageCaptionCell.thumbnailView.image) {
[imageCaptionCell.activityIndicatorView stopAnimating];
}
imageCaptionCell.textView.tag = imageIndex;
imageCaptionCell.textView.delegate = self;
NSError *imageError;
if (imageIndex < self.imageErrors.count) {
imageError = self.imageErrors[imageIndex];
}
imageCaptionCell.errorLabel.hidden = YES;
imageCaptionCell.textView.hidden = YES;
if (![imageError isKindOfClass:[NSNull class]]) {
imageCaptionCell.errorLabel.hidden = NO;
imageCaptionCell.errorLabel.text = [imageError localizedDescription];
} else {
imageCaptionCell.textView.hidden = NO;
imageCaptionCell.textView.layer.borderWidth = 0.5f;
imageCaptionCell.textView.layer.borderColor = [[UIColor lightGrayColor] CGColor];
imageCaptionCell.textView.layer.cornerRadius = 2.0f;
imageCaptionCell.textView.textContainer.lineBreakMode = NSLineBreakByWordWrapping;
}
NSString *caption = [self.captions objectAtIndex:imageIndex];
if ([caption isKindOfClass:[NSString class]])
imageCaptionCell.textView.text = caption;
else
imageCaptionCell.textView.text = @"";
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath{
// @try{
// UIImage *image = [[SDImageCache sharedImageCache]
// imageFromMemoryCacheForKey:[self.imageUrls[indexPath.row] absoluteString]];
// CGFloat height = image.size.height > image.size.width ? image.size.height : 256;
// return height > 0 ? height : 256;
// }
// @catch(NSException * exception){
// NSLog(@"estimatedHeightForRowAtIndexPath Exception %@",exception);
// }
return 300;// UITableViewAutomaticDimension;
}
#pragma mark - Private
#pragma mark App flow
- (void)loadExtensionItems {
NSExtensionItem *extensionItem = self.containerViewController.extensionContext.inputItems.firstObject;
NSArray *inputItems = extensionItem.attachments;
NSMutableArray *mutableImageUrls = [[NSMutableArray alloc] init];
dispatch_group_t group = dispatch_group_create();
// Load images from selection
[inputItems enumerateObjectsUsingBlock:^(NSItemProvider *itemProvider, NSUInteger idx, BOOL *stop) {
dispatch_group_enter(group);
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage
options:nil
completionHandler: ^(NSURL *imageUrl, NSError *error) {
[mutableImageUrls addObject:imageUrl];
dispatch_group_leave(group);
}];
} else {
dispatch_group_leave(group);
}
}];
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.imageUrls = [NSArray arrayWithArray:mutableImageUrls];
NSUInteger imagesCount = [self.imageUrls count];
self.imageErrors = [NSMutableArray arrayWithCapacity:imagesCount];
self.captions = [[NSMutableArray alloc] initWithCapacity:imagesCount];
for (NSUInteger i = 0; i < imagesCount; i++) {
[self.captions addObject:[NSNull null]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self validateImages];
[self.tableView reloadData];
});
});
}
-(BOOL) isOptimizeResolution {
NSUserDefaults *preferences = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.creedon.Nixplay"] ;
NSString *optimizeResolutionKey = @"nixSettings.settings.resolution";
if ([preferences objectForKey:optimizeResolutionKey] == nil)
{
NSLog(@"%@ not exist",optimizeResolutionKey);
return false;
}
else
{
// Get current level
const Boolean optimizeResolution = [preferences boolForKey:optimizeResolutionKey];
NSLog(@"currentLevel %i",optimizeResolution);
return optimizeResolution;
}
return false;
}
- (void)validateImages {
NSFileManager *fm = [NSFileManager defaultManager];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
for (NSUInteger i = 0; i < self.imageUrls.count; i++) {
@autoreleasepool {
NSURL *imageUrl = self.imageUrls[i];
NSDictionary *fileAttrs = [fm attributesOfItemAtPath:[imageUrl path] error:nil];
NSNumber *fileSizeLimit = self.containerViewController.uploadHandler.fileSizeLimit;
NSNumber *fileSize = [fileAttrs objectForKey:NSFileSize];
NSError *fileError;
//https://www.wrike.com/open.htm?id=190326881
//allow proess photo when isOptimizeResolution
if(![self isOptimizeResolution]){
if ([fileSize longLongValue] > [fileSizeLimit longLongValue]) {
fileError = [NSError errorWithDomain:bundleIdentifier
code:kNixIoFileTooLargeError
userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"selection_error_file_too_large", nil)
}];
}
}
if (fileError) {
[self.imageErrors addObject:fileError];
} else {
[self.imageErrors addObject:[NSNull null]];
}
}
}
}
- (void)loadImagesIntoCache {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
SDImageCache *imageCache = [SDImageCache sharedImageCache];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat screenScale = [[UIScreen mainScreen] scale];
NSNumber *maxWidth = [NSNumber numberWithFloat:screenBounds.size.width * screenScale * 0.75];
// Create thumbnail and save to cache
for (NSUInteger i = 0; i < self.imageUrls.count; i++) {
@autoreleasepool {
NSURL *imageUrl = self.imageUrls[i];
if (![imageCache imageFromMemoryCacheForKey:[imageUrl absoluteString]]) {
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, nil);
NSDictionary *options = @{
(id)kCGImageSourceCreateThumbnailWithTransform: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailFromImageIfAbsent: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailFromImageAlways: (id)kCFBooleanTrue,
(id)kCGImageSourceThumbnailMaxPixelSize: (id)maxWidth
};
CGImageRef thumbnailRef = CGImageSourceCreateThumbnailAtIndex(imageSource,
0,
(__bridge CFDictionaryRef)options);
UIImage *scaledImage = [UIImage imageWithCGImage:thumbnailRef];
[imageCache storeImage:scaledImage forKey:[imageUrl absoluteString] completion:^{
}];
}
}
}
// Update visible cells with images
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *visibleIndexPaths = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *visibleIndexPath in visibleIndexPaths) {
@autoreleasepool {
NSUInteger i = visibleIndexPath.row;
NSURL *imageUrl = self.imageUrls[i];
// Set up NSError to insert into cell
dispatch_async(dispatch_get_main_queue(), ^{
ImageCaptionTableViewCell *cell = (ImageCaptionTableViewCell *)[self.tableView
cellForRowAtIndexPath:visibleIndexPath];
if (!cell.thumbnailView.image) {
cell.thumbnailView.alpha = 0.f;
cell.thumbnailView.image = [imageCache imageFromMemoryCacheForKey:[imageUrl absoluteString]];
[UIView animateWithDuration:0.35f animations:^{
cell.thumbnailView.alpha = 1.f;
}];
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:@[visibleIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
[cell.activityIndicatorView stopAnimating];
});
}
}
});
});
}
#pragma mark - User
- (void) verifyUser {
[self.containerViewController verifyServiceAvailableWithCompletionHandler:^(BOOL success) {
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationItem.rightBarButtonItem.enabled = success;
});
}]; // must be in viewWillAppear (doesn't work in viewDidAppear)
}
@end
#pragma mark -
@implementation FrameTableViewCell
@end
@implementation ImageCaptionTableViewCell
@end