// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2015-2020 Threema GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, // as published by the Free Software Foundation. // // 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #import #import "EditableAvatarView.h" #import "AppDelegate.h" #import "ModalPresenter.h" #import "UIDefines.h" #import "UIImage+Resize.h" #import "RectUtil.h" #import "BundleUtil.h" #import "AvatarMaker.h" #import @interface EditableAvatarView () @property UIImagePickerController *imagePicker; @property NSInteger takePhotoIndex; @property NSInteger chooseExistingIndex; @property NSData *currentImageData; @end @implementation EditableAvatarView - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self setupViews]; _isReceivedImage = NO; } return self; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setupViews]; _isReceivedImage = NO; } return self; } - (void)setImageData:(NSData *)imageData { _currentImageData = imageData; [self updateView]; } - (NSData *)imageData { return _currentImageData; } - (void)setCanDeleteImage:(BOOL)canDeleteImage { _canDeleteImage = canDeleteImage; [self updatePlaceholderView]; } - (void)setCanChooseImage:(BOOL)canChooseImage { _canChooseImage = canChooseImage; [self updatePlaceholderView]; } - (void)setupViews { _imageView = [[UIImageView alloc] initWithFrame:self.bounds]; [self addSubview:_imageView]; _pickImageLabel.userInteractionEnabled = YES; CGRect rect = CGRectInset(_imageView.frame, 10.0, 10.0); _pickImageLabel = [[UILabel alloc] initWithFrame:rect]; _pickImageLabel.numberOfLines = 3; _pickImageLabel.textColor = [Colors main]; _pickImageLabel.font = [UIFont systemFontOfSize:15.0]; _pickImageLabel.minimumScaleFactor = 0.6; _pickImageLabel.adjustsFontSizeToFitWidth = YES; _pickImageLabel.text = NSLocalizedString(@"pick image", nil); _pickImageLabel.textAlignment = NSTextAlignmentCenter; [self addSubview:_pickImageLabel]; UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tappedImage)]; [_imageView addGestureRecognizer:tapRecognizer]; _imageView.userInteractionEnabled = YES; UITapGestureRecognizer *tapLabelRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tappedImage)]; [_pickImageLabel addGestureRecognizer:tapLabelRecognizer]; _pickImageLabel.userInteractionEnabled = YES; if (@available(iOS 11.0, *)) { _imageView.accessibilityIgnoresInvertColors = true; } self.backgroundColor = [UIColor clearColor]; [self updateView]; } - (void)updateView { if (_currentImageData) { UIImage *image = [UIImage imageWithData:_currentImageData]; _imageView.image = [AvatarMaker maskImage: image]; _pickImageLabel.hidden = YES; } else { _imageView.image = [AvatarMaker avatarWithString:nil size:(_imageView.frame.size.height * [UIScreen mainScreen].scale)]; _pickImageLabel.hidden = NO; } } - (void)updatePlaceholderView { _imageView.userInteractionEnabled = _canDeleteImage || _canChooseImage; _pickImageLabel.userInteractionEnabled = _canDeleteImage || _canChooseImage; _pickImageLabel.textColor = _canDeleteImage || _canChooseImage ? [Colors main] : [Colors fontPlaceholder]; } - (void)tappedImage { if (!_canDeleteImage && !_canChooseImage) { return; } [_presentingViewController resignFirstResponder]; if (!_isReceivedImage) { /* allow the user to choose a new image or clear the current one */ UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] && _canChooseImage) { [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"take_photo", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { _imagePicker = [[UIImagePickerController alloc] init]; _imagePicker.delegate = self; _imagePicker.allowsEditing = NO; _imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; _imagePicker.mediaTypes = @[(NSString*)kUTTypeImage]; CGRect rect = [_presentingViewController.view convertRect:self.frame fromView:self.superview]; [ModalPresenter present:_imagePicker on:_presentingViewController fromRect:rect inView:_presentingViewController.view]; }]]; } if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] && _canChooseImage) { [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"choose_existing", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { _imagePicker = [[UIImagePickerController alloc] init]; _imagePicker.delegate = self; _imagePicker.allowsEditing = NO; _imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; _imagePicker.mediaTypes = @[(NSString*)kUTTypeImage]; CGRect rect = [_presentingViewController.view convertRect:self.frame fromView:self.superview]; [ModalPresenter present:_imagePicker on:_presentingViewController fromRect:rect inView:_presentingViewController.view]; }]]; } if (_currentImageData != nil && _canDeleteImage) { [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"delete_photo", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) { _currentImageData = nil; [self updateView]; [_delegate avatarImageUpdated:_currentImageData]; }]]; } [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { }]]; if (SYSTEM_IS_IPAD == YES || !_presentingViewController.tabBarController) { CGRect rect = [_presentingViewController.view convertRect:self.frame fromView:self.superview]; actionSheet.popoverPresentationController.sourceRect = rect; actionSheet.popoverPresentationController.sourceView = self; } [[[AppDelegate sharedAppDelegate] currentTopViewController] presentViewController:actionSheet animated:YES completion:nil]; } } #pragma mark - Image picker controller delegate - (void)imagePickerController:(UIImagePickerController *)_picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *image = info[UIImagePickerControllerEditedImage] ?: info[UIImagePickerControllerOriginalImage]; RSKImageCropViewController *imageCropViewController = [[RSKImageCropViewController alloc] initWithImage:image]; imageCropViewController.delegate = self; imageCropViewController.cropMode = RSKImageCropModeCircle; imageCropViewController.avoidEmptySpaceAroundImage = YES; CGSize maskSize = CGSizeMake(kContactImageSize, kContactImageSize); imageCropViewController.preferredContentSize = CGSizeMake(maskSize.width, maskSize.height); [ModalPresenter dismissPresentedControllerOn:_presentingViewController animated:YES]; CGRect rect = [_presentingViewController.view convertRect:self.frame fromView:self.superview]; [ModalPresenter present:imageCropViewController on:_presentingViewController fromRect:rect inView:_presentingViewController.view]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)_picker { [ModalPresenter dismissPresentedControllerOn:_presentingViewController animated:YES]; _imagePicker = nil; [_delegate avatarImageUpdated:_currentImageData]; } #pragma mark - RSKImageCropViewControllerDelegate // Crop image has been canceled. - (void)imageCropViewControllerDidCancelCrop:(RSKImageCropViewController *)controller { [ModalPresenter dismissPresentedControllerOn:_presentingViewController animated:YES]; _imagePicker = nil; [_delegate avatarImageUpdated:_currentImageData]; } // The original image has been cropped. Additionally provides a rotation angle used to produce image. - (void)imageCropViewController:(RSKImageCropViewController *)controller didCropImage:(UIImage *)croppedImage usingCropRect:(CGRect)cropRect rotationAngle:(CGFloat)rotationAngle { UIImage *resizedImage = [croppedImage resizedImageWithContentMode:UIViewContentModeScaleAspectFit bounds:CGSizeMake(kContactImageSize, kContactImageSize) interpolationQuality:kCGInterpolationHigh]; _currentImageData = UIImageJPEGRepresentation(resizedImage, 0.7f); [self updateView]; [_delegate avatarImageUpdated:_currentImageData]; [ModalPresenter dismissPresentedControllerOn:_presentingViewController animated:YES]; } #pragma mark - Accessibility - (BOOL)isAccessibilityElement { return YES; } - (NSString *)accessibilityLabel { if (_currentImageData) { NSString *image = [BundleUtil localizedStringForKey:@"image"]; NSString *action = [BundleUtil localizedStringForKey:@"tap_to_change"]; return [NSString stringWithFormat:@"%@, %@", image, action]; } else { return _pickImageLabel.text; } } @end