/* * Copyright (c) 2010-2020 Belledonne Communications SARL. * * This file is part of linphone-iphone * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #import #import #import #import #import "Utils.h" #import "linphone/linphonecore.h" #import "UILabel+Boldify.h" #import "FastAddressBook.h" #import "ColorSpaceUtilities.h" @implementation LinphoneUtils + (UIImage *)resizeImage:(UIImage *)imageToResize newSize:(CGSize)newSize { UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newSize]; UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext*_Nonnull myContext) { [imageToResize drawInRect:(CGRect) {.origin = CGPointZero, .size = newSize}]; }]; return [image imageWithRenderingMode:imageToResize.renderingMode]; } + (BOOL)hasSelfAvatar { return [LinphoneManager.instance lpConfigStringForKey:@"avatar"] != nil; } + (UIImage *)selfAvatar { return [LinphoneManager.instance avatar]; } + (NSString *)durationToString:(int)duration { NSMutableString *result = [[NSMutableString alloc] init]; if (duration / 3600 > 0) { [result appendString:[NSString stringWithFormat:@"%02i:", duration / 3600]]; duration = duration % 3600; } return [result stringByAppendingString:[NSString stringWithFormat:@"%02i:%02i", (duration / 60), (duration % 60)]]; } + (NSString *) intervalToString:(NSTimeInterval)interval { NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; formatter.allowedUnits = NSCalendarUnitSecond; formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated; formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDropAll; return [formatter stringFromTimeInterval:interval]; } + (NSMutableDictionary *)photoAssetsDictionary { NSMutableDictionary *assetDict = [NSMutableDictionary dictionary]; PHFetchOptions *options = [[PHFetchOptions alloc] init]; [options setIncludeHiddenAssets:YES]; [options setIncludeAllBurstAssets:YES]; PHFetchResult *fetchRes = [PHAsset fetchAssetsWithOptions:options]; for (PHAsset *asset in fetchRes) { NSString *key = [asset valueForKey:@"filename"]; [assetDict setObject:asset forKey:[[key componentsSeparatedByString:@"."] firstObject]]; } return assetDict; } + (NSString *)timeToString:(time_t)time withFormat:(LinphoneDateFormat)format { NSString *formatstr; NSDate *todayDate = [[NSDate alloc] init]; NSDate *messageDate = (time == 0) ? todayDate : [NSDate dateWithTimeIntervalSince1970:time]; NSDateComponents *todayComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:todayDate]; NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:messageDate]; BOOL sameYear = (todayComponents.year == dateComponents.year); BOOL sameMonth = (sameYear && (todayComponents.month == dateComponents.month)); BOOL sameDay = (sameMonth && (todayComponents.day == dateComponents.day)); switch (format) { case LinphoneDateHistoryList: if (sameYear) { formatstr = NSLocalizedString(@"EEE dd MMMM", @"Date formatting in History List, for current year (also see " @"http://cybersam.com/ios-dev/quick-guide-to-ios-dateformatting)"); } else { formatstr = NSLocalizedString(@"EEE dd MMMM yyyy", @"Date formatting in History List, for previous years (also see " @"http://cybersam.com/ios-dev/quick-guide-to-ios-dateformatting)"); } break; case LinphoneDateHistoryDetails: formatstr = NSLocalizedString(@"EEE dd MMM 'at' HH'h'mm", @"Date formatting in History Details (also see " @"http://cybersam.com/ios-dev/" @"quick-guide-to-ios-dateformatting)"); break; case LinphoneDateChatList: if (sameDay) { formatstr = NSLocalizedString( @"HH:mm", @"Date formatting in Chat List and Conversation bubbles, for current day (also see " @"http://cybersam.com/ios-dev/quick-guide-to-ios-dateformatting)"); } else { formatstr = NSLocalizedString(@"MM/dd", @"Date formatting in Chat List, for all but current day (also see " @"http://cybersam.com/ios-dev/quick-guide-to-ios-dateformatting)"); } break; case LinphoneDateChatBubble: if (sameDay) { formatstr = NSLocalizedString( @"HH:mm", @"Date formatting in Chat List and Conversation bubbles, for current day (also see " @"http://cybersam.com/ios-dev/quick-guide-to-ios-dateformatting)"); } else { formatstr = NSLocalizedString(@"MM/dd - HH:mm", @"Date formatting in Conversation bubbles, for all but current day (also " @"see http://cybersam.com/ios-dev/quick-guide-to-ios-dateformatting)"); } break; } NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:formatstr]; return [dateFormatter stringFromDate:messageDate]; } + (BOOL)findAndResignFirstResponder:(UIView *)view { if (view.isFirstResponder) { [view resignFirstResponder]; return YES; } for (UIView *subView in view.subviews) { if ([LinphoneUtils findAndResignFirstResponder:subView]) return YES; } return NO; } + (void)adjustFontSize:(UIView *)view mult:(float)mult { if ([view isKindOfClass:[UILabel class]]) { UILabel *label = (UILabel *)view; UIFont *font = [label font]; [label setFont:[UIFont fontWithName:font.fontName size:font.pointSize * mult]]; } else if ([view isKindOfClass:[UITextField class]]) { UITextField *label = (UITextField *)view; UIFont *font = [label font]; [label setFont:[UIFont fontWithName:font.fontName size:font.pointSize * mult]]; } else if ([view isKindOfClass:[UIButton class]]) { UIButton *button = (UIButton *)view; UIFont *font = button.titleLabel.font; [button.titleLabel setFont:[UIFont fontWithName:font.fontName size:font.pointSize * mult]]; } else { for (UIView *subView in [view subviews]) { [LinphoneUtils adjustFontSize:subView mult:mult]; } } } + (void)buttonFixStates:(UIButton *)button { // Interface builder lack fixes [button setTitle:[button titleForState:UIControlStateSelected] forState:(UIControlStateHighlighted | UIControlStateSelected)]; [button setTitleColor:[button titleColorForState:UIControlStateHighlighted] forState:(UIControlStateHighlighted | UIControlStateSelected)]; [button setTitle:[button titleForState:UIControlStateSelected] forState:(UIControlStateDisabled | UIControlStateSelected)]; [button setTitleColor:[button titleColorForState:UIControlStateDisabled] forState:(UIControlStateDisabled | UIControlStateSelected)]; } + (void)buttonMultiViewAddAttributes:(NSMutableDictionary *)attributes button:(UIButton *)button { [LinphoneUtils addDictEntry:attributes item:[button titleForState:UIControlStateNormal] key:@"title-normal"]; [LinphoneUtils addDictEntry:attributes item:[button titleForState:UIControlStateHighlighted] key:@"title-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button titleForState:UIControlStateDisabled] key:@"title-disabled"]; [LinphoneUtils addDictEntry:attributes item:[button titleForState:UIControlStateSelected] key:@"title-selected"]; [LinphoneUtils addDictEntry:attributes item:[button titleForState:UIControlStateDisabled | UIControlStateHighlighted] key:@"title-disabled-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button titleForState:UIControlStateSelected | UIControlStateHighlighted] key:@"title-selected-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button titleForState:UIControlStateSelected | UIControlStateDisabled] key:@"title-selected-disabled"]; [LinphoneUtils addDictEntry:attributes item:[button titleColorForState:UIControlStateNormal] key:@"title-color-normal"]; [LinphoneUtils addDictEntry:attributes item:[button titleColorForState:UIControlStateHighlighted] key:@"title-color-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button titleColorForState:UIControlStateDisabled] key:@"title-color-disabled"]; [LinphoneUtils addDictEntry:attributes item:[button titleColorForState:UIControlStateSelected] key:@"title-color-selected"]; [LinphoneUtils addDictEntry:attributes item:[button titleColorForState:UIControlStateDisabled | UIControlStateHighlighted] key:@"title-color-disabled-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button titleColorForState:UIControlStateSelected | UIControlStateHighlighted] key:@"title-color-selected-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button titleColorForState:UIControlStateSelected | UIControlStateDisabled] key:@"title-color-selected-disabled"]; [LinphoneUtils addDictEntry:attributes item:NSStringFromUIEdgeInsets([button titleEdgeInsets]) key:@"title-edge"]; [LinphoneUtils addDictEntry:attributes item:NSStringFromUIEdgeInsets([button contentEdgeInsets]) key:@"content-edge"]; [LinphoneUtils addDictEntry:attributes item:NSStringFromUIEdgeInsets([button imageEdgeInsets]) key:@"image-edge"]; [LinphoneUtils addDictEntry:attributes item:[button imageForState:UIControlStateNormal] key:@"image-normal"]; [LinphoneUtils addDictEntry:attributes item:[button imageForState:UIControlStateHighlighted] key:@"image-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button imageForState:UIControlStateDisabled] key:@"image-disabled"]; [LinphoneUtils addDictEntry:attributes item:[button imageForState:UIControlStateSelected] key:@"image-selected"]; [LinphoneUtils addDictEntry:attributes item:[button imageForState:UIControlStateDisabled | UIControlStateHighlighted] key:@"image-disabled-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button imageForState:UIControlStateSelected | UIControlStateHighlighted] key:@"image-selected-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button imageForState:UIControlStateSelected | UIControlStateDisabled] key:@"image-selected-disabled"]; [LinphoneUtils addDictEntry:attributes item:[button backgroundImageForState:UIControlStateNormal] key:@"background-normal"]; [LinphoneUtils addDictEntry:attributes item:[button backgroundImageForState:UIControlStateHighlighted] key:@"background-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button backgroundImageForState:UIControlStateDisabled] key:@"background-disabled"]; [LinphoneUtils addDictEntry:attributes item:[button backgroundImageForState:UIControlStateSelected] key:@"background-selected"]; [LinphoneUtils addDictEntry:attributes item:[button backgroundImageForState:UIControlStateDisabled | UIControlStateHighlighted] key:@"background-disabled-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button backgroundImageForState:UIControlStateSelected | UIControlStateHighlighted] key:@"background-selected-highlighted"]; [LinphoneUtils addDictEntry:attributes item:[button backgroundImageForState:UIControlStateSelected | UIControlStateDisabled] key:@"background-selected-disabled"]; } + (void)buttonMultiViewApplyAttributes:(NSDictionary *)attributes button:(UIButton *)button { [button setTitle:[LinphoneUtils getDictEntry:attributes key:@"title-normal"] forState:UIControlStateNormal]; [button setTitle:[LinphoneUtils getDictEntry:attributes key:@"title-highlighted"] forState:UIControlStateHighlighted]; [button setTitle:[LinphoneUtils getDictEntry:attributes key:@"title-disabled"] forState:UIControlStateDisabled]; [button setTitle:[LinphoneUtils getDictEntry:attributes key:@"title-selected"] forState:UIControlStateSelected]; [button setTitle:[LinphoneUtils getDictEntry:attributes key:@"title-disabled-highlighted"] forState:UIControlStateDisabled | UIControlStateHighlighted]; [button setTitle:[LinphoneUtils getDictEntry:attributes key:@"title-selected-highlighted"] forState:UIControlStateSelected | UIControlStateHighlighted]; [button setTitle:[LinphoneUtils getDictEntry:attributes key:@"title-selected-disabled"] forState:UIControlStateSelected | UIControlStateDisabled]; [button setTitleColor:[LinphoneUtils getDictEntry:attributes key:@"title-color-normal"] forState:UIControlStateNormal]; [button setTitleColor:[LinphoneUtils getDictEntry:attributes key:@"title-color-highlighted"] forState:UIControlStateHighlighted]; [button setTitleColor:[LinphoneUtils getDictEntry:attributes key:@"title-color-disabled"] forState:UIControlStateDisabled]; [button setTitleColor:[LinphoneUtils getDictEntry:attributes key:@"title-color-selected"] forState:UIControlStateSelected]; [button setTitleColor:[LinphoneUtils getDictEntry:attributes key:@"title-color-disabled-highlighted"] forState:UIControlStateDisabled | UIControlStateHighlighted]; [button setTitleColor:[LinphoneUtils getDictEntry:attributes key:@"title-color-selected-highlighted"] forState:UIControlStateSelected | UIControlStateHighlighted]; [button setTitleColor:[LinphoneUtils getDictEntry:attributes key:@"title-color-selected-disabled"] forState:UIControlStateSelected | UIControlStateDisabled]; [button setTitleEdgeInsets:UIEdgeInsetsFromString([LinphoneUtils getDictEntry:attributes key:@"title-edge"])]; [button setContentEdgeInsets:UIEdgeInsetsFromString([LinphoneUtils getDictEntry:attributes key:@"content-edge"])]; [button setImageEdgeInsets:UIEdgeInsetsFromString([LinphoneUtils getDictEntry:attributes key:@"image-edge"])]; [button setImage:[LinphoneUtils getDictEntry:attributes key:@"image-normal"] forState:UIControlStateNormal]; [button setImage:[LinphoneUtils getDictEntry:attributes key:@"image-highlighted"] forState:UIControlStateHighlighted]; [button setImage:[LinphoneUtils getDictEntry:attributes key:@"image-disabled"] forState:UIControlStateDisabled]; [button setImage:[LinphoneUtils getDictEntry:attributes key:@"image-selected"] forState:UIControlStateSelected]; [button setImage:[LinphoneUtils getDictEntry:attributes key:@"image-disabled-highlighted"] forState:UIControlStateDisabled | UIControlStateHighlighted]; [button setImage:[LinphoneUtils getDictEntry:attributes key:@"image-selected-highlighted"] forState:UIControlStateSelected | UIControlStateHighlighted]; [button setImage:[LinphoneUtils getDictEntry:attributes key:@"image-selected-disabled"] forState:UIControlStateSelected | UIControlStateDisabled]; [button setBackgroundImage:[LinphoneUtils getDictEntry:attributes key:@"background-normal"] forState:UIControlStateNormal]; [button setBackgroundImage:[LinphoneUtils getDictEntry:attributes key:@"background-highlighted"] forState:UIControlStateHighlighted]; [button setBackgroundImage:[LinphoneUtils getDictEntry:attributes key:@"background-disabled"] forState:UIControlStateDisabled]; [button setBackgroundImage:[LinphoneUtils getDictEntry:attributes key:@"background-selected"] forState:UIControlStateSelected]; [button setBackgroundImage:[LinphoneUtils getDictEntry:attributes key:@"background-disabled-highlighted"] forState:UIControlStateDisabled | UIControlStateHighlighted]; [button setBackgroundImage:[LinphoneUtils getDictEntry:attributes key:@"background-selected-highlighted"] forState:UIControlStateSelected | UIControlStateHighlighted]; [button setBackgroundImage:[LinphoneUtils getDictEntry:attributes key:@"background-selected-disabled"] forState:UIControlStateSelected | UIControlStateDisabled]; } + (void)addDictEntry:(NSMutableDictionary *)dict item:(id)item key:(id)key { if (item != nil && key != nil) { [dict setObject:item forKey:key]; } } + (id)getDictEntry:(NSDictionary *)dict key:(id)key { if (key != nil) { return [dict objectForKey:key]; } return nil; } + (NSString *)deviceModelIdentifier { struct utsname systemInfo; uname(&systemInfo); NSString *machine = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; if ([machine isEqual:@"iPad1,1"]) return @"iPad"; else if ([machine isEqual:@"iPad2,1"]) return @"iPad 2"; else if ([machine isEqual:@"iPad2,2"]) return @"iPad 2"; else if ([machine isEqual:@"iPad2,3"]) return @"iPad 2"; else if ([machine isEqual:@"iPad2,4"]) return @"iPad 2"; else if ([machine isEqual:@"iPad3,1"]) return @"iPad 3"; else if ([machine isEqual:@"iPad3,2"]) return @"iPad 3"; else if ([machine isEqual:@"iPad3,3"]) return @"iPad 3"; else if ([machine isEqual:@"iPad3,4"]) return @"iPad 4"; else if ([machine isEqual:@"iPad3,5"]) return @"iPad 4"; else if ([machine isEqual:@"iPad3,6"]) return @"iPad 4"; else if ([machine isEqual:@"iPad4,1"]) return @"iPad Air"; else if ([machine isEqual:@"iPad4,2"]) return @"iPad Air"; else if ([machine isEqual:@"iPad4,3"]) return @"iPad Air"; else if ([machine isEqual:@"iPad5,3"]) return @"iPad Air 2"; else if ([machine isEqual:@"iPad5,4"]) return @"iPad Air 2"; else if ([machine isEqual:@"iPad6,7"]) return @"iPad Pro 12.9"; else if ([machine isEqual:@"iPad6,8"]) return @"iPad Pro 12.9"; else if ([machine isEqual:@"iPad6,3"]) return @"iPad Pro 9.7"; else if ([machine isEqual:@"iPad6,4"]) return @"iPad Pro 9.7"; else if ([machine isEqual:@"iPad2,5"]) return @"iPad mini"; else if ([machine isEqual:@"iPad2,6"]) return @"iPad mini"; else if ([machine isEqual:@"iPad2,7"]) return @"iPad mini"; else if ([machine isEqual:@"iPad4,4"]) return @"iPad mini 2"; else if ([machine isEqual:@"iPad4,5"]) return @"iPad mini 2"; else if ([machine isEqual:@"iPad4,6"]) return @"iPad mini 2"; else if ([machine isEqual:@"iPad4,7"]) return @"iPad mini 3"; else if ([machine isEqual:@"iPad4,8"]) return @"iPad mini 3"; else if ([machine isEqual:@"iPad4,9"]) return @"iPad mini 3"; else if ([machine isEqual:@"iPad5,1"]) return @"iPad mini 4"; else if ([machine isEqual:@"iPad5,2"]) return @"iPad mini 4"; else if ([machine isEqual:@"iPhone1,1"]) return @"iPhone"; else if ([machine isEqual:@"iPhone1,2"]) return @"iPhone 3G"; else if ([machine isEqual:@"iPhone2,1"]) return @"iPhone 3GS"; else if ([machine isEqual:@"iPhone3,1"]) return @"iPhone 4"; else if ([machine isEqual:@"iPhone3,2"]) return @"iPhone 4"; else if ([machine isEqual:@"iPhone3,3"]) return @"iPhone 4"; else if ([machine isEqual:@"iPhone4,1"]) return @"iPhone 4S"; else if ([machine isEqual:@"iPhone5,1"]) return @"iPhone5,2 iPhone 5"; else if ([machine isEqual:@"iPhone5,3"]) return @"iPhone5,4 iPhone 5c"; else if ([machine isEqual:@"iPhone6,1"]) return @"iPhone6,2 iPhone 5s"; else if ([machine isEqual:@"iPhone7,2"]) return @"iPhone 6"; else if ([machine isEqual:@"iPhone7,1"]) return @"iPhone 6 Plus"; else if ([machine isEqual:@"iPhone8,1"]) return @"iPhone 6s"; else if ([machine isEqual:@"iPhone8,2"]) return @"iPhone 6s Plus"; else if ([machine isEqual:@"iPhone8,4"]) return @"iPhone SE"; else if ([machine isEqual:@"iPod1,1"]) return @"iPod touch"; else if ([machine isEqual:@"iPod2,1"]) return @"iPod touch 2G"; else if ([machine isEqual:@"iPod3,1"]) return @"iPod touch 3G"; else if ([machine isEqual:@"iPod4,1"]) return @"iPod touch 4G"; else if ([machine isEqual:@"iPod5,1"]) return @"iPod touch 5G"; else if ([machine isEqual:@"iPod7,1"]) return @"iPod touch 6G"; else if ([machine isEqual:@"x86_64"]) return @"simulator 64bits"; // none matched: cf https://www.theiphonewiki.com/wiki/Models for the whole list LOGW(@"%s: Oops, unknown machine %@... consider completing me!", __FUNCTION__, machine); return machine; } + (LinphoneAddress *)normalizeSipOrPhoneAddress:(NSString *)value { if (!value || [value isEqualToString:@""]) return NULL; LinphoneAccount *account = linphone_core_get_default_account(LC); const char *normvalue; normvalue = linphone_account_is_phone_number(account, value.UTF8String) ? linphone_account_normalize_phone_number(account, value.UTF8String) : value.UTF8String; LinphoneAddress *addr = linphone_account_normalize_sip_uri(account, normvalue); // first try to find a friend with the given address Contact *c = [FastAddressBook getContactWithAddress:addr]; if (c && c.friend) { LinphoneFriend *f = c.friend; const LinphonePresenceModel *m = f ? linphone_friend_get_presence_model_for_uri_or_tel(f, value.UTF8String) : NULL; char *contact = m ? linphone_presence_model_get_contact(m) : NULL; if (contact) { LinphoneAddress *contact_addr = linphone_address_new(contact); ms_free(contact); if (contact_addr) { linphone_address_unref(addr); return contact_addr; } } } // since user wants to escape plus, we assume it expects to have phone // numbers by default if (addr && account) { const char *username = linphone_account_params_get_dial_escape_plus_enabled(linphone_account_get_params(account)) ? normvalue : value.UTF8String; if (linphone_account_is_phone_number(account, username)) linphone_address_set_username(addr, linphone_account_normalize_phone_number(account, username)); } return addr; } + (NSArray *)parseRecordingName:(NSString *)filename { NSString *rec = @"recording_"; //key that helps find recordings NSString *subName = [filename substringFromIndex:[filename rangeOfString:rec].location]; //We remove the parent folders if they exist in the filename NSArray *splitString = [subName componentsSeparatedByString:@"_"]; //splitString: first element is the 'recording' prefix, last element is the date with the "E-d-MMM-yyyy-HH-mm-ss" format. NSString *name = [[splitString subarrayWithRange:NSMakeRange(1, [splitString count] -2)] componentsJoinedByString:@""]; NSDateFormatter *format = [[NSDateFormatter alloc] init]; [format setDateFormat:@"E-d-MMM-yyyy-HH-mm-ss"]; NSString *dateWithMkv = [splitString objectAtIndex:[splitString count]-1]; //this will be in the form "E-d-MMM-yyyy-HH-mm-ss.mkv", we have to delete the extension NSDate *date = [format dateFromString:[dateWithMkv substringToIndex:[dateWithMkv length] - 4]]; NSArray *res = [NSArray arrayWithObjects:name, date, nil]; return res; } + (UIAlertController *)networkErrorView { UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Network Error", nil) message:NSLocalizedString(@"There is no network connection available, " @"enable WIFI or WWAN prior to place a call", nil) preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){ }]; [errView addAction:defaultAction]; return errView; } @end @implementation NSNumber (HumanReadableSize) - (NSString *)toHumanReadableSize { float floatSize = [self floatValue]; if (floatSize < 1023) return ([NSString stringWithFormat:@"%1.0f bytes", floatSize]); floatSize = floatSize / 1024; if (floatSize < 1023) return ([NSString stringWithFormat:@"%1.1f KB", floatSize]); floatSize = floatSize / 1024; if (floatSize < 1023) return ([NSString stringWithFormat:@"%1.1f MB", floatSize]); floatSize = floatSize / 1024; return ([NSString stringWithFormat:@"%1.1f GB", floatSize]); } @end @implementation UIImage (systemIcons) + (UIImage *)imageFromSystemBarButton:(UIBarButtonSystemItem)systemItem :(UIColor *) color { // thanks to Renetik https://stackoverflow.com/a/49822488 UIToolbar *bar = UIToolbar.new; UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:systemItem target:nil action:nil]; [bar setItems:@[buttonItem] animated:NO]; [bar snapshotViewAfterScreenUpdates:YES]; for (UIView *view in [(id) buttonItem view].subviews) if ([view isKindOfClass:UIButton.class]) { UIImage *image = [((UIButton *) view).imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); //[color set]; [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } return nil; } @end @implementation NSString (md5) - (NSString *)md5 { const char *ptr = [self UTF8String]; unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH]; CC_MD5(ptr, (unsigned int)strlen(ptr), md5Buffer); NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [output appendFormat:@"%02x", md5Buffer[i]]; } return output; } - (BOOL)containsSubstring:(NSString *)str { if (UIDevice.currentDevice.systemVersion.doubleValue >= 8.0) { #pragma deploymate push "ignored-api-availability" return [self containsString:str]; #pragma deploymate pop } return ([self rangeOfString:str].location != NSNotFound); } @end @implementation ContactDisplay + (void)setDisplayNameLabel:(UILabel *)label forContact:(Contact *)contact { label.text = [FastAddressBook displayNameForContact:contact]; #if 0 NSString *lLastName = CFBridgingRelease(ABRecordCopyValue(contact, kABPersonLastNameProperty)); NSString *lLocalizedLastName = [FastAddressBook localizedLabel:lLastName]; if (lLocalizedLastName) { [label boldSubstring:lLocalizedLastName]; } #endif } + (void)setDisplayNameLabel:(UILabel *)label forAddress:(const LinphoneAddress *)addr { Contact *contact = [FastAddressBook getContactWithAddress:addr]; if (contact) { [ContactDisplay setDisplayNameLabel:label forContact:contact]; } else { label.text = [FastAddressBook displayNameForAddress:addr]; } } + (void)setDisplayNameLabel:(UILabel *)label forAddress:(const LinphoneAddress *)addr withAddressLabel:(UILabel*)addressLabel{ Contact *contact = [FastAddressBook getContactWithAddress:addr]; NSString *tmpAddress = nil; char *uri = linphone_address_as_string_uri_only(addr); if (contact) { [ContactDisplay setDisplayNameLabel:label forContact:contact]; tmpAddress = [NSString stringWithUTF8String:uri]; addressLabel.hidden = FALSE; } else { label.text = [FastAddressBook displayNameForAddress:addr]; if([LinphoneManager.instance lpConfigBoolForKey:@"display_phone_only" inSection:@"app"]) addressLabel.hidden = TRUE; else tmpAddress = [NSString stringWithUTF8String:uri]; } ms_free(uri); NSRange range = [tmpAddress rangeOfString:@";"]; if (range.location != NSNotFound) { tmpAddress = [tmpAddress substringToIndex:range.location]; } addressLabel.text = tmpAddress; } @end @implementation UIImage (squareCrop) - (UIImage *)squareCrop { // This calculates the crop area. size_t originalWidth = CGImageGetWidth(self.CGImage); size_t originalHeight = CGImageGetHeight(self.CGImage); size_t edge = MIN(originalWidth, originalHeight); float posX = (originalWidth - edge) / 2.0f; float posY = (originalHeight - edge) / 2.0f; CGRect rect = CGRectMake(posX, posY, edge, edge); // Create bitmap image from original image data, // using rectangle to specify desired crop area CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], rect); UIImage *img = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); return img; /* UIImage *ret = nil; CGRect cropSquare = CGRectMake(posX, posY, edge, edge); // CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], cropSquare); // ret = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; // // CGImageRelease(imageRef); CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, cropSquare); ret = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; CGImageRelease(imageRef); return ret;*/ } - (UIImage *)scaleToSize:(CGSize)size squared:(BOOL)squared { UIImage *scaledImage = self; if (squared) { // scaledImage = [self squareCrop]; size.width = size.height = MAX(size.width, size.height); } UIGraphicsBeginImageContext(size); [scaledImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return scaledImage; } + (UIImage *)resizeImage:(UIImage *)image withMaxWidth:(float)maxWidth andMaxHeight:(float)maxHeight { float actualHeight = image.size.height; float actualWidth = image.size.width; float imgRatio = actualWidth / actualHeight; float maxRatio = maxWidth / maxHeight; float compressionQuality = 1; if (actualHeight > maxHeight || actualWidth > maxWidth) { if (imgRatio < maxRatio) { imgRatio = maxHeight / actualHeight; actualWidth = imgRatio * actualWidth; actualHeight = maxHeight; } else if (imgRatio > maxRatio) { imgRatio = maxWidth / actualWidth; actualHeight = imgRatio * actualHeight; actualWidth = maxWidth; } else { actualHeight = maxHeight; actualWidth = maxWidth; } } CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight); UIGraphicsBeginImageContext(rect.size); [image drawInRect:rect]; UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); NSData *imageData = UIImageJPEGRepresentation(img, compressionQuality); UIGraphicsEndImageContext(); return [UIImage imageWithData:imageData]; } @end @implementation UIColor (LightAndDark) - (UIColor *)lumColor:(float)mult { float hsbH, hsbS, hsbB; float rgbaR, rgbaG, rgbaB, rgbaA; // Get RGB CGColorRef cgColor = [self CGColor]; CGColorSpaceRef cgColorSpace = CGColorGetColorSpace(cgColor); if (CGColorSpaceGetModel(cgColorSpace) != kCGColorSpaceModelRGB) { LOGW(@"Can't convert not RGB color"); return self; } else { const CGFloat *colors = CGColorGetComponents(cgColor); rgbaR = colors[0]; rgbaG = colors[1]; rgbaB = colors[2]; rgbaA = CGColorGetAlpha(cgColor); } RGB2HSL(rgbaR, rgbaG, rgbaB, &hsbH, &hsbS, &hsbB); hsbB = MIN(MAX(hsbB * mult, 0.0), 1.0); HSL2RGB(hsbH, hsbS, hsbB, &rgbaR, &rgbaG, &rgbaB); return [UIColor colorWithRed:rgbaR green:rgbaG blue:rgbaB alpha:rgbaA]; } - (UIColor *)adjustHue:(float)hm saturation:(float)sm brightness:(float)bm alpha:(float)am { float hsbH, hsbS, hsbB; float rgbaR, rgbaG, rgbaB, rgbaA; // Get RGB CGColorRef cgColor = [self CGColor]; CGColorSpaceRef cgColorSpace = CGColorGetColorSpace(cgColor); if (CGColorSpaceGetModel(cgColorSpace) != kCGColorSpaceModelRGB) { LOGW(@"Can't convert not RGB color"); return self; } else { const CGFloat *colors = CGColorGetComponents(cgColor); rgbaR = colors[0]; rgbaG = colors[1]; rgbaB = colors[2]; rgbaA = CGColorGetAlpha(cgColor); } RGB2HSL(rgbaR, rgbaG, rgbaB, &hsbH, &hsbS, &hsbB); hsbH = MIN(MAX(hsbH + hm, 0.0), 1.0); hsbS = MIN(MAX(hsbS + sm, 0.0), 1.0); hsbB = MIN(MAX(hsbB + bm, 0.0), 1.0); rgbaA = MIN(MAX(rgbaA + am, 0.0), 1.0); HSL2RGB(hsbH, hsbS, hsbB, &rgbaR, &rgbaG, &rgbaB); return [UIColor colorWithRed:rgbaR green:rgbaG blue:rgbaB alpha:rgbaA]; } - (UIColor *)lighterColor { return [self lumColor:1.3]; } - (UIColor *)darkerColor { return [self lumColor:0.75]; } @end @implementation UIImage (ForceDecode) + (UIImage *)decodedImageWithImage:(UIImage *)image { CGImageRef imageRef = image.CGImage; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate( NULL, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, // Just always return width * 4 will be enough CGImageGetWidth(imageRef) * 4, // System only supports RGB, set explicitly colorSpace, // Makes system don't need to do extra conversion when displayed. // NOTE: here we remove the alpha channel for performance. Most of the time, images loaded // from the network are jpeg with no alpha channel. As a TODO, finding a way to detect // if alpha channel is necessary would be nice. kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Little); CGColorSpaceRelease(colorSpace); if (!context) return nil; CGRect rect = (CGRect){CGPointZero, {CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}}; CGContextDrawImage(context, rect, imageRef); CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation]; CGImageRelease(decompressedImageRef); return decompressedImage; } @end @implementation UIImage (ResizeAndThumbnail) + (UIImage *)UIImageThumbnail:(UIImage *)image thumbSize:(CGFloat) tbSize { // Create a thumbnail version of the image for the event object. CGSize size = image.size; CGSize croppedSize; CGFloat offsetX = 0.0; CGFloat offsetY = 0.0; CGFloat actualTbSize = MAX(tbSize, MAX(size.height, size.width)); // check the size of the image, we want to make it // a square with sides the size of the smallest end if (size.width > size.height) { offsetX = (size.height - size.width) / 2; croppedSize = CGSizeMake(size.height, size.height); } else { offsetY = (size.width - size.height) / 2; croppedSize = CGSizeMake(size.width, size.width); } // Crop the image before resize CGRect clippedRect = CGRectMake(offsetX * -1, offsetY * -1, croppedSize.width, croppedSize.height); CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], clippedRect); UIImage *cropped = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); // Done cropping // Resize the image CGRect rect = CGRectMake(0, 0, actualTbSize, actualTbSize); UIGraphicsBeginImageContext(rect.size); [cropped drawInRect:rect]; UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // Done Resizing return thumbnail; } + (UIImage *)UIImageResize:(UIImage *)image toSize:(CGSize) newSize { CGImageRef newImage = [image CGImage]; CGSize originalSize = [image size]; float originalAspectRatio = originalSize.width / originalSize.height; // We resize in width and crop in height if (originalSize.width > newSize.width) { int height = newSize.width / originalAspectRatio; newImage = [UIImage resizeCGImage:newImage toWidth:newSize.width andHeight:height]; originalSize.height = height; } CGRect cropRect = CGRectMake(0, 0, newSize.width, newSize.height); if (newSize.height < originalSize.height) cropRect.origin.y = (originalSize.height - newSize.height)/2; newImage = CGImageCreateWithImageInRect(newImage, cropRect); UIImage *cropped = [UIImage imageWithCGImage:newImage]; CGImageRelease(newImage); return cropped; } + (CGImageRef)resizeCGImage:(CGImageRef)image toWidth:(int)width andHeight:(int)height { // create context, keeping original image properties CGColorSpaceRef colorspace = CGImageGetColorSpace(image); CGContextRef context = CGBitmapContextCreate(NULL, width, height, CGImageGetBitsPerComponent(image), CGImageGetBytesPerRow(image), colorspace, CGImageGetAlphaInfo(image)); CGColorSpaceRelease(colorspace); if(context == NULL) return nil; // draw image to context (resizing it) CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); // extract resulting image from context CGImageRef imgRef = CGBitmapContextCreateImage(context); CGContextRelease(context); return imgRef; } @end