completion
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// ContainerViewController.h
|
||||
// PhotoShareExtension
|
||||
//
|
||||
// Created by Russell on 27/5/15.
|
||||
//
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <NixNetwork/NixUpload.h>
|
||||
#import <SDWebImage/SDImageCache.h>
|
||||
@interface ContainerViewController : UIViewController <NixUploadDelegate>
|
||||
@property (nonatomic, strong) NixAPI *api;
|
||||
@property (nonatomic, strong) NixUpload *uploadHandler;
|
||||
|
||||
@property (nonatomic, strong) NSArray *imageErrors;
|
||||
@property (nonatomic, strong) NSArray *imageUrls;
|
||||
@property (nonatomic, strong) NSArray *captions;
|
||||
@property (nonatomic, strong) NSArray *playlistIds;
|
||||
@property (nonatomic, strong) NSArray *friendUsernames;
|
||||
|
||||
@property (nonatomic, strong) NSArray *playlistData;
|
||||
@property (nonatomic, strong) NSArray *friendData;
|
||||
@property (nonatomic, strong) NSArray *selectedReceiverIndexPaths;
|
||||
|
||||
- (void)verifyServiceAvailable;
|
||||
- (void)verifyServiceAvailableWithCompletionHandler:(void (^)(BOOL))completionHandler;
|
||||
- (void)pingWithCompletionHandler:(void (^)(NSError *))completionHandler;
|
||||
- (void)send;
|
||||
- (void)cancel;
|
||||
@end
|
||||
@@ -0,0 +1,368 @@
|
||||
//
|
||||
// ContainerViewController.m
|
||||
// PhotoShareExtension
|
||||
//
|
||||
// Created by Russell on 27/5/15.
|
||||
//
|
||||
//
|
||||
|
||||
#import "ContainerViewController.h"
|
||||
#import "ImageCaptionViewController.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
|
||||
@interface ContainerViewController ()
|
||||
@property (assign) ImageCaptionViewController *svc;
|
||||
- (BOOL)tokenExists;
|
||||
- (void)handleUploadError:(NSError *)error;
|
||||
@end
|
||||
|
||||
@implementation ContainerViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
[self performSegueWithIdentifier:@"InitialSegue" sender:nil];
|
||||
|
||||
self.api = [[NixAPI alloc] init];
|
||||
self.uploadHandler = [[NixUpload alloc] initWithAPI:self.api];
|
||||
self.uploadHandler.delegate = self;
|
||||
self.uploadHandler.uploadSessionIdentifier = @"com.creedon.Nixplay.uploadsession.appext";
|
||||
[self.uploadHandler createSession]; // rejoin the session
|
||||
[self.uploadHandler loadUploadState];
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
#pragma mark - Navigation
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
// Get the new view controller using [segue destinationViewController].
|
||||
// Pass the selected object to the new view controller.
|
||||
self.svc = ((UINavigationController *)segue.destinationViewController)
|
||||
.childViewControllers[0];
|
||||
self.svc.containerViewController = self;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)verifyServiceAvailable {
|
||||
[self verifyServiceAvailableWithCompletionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)verifyServiceAvailableWithCompletionHandler:(void (^)(BOOL))completionHandler {
|
||||
|
||||
[self.uploadHandler getCurrentTasks:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
|
||||
NSLog(@"uploadTasks: %@", uploadTasks);
|
||||
}];
|
||||
|
||||
|
||||
if (!self.api.apiURL.length) {
|
||||
|
||||
[self showAlertViewWithTitle:NSLocalizedString(@"app_never_launched_title", nil)
|
||||
message:NSLocalizedString(@"app_never_launched_message", nil)
|
||||
actionWithTitle:NSLocalizedString(@"close", nil)
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self cancel];
|
||||
}];
|
||||
completionHandler(NO);
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self tokenExists]) {
|
||||
[self showAlertViewWithTitle:NSLocalizedString(@"no_account_title", nil)
|
||||
message:NSLocalizedString(@"no_account_message", nil)
|
||||
actionWithTitle:NSLocalizedString(@"close", nil)
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self cancel];
|
||||
}];
|
||||
completionHandler(NO);
|
||||
} else {
|
||||
[self pingWithCompletionHandler:^(NSError *error) {
|
||||
if (error) {
|
||||
if (error.code == 401) {
|
||||
|
||||
[self showAlertViewWithTitle:NSLocalizedString(@"signin_again_title", nil)
|
||||
message:NSLocalizedString(@"signin_again_message", nil)
|
||||
actionWithTitle:NSLocalizedString(@"close", nil)
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self cancel];
|
||||
}];
|
||||
} else {
|
||||
[self showAlertViewWithTitle:NSLocalizedString(@"server_connection_error_title", nil)
|
||||
message:NSLocalizedString(@"server_connection_error_message", nil)
|
||||
actionWithTitle:NSLocalizedString(@"close", nil)
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self cancel];
|
||||
}];
|
||||
}
|
||||
completionHandler(NO);
|
||||
} else {
|
||||
completionHandler(YES);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pingWithCompletionHandler:(void (^)(NSError *))completionHandler {
|
||||
[self.api ping:^(id data, NSURLResponse *response, NSError *error) {
|
||||
NSLog(@"pingError: %@", error);
|
||||
completionHandler(error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)send {
|
||||
|
||||
NSMutableIndexSet *imageErrorIndexes = [NSMutableIndexSet indexSet];
|
||||
for (NSUInteger i = 0; i < self.imageErrors.count; i++) {
|
||||
NSError *error = self.imageErrors[i];
|
||||
if ([error isKindOfClass:[NSError class]]) {
|
||||
[imageErrorIndexes addIndex:i];
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableArray *mutableImageUrls = [NSMutableArray arrayWithArray:self.imageUrls];
|
||||
NSMutableArray *mutableCaptions = [NSMutableArray arrayWithArray:self.captions];
|
||||
[mutableImageUrls removeObjectsAtIndexes:imageErrorIndexes];
|
||||
[mutableCaptions removeObjectsAtIndexes:imageErrorIndexes];
|
||||
|
||||
if (mutableImageUrls.count == 0) {
|
||||
NSError *error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier]
|
||||
code:0
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"upload_error_no_files_selected", nil)
|
||||
}];
|
||||
[self handleUploadError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//process image before upload
|
||||
//if user choose optimize photo size
|
||||
if( [self isOptimizeResolution ]){
|
||||
//optimize photo size
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
SDImageCache *imageCache = [SDImageCache sharedImageCache];
|
||||
//store images to /tmp
|
||||
NSString *defaultCachePath = NSTemporaryDirectory();
|
||||
|
||||
//nix opt. size
|
||||
float optimizedDimension = 1820.0f;
|
||||
NSNumber *maxWidth = [NSNumber numberWithFloat:optimizedDimension];
|
||||
|
||||
|
||||
for (NSUInteger i = 0; i < mutableImageUrls.count; i++) {
|
||||
@autoreleasepool {
|
||||
NSURL *imageUrl = mutableImageUrls[i];
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, nil);
|
||||
|
||||
// load the image at the desired size
|
||||
NSDictionary* options = @{
|
||||
(id)kCGImageSourceShouldAllowFloat: (id)kCFBooleanTrue,
|
||||
(id)kCGImageSourceCreateThumbnailWithTransform: (id)kCFBooleanTrue,
|
||||
(id)kCGImageSourceCreateThumbnailFromImageAlways: (id)kCFBooleanTrue,
|
||||
(id)kCGImageSourceThumbnailMaxPixelSize: maxWidth
|
||||
};
|
||||
|
||||
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
|
||||
|
||||
//80% jpg quality
|
||||
NSData * data = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.8f);
|
||||
|
||||
//retrieve cache path from Table view SDImageCache
|
||||
NSURL* cachePathURL = [[NSURL alloc] initWithString:[imageCache cachePathForKey:[imageUrl absoluteString]]] ;
|
||||
NSURL* newPathURL = [[NSURL alloc] initWithString:
|
||||
[NSString stringWithFormat:@"%@/%@.JPG",defaultCachePath, [cachePathURL lastPathComponent]]];
|
||||
|
||||
NSDictionary *metaoptions = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
|
||||
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)metaoptions);
|
||||
if (imageProperties) {
|
||||
NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
|
||||
NSMutableDictionary *mutableMetadata = [NSMutableDictionary dictionaryWithDictionary:metadata];
|
||||
[mutableMetadata setValue:[NSNumber numberWithInt:1] forKey:@"Orientation"];
|
||||
[self writeData: [self writeMetadataIntoImageData:data metadata:mutableMetadata ] toPath: [newPathURL absoluteString]];
|
||||
|
||||
}
|
||||
else{
|
||||
//wirte to file
|
||||
[self writeData: data toPath: [newPathURL absoluteString]];
|
||||
}
|
||||
//replace imageURLs object
|
||||
[mutableImageUrls replaceObjectAtIndex:i withObject:[newPathURL absoluteString] ];
|
||||
}
|
||||
}
|
||||
|
||||
[self.uploadHandler uploadWithImageUrls:[mutableImageUrls copy]
|
||||
captions:[mutableCaptions copy]
|
||||
playlists:self.playlistIds
|
||||
friends:self.friendUsernames
|
||||
camera:NO];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
else{
|
||||
[mutableImageUrls enumerateObjectsUsingBlock:^(NSURL* url, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[mutableImageUrls replaceObjectAtIndex:idx withObject:[url absoluteString]];
|
||||
}];
|
||||
[self.uploadHandler uploadWithImageUrls:[mutableImageUrls copy]
|
||||
captions:[mutableCaptions copy]
|
||||
playlists:self.playlistIds
|
||||
friends:self.friendUsernames
|
||||
camera:NO];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)cancel {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was cancelled.", nil),
|
||||
NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"Operation was cancelled.", nil),
|
||||
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Operation was cancelled.", nil)
|
||||
};
|
||||
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
|
||||
code:-57
|
||||
userInfo:userInfo];
|
||||
[self.extensionContext cancelRequestWithError:error];
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (BOOL)tokenExists {
|
||||
return [[self.api getAccessToken] length];
|
||||
}
|
||||
|
||||
- (void)handleUploadError:(NSError *)error {
|
||||
if (error) {
|
||||
UINavigationController *navigationController = (UINavigationController *)self.presentedViewController;
|
||||
ReceiverTableViewController *receiverTableVC = (ReceiverTableViewController *)navigationController.topViewController;
|
||||
if ([receiverTableVC isKindOfClass:[ReceiverTableViewController class]]) {
|
||||
[receiverTableVC hideActivityIndicator];
|
||||
receiverTableVC.navigationItem.rightBarButtonItem.enabled = YES;
|
||||
}
|
||||
|
||||
NSString *errorMessage = [error localizedDescription];
|
||||
switch (error.code) {
|
||||
case kNixIoFileTooLargeError:
|
||||
errorMessage = NSLocalizedString(@"upload_error_file_too_large", nil);
|
||||
break;
|
||||
}
|
||||
[self showAlertViewWithTitle:NSLocalizedString(@"upload_error_title", nil)
|
||||
message:errorMessage
|
||||
actionWithTitle:NSLocalizedString(@"close", nil)
|
||||
handler:^(UIAlertAction *action) {
|
||||
[self cancel];
|
||||
}];
|
||||
}
|
||||
|
||||
//delete cache image
|
||||
[self deleteCache];
|
||||
|
||||
}
|
||||
|
||||
-(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;
|
||||
}
|
||||
|
||||
- (BOOL)writeData:(NSData*) data toPath:(NSString *)path
|
||||
{
|
||||
NSError* errorWrite = nil;
|
||||
|
||||
BOOL write = [data writeToFile:path atomically:YES];
|
||||
if(errorWrite != NULL){
|
||||
NSLog(@"Failed to write to '%@': %@", path , [errorWrite localizedDescription]);
|
||||
}
|
||||
return write;
|
||||
}
|
||||
|
||||
-(void)deleteCache{
|
||||
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
|
||||
for (NSString *file in tmpDirectory) {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), file] error:NULL];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//http://stackoverflow.com/questions/9006759/how-to-write-exif-metadata-to-an-image-not-the-camera-roll-just-a-uiimage-or-j
|
||||
-(NSData *)writeMetadataIntoImageData:(NSData *)imageData metadata:(NSMutableDictionary *)metadata {
|
||||
// create an imagesourceref
|
||||
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
|
||||
|
||||
// this is the type of image (e.g., public.jpeg)
|
||||
CFStringRef UTI = CGImageSourceGetType(source);
|
||||
|
||||
// create a new data object and write the new image into it
|
||||
NSMutableData *dest_data = [NSMutableData data];
|
||||
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data, UTI, 1, NULL);
|
||||
if (!destination) {
|
||||
NSLog(@"Error: Could not create image destination");
|
||||
}
|
||||
// add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
|
||||
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) metadata);
|
||||
BOOL success = NO;
|
||||
success = CGImageDestinationFinalize(destination);
|
||||
if (!success) {
|
||||
NSLog(@"Error: Could not create data from image destination");
|
||||
}
|
||||
CFRelease(destination);
|
||||
CFRelease(source);
|
||||
return dest_data;
|
||||
}
|
||||
|
||||
#pragma mark - NixUploadDelegate
|
||||
- (void)didStartUploadWithTasks:(NSArray *)tasks {
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
[self.extensionContext completeRequestReturningItems:@[]
|
||||
completionHandler:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didUploadImageAtIndex:(NSUInteger)index error:(NSError *)error {
|
||||
[self handleUploadError:error];
|
||||
}
|
||||
|
||||
- (void)didFinishUploadingWithError:(NSError *)error {
|
||||
[self handleUploadError:error];
|
||||
|
||||
}
|
||||
|
||||
-(void) showAlertViewWithTitle:(NSString*)title message:(NSString*)message actionWithTitle:actiontitle handler:(void (^ __nullable)(UIAlertAction *action))handler{
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle:title
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
[self cancel];
|
||||
}];
|
||||
[alert addAction:defaultAction];
|
||||
|
||||
[self.svc presentViewController:alert animated:YES completion:nil];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// ShareViewController.h
|
||||
// PhotoShareExtension
|
||||
//
|
||||
// Created by Russell on 28/4/15.
|
||||
//
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Social/Social.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
#import "ReceiverTableViewController.h"
|
||||
#import "ContainerViewController.h"
|
||||
|
||||
FOUNDATION_EXPORT NSString *const kNixAppUrlScheme;
|
||||
|
||||
FOUNDATION_EXPORT NSString *const kNixAppUrlActionPhotoShare;
|
||||
typedef NSString *kNixAppUrlAction;
|
||||
|
||||
FOUNDATION_EXPORT NSString *const kNixAppUrlQueryKeyImageUrl;
|
||||
FOUNDATION_EXPORT NSString *const kNixAppUrlQueryKeyCaption;
|
||||
FOUNDATION_EXPORT NSString *const kNixAppUrlQueryKeyFrameId;
|
||||
typedef NSString *kNixAppUrlQueryKey;
|
||||
|
||||
FOUNDATION_EXPORT NSUInteger const kNixCaptionMaxLength;
|
||||
FOUNDATION_EXPORT NSUInteger const kNixInputFieldCells;
|
||||
|
||||
@interface ImageCaptionViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextViewDelegate>
|
||||
@property (nonatomic, strong) ContainerViewController *containerViewController;
|
||||
- (IBAction)cancel:(id)sender;
|
||||
@end
|
||||
@@ -0,0 +1,399 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// SLComposeTableViewController.h
|
||||
// PhotoShareExtension
|
||||
//
|
||||
// Created by Russell on 8/5/15.
|
||||
//
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ContainerViewController.h"
|
||||
|
||||
@interface ReceiverTableViewController : UITableViewController
|
||||
@property (nonatomic, strong) ContainerViewController *containerViewController;
|
||||
- (void)showActivityIndicator;
|
||||
- (void)hideActivityIndicator;
|
||||
@end
|
||||
@@ -0,0 +1,556 @@
|
||||
//
|
||||
// SLComposeTableViewController.m
|
||||
// PhotoShareExtension
|
||||
//
|
||||
// Created by Russell on 8/5/15.
|
||||
//
|
||||
//
|
||||
|
||||
#import "ReceiverTableViewController.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, NixTableViewSection) {
|
||||
kPlaylistHeader,
|
||||
kPlaylist,
|
||||
kFriendHeader,
|
||||
kFriend
|
||||
};
|
||||
|
||||
@interface ReceiverTableViewController ()
|
||||
@property (nonatomic, strong) NSArray *playlistValues;
|
||||
@property (nonatomic, strong) NSArray *friendValues;
|
||||
@property (nonatomic, strong) NSMutableArray *selectedPlaylists;
|
||||
@property (nonatomic, strong) NSMutableArray *selectedFriends;
|
||||
- (IBAction)send:(id)sender;
|
||||
- (void)preparePlaylistValues;
|
||||
- (void)prepareFriendValues;
|
||||
- (void)reloadData;
|
||||
|
||||
- (NSString *)headerTitleWithPlaylistCounts:(NSUInteger)playlists andFriendCounts:(NSUInteger)friends;
|
||||
- (void)updateHeaderTitleWithCounts;
|
||||
|
||||
- (void)loadAllData:(void (^)(NSError *))completionHandler;
|
||||
- (void)loadPlaylistsWithCompletionHandler:(void (^)(NSError *))completionHandler;
|
||||
- (void)loadFriendsWithCompletionHandler:(void (^)(NSError *))completionHandler;
|
||||
@end
|
||||
|
||||
@implementation ReceiverTableViewController
|
||||
|
||||
#pragma mark - IB
|
||||
- (IBAction)send:(id)sender {
|
||||
self.selectedPlaylists = [[NSMutableArray alloc]
|
||||
initWithCapacity:self.containerViewController.playlistData.count];
|
||||
self.selectedFriends = [[NSMutableArray alloc]
|
||||
initWithCapacity:self.containerViewController.friendData.count];
|
||||
|
||||
NSArray *selectedIndexPaths = [self.tableView indexPathsForSelectedRows];
|
||||
[selectedIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
@autoreleasepool {
|
||||
switch (indexPath.section) {
|
||||
case kPlaylist:
|
||||
{
|
||||
NSString *playlistId = self.containerViewController.playlistData[indexPath.row][@"id"];
|
||||
[self.selectedPlaylists addObject:playlistId];
|
||||
break;
|
||||
}
|
||||
case kFriend:
|
||||
{
|
||||
NSString *friendUsername = self.containerViewController.friendData[indexPath.row][@"username"];
|
||||
[self.selectedFriends addObject:friendUsername];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
self.containerViewController.playlistIds = [self.selectedPlaylists copy];
|
||||
self.containerViewController.friendUsernames = [self.selectedFriends copy];
|
||||
|
||||
[self showActivityIndicator];
|
||||
self.navigationItem.rightBarButtonItem.enabled = NO;
|
||||
self.navigationItem.hidesBackButton = YES;
|
||||
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
|
||||
|
||||
[self.containerViewController send];
|
||||
}
|
||||
|
||||
#pragma mark - Navigation
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// Uncomment the following line to preserve selection between presentations.
|
||||
// self.clearsSelectionOnViewWillAppear = NO;
|
||||
|
||||
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
|
||||
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
|
||||
|
||||
self.tableView.editing = YES;
|
||||
|
||||
self.refreshControl = [[UIRefreshControl alloc] init];
|
||||
[self.refreshControl addTarget:self action:@selector(reloadData) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
if (!self.containerViewController.playlistData || !self.containerViewController.friendData) {
|
||||
[self showActivityIndicator];
|
||||
[self loadAllData:^(NSError *error) {
|
||||
[self hideActivityIndicator];
|
||||
if (error) {
|
||||
UIAlertController *alertController =
|
||||
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"signin_again_title", nil)
|
||||
message:NSLocalizedString(@"signin_again_message", nil)
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[self.containerViewController.extensionContext cancelRequestWithError:error];
|
||||
}]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
} else {
|
||||
if (self.playlistValues.count == 0 && self.friendValues.count == 0) {
|
||||
NSError *err;
|
||||
UIAlertController *alertController =
|
||||
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"no_receiver_title", nil)
|
||||
message:NSLocalizedString(@"no_receiver_message", nil)
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[self.containerViewController.extensionContext cancelRequestWithError:err];
|
||||
}]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
} else {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
self.navigationItem.rightBarButtonItem.enabled = NO;
|
||||
|
||||
NSArray *selectedReceiverIndexPaths = self.containerViewController.selectedReceiverIndexPaths;
|
||||
|
||||
[self.containerViewController verifyServiceAvailableWithCompletionHandler:^(BOOL success) {
|
||||
if ([selectedReceiverIndexPaths count]) {
|
||||
self.navigationItem.rightBarButtonItem.enabled = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
if (self.containerViewController.playlistData && self.containerViewController.friendData) {
|
||||
[self preparePlaylistValues];
|
||||
[self prepareFriendValues];
|
||||
|
||||
[self.tableView reloadData];
|
||||
|
||||
if ([selectedReceiverIndexPaths count]) {
|
||||
[selectedReceiverIndexPaths
|
||||
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
[self.tableView selectRowAtIndexPath:indexPath
|
||||
animated:NO
|
||||
scrollPosition:UITableViewScrollPositionNone];
|
||||
}];
|
||||
[self updateHeaderTitleWithCounts];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
self.friendValues = nil;
|
||||
self.playlistValues = nil;
|
||||
self.containerViewController.friendData = nil;
|
||||
self.containerViewController.playlistData = nil;
|
||||
self.containerViewController.selectedReceiverIndexPaths = nil; //[self.tableView indexPathsForSelectedRows];
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)showActivityIndicator {
|
||||
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]
|
||||
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
self.navigationItem.titleView = activityIndicator;
|
||||
[activityIndicator startAnimating];
|
||||
}
|
||||
|
||||
- (void)hideActivityIndicator {
|
||||
UIActivityIndicatorView *activityIndicator = (UIActivityIndicatorView *)self.navigationItem.titleView;
|
||||
if ([activityIndicator isKindOfClass:[UIActivityIndicatorView class]]) {
|
||||
[activityIndicator stopAnimating];
|
||||
}
|
||||
self.navigationItem.titleView = nil;
|
||||
}
|
||||
|
||||
- (void)preparePlaylistValues {
|
||||
NSMutableArray *playlistValues = [NSMutableArray arrayWithCapacity:self.containerViewController.playlistData.count];
|
||||
for (NSDictionary *record in self.containerViewController.playlistData) {
|
||||
@autoreleasepool {
|
||||
NSString *name = record[@"name"];
|
||||
[playlistValues addObject:name];
|
||||
}
|
||||
}
|
||||
self.playlistValues = playlistValues;
|
||||
}
|
||||
|
||||
- (void)prepareFriendValues {
|
||||
NSMutableArray *friendValues = [NSMutableArray arrayWithCapacity:self.containerViewController.friendData.count];
|
||||
for (NSDictionary *record in self.containerViewController.friendData) {
|
||||
@autoreleasepool {
|
||||
NSString *name = record[@"fullName"];
|
||||
[friendValues addObject:name];
|
||||
}
|
||||
}
|
||||
self.friendValues = friendValues;
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
NSArray *playlistData = [self.containerViewController.playlistData copy];
|
||||
NSArray *friendData = [self.containerViewController.friendData copy];
|
||||
NSMutableArray *selectedPlaylistIds = [[NSMutableArray alloc] initWithCapacity:playlistData.count];
|
||||
NSMutableArray *selectedFriendUsernames = [[NSMutableArray alloc] initWithCapacity:friendData.count];
|
||||
|
||||
[[self.tableView indexPathsForSelectedRows]
|
||||
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
switch (indexPath.section) {
|
||||
case kPlaylist:
|
||||
[selectedPlaylistIds addObject:playlistData[indexPath.row][@"id"]];
|
||||
break;
|
||||
case kFriend:
|
||||
[selectedFriendUsernames addObject:friendData[indexPath.row][@"username"]];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
|
||||
[self loadAllData:^(NSError *error) {
|
||||
[self.refreshControl endRefreshing];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSArray *freshPlaylistData = [self.containerViewController.playlistData copy];
|
||||
NSArray *freshFriendData = [self.containerViewController.friendData copy];
|
||||
|
||||
[freshPlaylistData enumerateObjectsUsingBlock:^(NSDictionary *playlist, NSUInteger idx, BOOL *stop) {
|
||||
@autoreleasepool {
|
||||
if ([selectedPlaylistIds containsObject:playlist[@"id"]]) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:kPlaylist];
|
||||
[self.tableView selectRowAtIndexPath:indexPath
|
||||
animated:NO
|
||||
scrollPosition:UITableViewScrollPositionNone];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[freshFriendData enumerateObjectsUsingBlock:^(NSDictionary *friend, NSUInteger idx, BOOL *stop) {
|
||||
@autoreleasepool {
|
||||
if ([selectedFriendUsernames containsObject:friend[@"username"]]) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:kFriend];
|
||||
[self.tableView selectRowAtIndexPath:indexPath
|
||||
animated:NO
|
||||
scrollPosition:UITableViewScrollPositionNone];
|
||||
}
|
||||
}
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)headerTitleWithPlaylistCounts:(NSUInteger)playlists andFriendCounts:(NSUInteger)friends {
|
||||
NSString *rawString;
|
||||
if (playlists == 0) {
|
||||
if (friends == 1) {
|
||||
rawString = NSLocalizedString(@"navigation_header_n_selected_friend", nil);
|
||||
} else {
|
||||
rawString = NSLocalizedString(@"navigation_header_n_selected_friends", nil);
|
||||
}
|
||||
}
|
||||
if (friends == 0) {
|
||||
if (playlists == 1) {
|
||||
rawString = NSLocalizedString(@"navigation_header_n_selected_playlist", nil);
|
||||
} else {
|
||||
rawString = NSLocalizedString(@"navigation_header_n_selected_playlists", nil);
|
||||
}
|
||||
}
|
||||
if (0 < playlists && 0 < friends) {
|
||||
rawString = NSLocalizedString(@"navigation_header_n_selected_receivers", nil);
|
||||
}
|
||||
if (playlists + friends == 0) {
|
||||
return NSLocalizedString(@"navigation_header_select_receivers", nil);
|
||||
}
|
||||
return [NSString stringWithFormat:rawString, playlists + friends];
|
||||
}
|
||||
|
||||
- (void)updateHeaderTitleWithCounts {
|
||||
__block NSUInteger playlistCount = 0;
|
||||
__block NSUInteger friendCount = 0;
|
||||
[[self.tableView indexPathsForSelectedRows]
|
||||
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
||||
switch (indexPath.section) {
|
||||
case kPlaylist:
|
||||
playlistCount++;
|
||||
break;
|
||||
|
||||
case kFriend:
|
||||
friendCount++;
|
||||
break;
|
||||
}
|
||||
}];
|
||||
NSString *title = [self headerTitleWithPlaylistCounts:playlistCount andFriendCounts:friendCount];
|
||||
self.navigationItem.title = title;
|
||||
}
|
||||
|
||||
#pragma mark Network
|
||||
- (void)loadAllData:(void (^)(NSError *))completionHandler {
|
||||
__block NSError *error = nil;
|
||||
|
||||
dispatch_group_t dispatchGroup = dispatch_group_create();
|
||||
dispatch_group_enter(dispatchGroup);
|
||||
|
||||
[self loadPlaylistsWithCompletionHandler:^(NSError *playlistsError) {
|
||||
if (playlistsError)
|
||||
error = playlistsError;
|
||||
dispatch_group_leave(dispatchGroup);
|
||||
}];
|
||||
|
||||
dispatch_group_enter(dispatchGroup);
|
||||
[self loadFriendsWithCompletionHandler:^(NSError *friendsError) {
|
||||
if (friendsError)
|
||||
error = friendsError;
|
||||
dispatch_group_leave(dispatchGroup);
|
||||
}];
|
||||
|
||||
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
|
||||
completionHandler(error);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)loadPlaylistsWithCompletionHandler:(void (^)(NSError *))completionHandler {
|
||||
[self.containerViewController.api playlists:^(id data, NSURLResponse *response, NSError *error) {
|
||||
if (error || !data || [data isKindOfClass:[NSDictionary class]]) {
|
||||
completionHandler(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if ([data isKindOfClass:[NSArray class]]) {
|
||||
NSPredicate *predicateByTitle = [NSPredicate predicateWithFormat:@"SELF.type == %@", @"normal"];
|
||||
NSArray *playlists = [(NSArray*)data filteredArrayUsingPredicate:predicateByTitle];
|
||||
self.containerViewController.playlistData = playlists;
|
||||
[self preparePlaylistValues];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
completionHandler(nil);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)loadFriendsWithCompletionHandler:(void (^)(NSError *))completionHandler {
|
||||
[self.containerViewController.api friends:^(NSDictionary *data, NSURLResponse *response, NSError *error) {
|
||||
if (error || data[@"error"]) {
|
||||
completionHandler(error);
|
||||
return;
|
||||
}
|
||||
NSString *predString = [NSString stringWithFormat:@"(status='%@')", @"success"];
|
||||
NSPredicate *pred = [NSPredicate predicateWithFormat:predString];
|
||||
NSArray *friendsArr = [data[@"friends"] filteredArrayUsingPredicate:pred];
|
||||
self.containerViewController.friendData = friendsArr;
|
||||
[self prepareFriendValues];
|
||||
[self.tableView reloadData];
|
||||
completionHandler(nil);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
// Return the number of sections.
|
||||
return 4;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
|
||||
switch (section) {
|
||||
case kPlaylistHeader:
|
||||
return self.playlistValues.count ? NSLocalizedString(@"table_header_playlist", nil) : nil;
|
||||
case kFriendHeader:
|
||||
return self.friendValues.count ? NSLocalizedString(@"table_header_friend", nil) : nil;
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
// Return the number of rows in the section.
|
||||
switch (section) {
|
||||
case kPlaylist:
|
||||
return self.playlistValues.count;
|
||||
case kFriend:
|
||||
return self.friendValues.count;
|
||||
case kPlaylistHeader:
|
||||
return self.playlistValues.count ? 1 : 0;
|
||||
case kFriendHeader:
|
||||
return self.friendValues.count ? 1 : 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
static NSString *cellId = @"SLComposeTableViewControllerCell";
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
|
||||
if (cell == nil) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
|
||||
}
|
||||
|
||||
if ([[tableView indexPathsForSelectedRows] containsObject:indexPath]) {
|
||||
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
|
||||
}
|
||||
|
||||
switch (indexPath.section) {
|
||||
case kPlaylist:
|
||||
cell.textLabel.text = self.playlistValues[indexPath.row];
|
||||
break;
|
||||
case kFriend:
|
||||
cell.textLabel.text = self.friendValues[indexPath.row];
|
||||
break;
|
||||
case kPlaylistHeader:
|
||||
cell.textLabel.text = NSLocalizedString(@"table_select_all_playlists", nil);
|
||||
break;
|
||||
case kFriendHeader:
|
||||
cell.textLabel.text = NSLocalizedString(@"table_select_all_friends", nil);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
/*
|
||||
// Override to support conditional editing of the table view.
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// Return NO if you do not want the specified item to be editable.
|
||||
return YES;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// Override to support editing the table view.
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete) {
|
||||
// Delete the row from the data source
|
||||
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
|
||||
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
|
||||
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// Override to support rearranging the table view.
|
||||
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
|
||||
}
|
||||
*/
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
switch (indexPath.section) {
|
||||
case kPlaylistHeader:
|
||||
{
|
||||
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kPlaylist]; i++) {
|
||||
NSIndexPath *playlistIndexPath = [NSIndexPath indexPathForRow:i inSection:kPlaylist];
|
||||
[tableView selectRowAtIndexPath:playlistIndexPath
|
||||
animated:NO
|
||||
scrollPosition:UITableViewScrollPositionNone];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case kFriendHeader:
|
||||
{
|
||||
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kFriend]; i++) {
|
||||
NSIndexPath *friendIndexPath = [NSIndexPath indexPathForRow:i inSection:kFriend];
|
||||
[tableView selectRowAtIndexPath:friendIndexPath
|
||||
animated:NO
|
||||
scrollPosition:UITableViewScrollPositionNone];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
self.navigationItem.rightBarButtonItem.enabled = YES;
|
||||
[self updateHeaderTitleWithCounts];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
switch (indexPath.section) {
|
||||
case kPlaylistHeader:
|
||||
{
|
||||
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kPlaylist]; i++) {
|
||||
NSIndexPath *playlistIndexPath = [NSIndexPath indexPathForRow:i inSection:kPlaylist];
|
||||
[tableView deselectRowAtIndexPath:playlistIndexPath
|
||||
animated:NO];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case kFriendHeader:
|
||||
{
|
||||
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kFriend]; i++) {
|
||||
NSIndexPath *friendIndexPath = [NSIndexPath indexPathForRow:i inSection:kFriend];
|
||||
[tableView deselectRowAtIndexPath:friendIndexPath
|
||||
animated:NO];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case kPlaylist:
|
||||
{
|
||||
NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:kPlaylistHeader];
|
||||
[tableView deselectRowAtIndexPath:headerIndexPath
|
||||
animated:NO];
|
||||
break;
|
||||
}
|
||||
|
||||
case kFriend:
|
||||
{
|
||||
NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:kFriendHeader];
|
||||
[tableView deselectRowAtIndexPath:headerIndexPath
|
||||
animated:NO];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (![tableView indexPathsForSelectedRows]) {
|
||||
self.navigationItem.rightBarButtonItem.enabled = NO;
|
||||
}
|
||||
[self updateHeaderTitleWithCounts];
|
||||
}
|
||||
|
||||
/*
|
||||
// Override to support conditional rearranging of the table view.
|
||||
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// Return NO if you do not want the item to be re-orderable.
|
||||
return YES;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
#pragma mark - Navigation
|
||||
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
// Get the new view controller using [segue destinationViewController].
|
||||
// Pass the selected object to the new view controller.
|
||||
}
|
||||
*/
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user