diff --git a/Preferences/NSPCustomizeAppsController.m b/Preferences/NSPCustomizeAppsController.m index 1a16bbc..7d87f47 100644 --- a/Preferences/NSPCustomizeAppsController.m +++ b/Preferences/NSPCustomizeAppsController.m @@ -43,7 +43,7 @@ - (void)setAppDefaults:(NSString *)appID { XEq(_service, PUSHER_SERVICE_PUSHER_RECEIVER)) { defaultDict[@"includeIcon"] = _defaultIncludeIcon; } - if (_isCustomService || XEq(_service, PUSHER_SERVICE_PUSHER_RECEIVER)) { + if (_isCustomService || XEq(_service, PUSHER_SERVICE_PUSHER_RECEIVER) || Xeq(_service, PUSHER_SERVICE_PUSHOVER)) { defaultDict[@"includeImage"] = _defaultIncludeImage; defaultDict[@"imageMaxWidth"] = _defaultImageMaxWidth; defaultDict[@"imageMaxHeight"] = _defaultImageMaxHeight; @@ -163,6 +163,18 @@ - (void)viewWillAppear:(BOOL)animated { _defaultSounds = [(prefs[[self.specifier propertyForKey:@"defaultSoundsKey"]] ?: @[]) copy]; + _defaultIncludeImage = + [(prefs[[self.specifier propertyForKey:@"defaultIncludeImageKey"]] + ?: @YES) copy]; + _defaultImageMaxWidth = + [(prefs[[self.specifier propertyForKey:@"defaultImageMaxWidthKey"]] + ?: @(PUSHER_DEFAULT_MAX_WIDTH)) copy]; + _defaultImageMaxHeight = + [(prefs[[self.specifier propertyForKey:@"defaultImageMaxHeightKey"]] + ?: @(PUSHER_DEFAULT_MAX_HEIGHT)) copy]; + _defaultImageShrinkFactor = + [(prefs[[self.specifier propertyForKey:@"defaultImageShrinkFactorKey"]] + ?: @(PUSHER_DEFAULT_SHRINK_FACTOR)) copy]; } if (XEq(_service, PUSHER_SERVICE_IFTTT)) { _defaultEventName = diff --git a/Preferences/NSPSharedSpecifiers.m b/Preferences/NSPSharedSpecifiers.m index 49ac98d..3d35c56 100644 --- a/Preferences/NSPSharedSpecifiers.m +++ b/Preferences/NSPSharedSpecifiers.m @@ -207,13 +207,52 @@ + (NSArray *)pushover:(NSString *)appID { [devices setProperty:@(isCustomApp) forKey:@"isCustomApp"]; [sounds setProperty:@(isCustomApp) forKey:@"isCustomApp"]; + + PSSpecifier *includeImage = [PSSpecifier preferenceSpecifierNamed:@"Include Image" target:self set:@selector(setPreferenceValue:forBuiltInServiceSpecifier:) get:@selector(readBuiltInServicePreferenceValue:) detail:nil cell:PSSwitchCell edit:nil]; + [includeImage setProperty:NSPPreferencePushoverIncludeImageKey forKey:@"key"]; + [includeImage setProperty:@YES forKey:@"enabled"]; + [includeImage setProperty:@YES forKey:@"default"]; + [includeImage setProperty:@(isCustomApp) forKey:@"isCustomApp"]; + [includeImage setProperty:NSPPreferencePushoverCustomAppsKey forKey:@"customAppsKey"]; + [includeImage setProperty:@"includeImage" forKey:@"customAppsPrefsKey"]; + + PSSpecifier *imageMaxWidth = [PSSpecifier preferenceSpecifierNamed:@"Maximum Image Width (pixels)" target:self set:@selector(setPreferenceValue:forBuiltInServiceSpecifier:) get:@selector(readBuiltInServicePreferenceValue:) detail:nil cell:PSEditTextCell edit:nil]; + [imageMaxWidth setProperty:NSPPreferencePushoverImageMaxWidthKey forKey:@"key"]; + [imageMaxWidth setProperty:@YES forKey:@"enabled"]; + [imageMaxWidth setProperty:@YES forKey:@"isDecimalPad"]; + [imageMaxWidth setProperty:@(PUSHER_DEFAULT_MAX_WIDTH) forKey:@"default"]; + [imageMaxWidth setProperty:@(isCustomApp) forKey:@"isCustomApp"]; + [imageMaxWidth setProperty:NSPPreferencePushoverCustomAppsKey forKey:@"customAppsKey"]; + [imageMaxWidth setProperty:@"imageMaxWidth" forKey:@"customAppsPrefsKey"]; + + PSSpecifier *imageMaxHeight = [PSSpecifier preferenceSpecifierNamed:@"Maximum Image Height (pixels)" target:self set:@selector(setPreferenceValue:forBuiltInServiceSpecifier:) get:@selector(readBuiltInServicePreferenceValue:) detail:nil cell:PSEditTextCell edit:nil]; + [imageMaxHeight setProperty:NSPPreferencePushoverImageMaxHeightKey forKey:@"key"]; + [imageMaxHeight setProperty:@YES forKey:@"enabled"]; + [imageMaxHeight setProperty:@YES forKey:@"isDecimalPad"]; + [imageMaxHeight setProperty:@(PUSHER_DEFAULT_MAX_HEIGHT) forKey:@"default"]; + [imageMaxHeight setProperty:@(isCustomApp) forKey:@"isCustomApp"]; + [imageMaxHeight setProperty:NSPPreferencePushoverCustomAppsKey forKey:@"customAppsKey"]; + [imageMaxHeight setProperty:@"imageMaxHeight" forKey:@"customAppsPrefsKey"]; + + PSSpecifier *imageShrinkFactor = [PSSpecifier preferenceSpecifierNamed:@"Image Shrink Factor Upon Retry" target:self set:@selector(setPreferenceValue:forBuiltInServiceSpecifier:) get:@selector(readBuiltInServicePreferenceValue:) detail:nil cell:PSEditTextCell edit:nil]; + [imageShrinkFactor setProperty:NSPPreferencePushoverImageShrinkFactorKey forKey:@"key"]; + [imageShrinkFactor setProperty:@YES forKey:@"enabled"]; + [imageShrinkFactor setProperty:@YES forKey:@"isDecimalPad"]; + [imageShrinkFactor setProperty:@(PUSHER_DEFAULT_SHRINK_FACTOR) forKey:@"default"]; + [imageShrinkFactor setProperty:@(isCustomApp) forKey:@"isCustomApp"]; + [imageShrinkFactor setProperty:NSPPreferencePushoverCustomAppsKey forKey:@"customAppsKey"]; + [imageShrinkFactor setProperty:@"imageShrinkFactor" forKey:@"customAppsPrefsKey"]; if (isCustomApp) { [devices setProperty:appID forKey:@"customAppIDKey"]; [sounds setProperty:appID forKey:@"customAppIDKey"]; + [includeImage setProperty:appID forKey:@"customAppID"]; + [imageMaxWidth setProperty:appID forKey:@"customAppID"]; + [imageMaxHeight setProperty:appID forKey:@"customAppID"]; + [imageShrinkFactor setProperty:appID forKey:@"customAppID"]; } - return @[ devices, sounds ]; + return @[ devices, sounds, includeImage, imageMaxWidth, imageMaxHeight, imageShrinkFactor ]; } + (NSArray *)pushbullet:(NSString *)appID { diff --git a/Tweak.xm b/Tweak.xm index 2843ac4..aa835d4 100644 --- a/Tweak.xm +++ b/Tweak.xm @@ -30,6 +30,21 @@ format:(int)format; @end +@interface UIImage (Alpha) +- (BOOL)hasAlpha; +@end + +@implementation UIImage (Alpha) +// Returns true if the image has an alpha layer +- (BOOL)hasAlpha { + CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage); + return (alpha == kCGImageAlphaFirst || + alpha == kCGImageAlphaLast || + alpha == kCGImageAlphaPremultipliedFirst || + alpha == kCGImageAlphaPremultipliedLast); +} +@end + static BOOL pusherEnabled = NO; static int pusherWhenToPush = PUSHER_WHEN_TO_PUSH_EITHER; static NSArray *pusherSNS = nil; @@ -75,6 +90,9 @@ static NSString *stringForObject(id object, NSString *prefix, stringForObject(dict[key], XStr(@"%@\t", prefix), dontTruncate)); } str = XStr(@"%@\n%@}", str, prefix); + } else if ([object isKindOfClass:NSData.class]) { + NSData *data = (NSData *) object; + str = Xstr(@"%@%@", prefix, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } else { if (!dontTruncate && [object isKindOfClass:NSString.class] && ((NSString *)object).length > PUSHER_LOG_MAX_STRING_LENGTH) { @@ -307,7 +325,7 @@ static NSString *base64RepresentationForImage(UIImage *image) { static UIImage *shrinkImage(UIImage *image, CGFloat factor) { CGSize newSize = CGSizeMake(image.size.width / factor, image.size.height / factor); - UIGraphicsBeginImageContext(newSize); + UIGraphicsBeginImageContextWithOptions(newSize, !image.hasAlpha, 1.0); [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); @@ -508,6 +526,20 @@ static void pusherPrefsChanged() { NSString *curateDataKey = XStr(@"%@CurateData", service); servicePrefs[@"curateData"] = prefs[curateDataKey] ?: @YES; } + + if (Xeq(service, PUSHER_SERVICE_PUSHOVER)) { + NSString *includeImageKey = Xstr(@"%@IncludeImage", service); + servicePrefs[@"includeImage"] = prefs[includeImageKey] ?: @YES; + + NSString *imageMaxWidthKey = Xstr(@"%@ImageMaxWidth", service); + servicePrefs[@"imageMaxWidth"] = prefs[imageMaxWidthKey] ?: @(PUSHER_DEFAULT_MAX_WIDTH); + + NSString *imageMaxHeightKey = Xstr(@"%@ImageMaxHeight", service); + servicePrefs[@"imageMaxHeight"] = prefs[imageMaxHeightKey] ?: @(PUSHER_DEFAULT_MAX_HEIGHT); + + NSString *imageShrinkFactorKey = Xstr(@"%@ImageShrinkFactor", service); + servicePrefs[@"imageShrinkFactor"] = prefs[imageShrinkFactorKey] ?: @(PUSHER_DEFAULT_SHRINK_FACTOR); + } if (XEq(service, PUSHER_SERVICE_PUSHER_RECEIVER)) { NSString *includeIconKey = XStr(@"%@IncludeIcon", service); @@ -1037,19 +1069,7 @@ static NSString *prefsSayNo(BBServer *server, BBBulletin *bulletin) { for (NSDictionary *device in dictionary[@"devices"]) { [deviceIDs addObject:device[@"id"]]; } - if (XEq(service, PUSHER_SERVICE_PUSHOVER)) { - NSString *combinedDevices = [deviceIDs componentsJoinedByString:@","]; - NSMutableDictionary *pushoverInfoDict = [@{ - @"title" : dictionary[@"title"], - @"message" : dictionary[@"message"], - @"device" : combinedDevices - } mutableCopy]; - NSString *firstSoundID = [dictionary[@"sounds"] firstObject]; - if (firstSoundID) { - pushoverInfoDict[@"sound"] = firstSoundID; - } - return pushoverInfoDict; - } else if (XEq(service, PUSHER_SERVICE_PUSHBULLET)) { + if (XEq(service, PUSHER_SERVICE_PUSHBULLET)) { // should always only be one, but just in case NSString *firstDevice = [deviceIDs firstObject]; NSMutableDictionary *pushbulletInfoDict = [@{ @@ -1133,8 +1153,16 @@ static NSString *prefsSayNo(BBServer *server, BBBulletin *bulletin) { } } } - - if (XEq(service, PUSHER_SERVICE_IFTTT)) { + if (Xeq(service, PUSHER_SERVICE_PUSHOVER)) { + NSString *combinedDevices = [deviceIDs componentsJoinedByString:@","]; + data[@"device"] = combinedDevices; + data[@"title"] = dictionary[@"title"]; + NSString *firstSoundID = [dictionary[@"sounds"] firstObject]; + if (firstSoundID) { + data[@"sound"] = firstSoundID; + } + return data; + } else if (XEq(service, PUSHER_SERVICE_IFTTT)) { if (dictionary[@"curateData"] && ((NSNumber *)dictionary[@"curateData"]).boolValue) { return @{ @@ -1224,10 +1252,12 @@ static NSString *prefsSayNo(BBServer *server, BBBulletin *bulletin) { } //! XEq(service, PUSHER_SERVICE_PUSHER_RECEIVER) && - if (infoDictForRequest[@"image"] && - [infoDictForRequest[@"image"] isKindOfClass:UIImage.class]) { - infoDictForRequest[@"image"] = - base64RepresentationForImage(infoDictForRequest[@"image"]); + if (!Xeq(service, PUSHER_SERVICE_PUSHOVER)) { + if (infoDictForRequest[@"image"] && + [infoDictForRequest[@"image"] isKindOfClass:UIImage.class]) { + infoDictForRequest[@"image"] = + base64RepresentationForImage(infoDictForRequest[@"image"]); + } } if (XEq(method, @"GET")) { @@ -1282,26 +1312,82 @@ static NSString *prefsSayNo(BBServer *server, BBBulletin *bulletin) { } if (XEq(method, @"POST")) { - // replace image strings with shorter string - NSMutableDictionary *infoDictForLog = [infoDictForRequest mutableCopy]; - for (NSString *prop in PUSHER_LOG_IMAGE_DATA_PROPERTIES) { - if (infoDictForLog[prop]) { - infoDictForLog[prop] = PUSHER_LOG_IMAGE_DATA_REPLACEMENT; + if (Xeq(service, PUSHER_SERVICE_PUSHOVER)) { + NSString *boundary = @"---BoundaryPUSHOVER"; + NSString *contentType = Xstr(@"multipart/form-data; boundary=%@",boundary); + [request addValue:contentType forHTTPHeaderField: @"Content-Type"]; + NSMutableData *body = [NSMutableData data]; + + NSDictionary *stringOnlyInfoDictForRequest = [infoDictForRequest dictionaryWithValuesForKeys:@[@"user", @"token", @"message", @"title", @"device"]]; + [stringOnlyInfoDictForRequest enumerateKeysAndObjectsUsingBlock: ^(NSString *key, NSString *object, BOOL *stop) { + [body appendData:[Xstr(@"--%@\r\n",boundary) dataUsingEncoding:NSUTF8StringEncoding]]; + [body appendData:[Xstr(@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key) dataUsingEncoding:NSUTF8StringEncoding]]; + [body appendData:[Xstr(@"%@\r\n",object) dataUsingEncoding:NSUTF8StringEncoding]]; + }]; + NSMutableData *bodyForLog = [body mutableCopy]; + + if (infoDictForRequest[@"image"] && [infoDictForRequest[@"image"] isKindOfClass:UIImage.class]) { + + UIImage* image = (UIImage*)infoDictForRequest[@"image"]; + + for( NSData* data in @[ + [Xstr(@"--%@\r\n",boundary) dataUsingEncoding:NSUTF8StringEncoding], + [Xstr(@"Content-Disposition: form-data; name=\"attachment\"; filename=\"file.%@\"\r\n", image.hasAlpha ? @"png" : @"jpg") dataUsingEncoding:NSUTF8StringEncoding], + [Xstr(@"Content-Type: image/%@\r\n\r\n", image.hasAlpha ? @"png" : @"jpeg") dataUsingEncoding:NSUTF8StringEncoding] + ] ){ + [body appendData:data]; + [bodyForLog appendData:data]; + } + + + if ( image.hasAlpha ) { + [body appendData:UIImagePNGRepresentation(image)]; + }else{ + [body appendData:UIImageJPEGRepresentation(image, 1.0)]; + } + [bodyForLog appendData:[PUSHER_LOG_IMAGE_DATA_REPLACEMENT dataUsingEncoding:NSUTF8StringEncoding]]; + + + for( NSData* data in @[ + [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding] + ] ){ + [body appendData:data]; + [bodyForLog appendData:data]; + } + } + + for( NSData* data in @[ + [Xstr(@"--%@--\r\n", boundary) dataUsingEncoding:NSUTF8StringEncoding] + ] ){ + [body appendData:data]; + [bodyForLog appendData:data]; + } + + [request setHTTPBody:body]; + addToLogIfEnabled(service, bulletin, @"Request Body", bodyForLog); + + } else { + // replace image strings with shorter string + NSMutableDictionary *infoDictForLog = [infoDictForRequest mutableCopy]; + for (NSString *prop in PUSHER_LOG_IMAGE_DATA_PROPERTIES) { + if (infoDictForLog[prop]) { + infoDictForLog[prop] = PUSHER_LOG_IMAGE_DATA_REPLACEMENT; + } } + addToLogIfEnabled(service, bulletin, @"Request Body Dictionary", + infoDictForLog); + + [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + NSData *requestData = + [NSJSONSerialization dataWithJSONObject:infoDictForRequest + options:NSJSONWritingPrettyPrinted + error:nil]; + [request setValue:XStr(@"%d", (int)requestData.length) + forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody:requestData]; } - addToLogIfEnabled(service, bulletin, @"Request Body Dictionary", - infoDictForLog); - - [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - - NSData *requestData = - [NSJSONSerialization dataWithJSONObject:infoDictForRequest - options:NSJSONWritingPrettyPrinted - error:nil]; - [request setValue:XStr(@"%d", (int)requestData.length) - forHTTPHeaderField:@"Content-Length"]; - [request setHTTPBody:requestData]; } // use async way to connect network diff --git a/global.h b/global.h index ff01ca5..4ebabb4 100644 --- a/global.h +++ b/global.h @@ -85,6 +85,10 @@ typedef enum { #define NSPPreferencePushoverSoundsKey @"PushoverSounds" #define NSPPreferencePushoverBLPrefix @"PushoverBL-" #define NSPPreferencePushoverCustomAppsKey @"PushoverCustomApps" +#define NSPPreferencePushoverIncludeImageKey @"PushoverIncludeImage" +#define NSPPreferencePushoverImageMaxWidthKey @"PushoverImageMaxWidth" +#define NSPPreferencePushoverImageMaxHeightKey @"PushoverImageMaxHeight" +#define NSPPreferencePushoverImageShrinkFactorKey @"PushoverImageShrinkFactor" // All keys MUST HAVE the prefix equal to the name of the service #define PUSHER_SERVICE_PUSHBULLET @"Pushbullet"