// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// Threema iOS Client
// Copyright (c) 2014-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 "ChatViewHeader.h"
#import "Contact.h"
#import "ImageData.h"
#import "MWPhotoBrowser.h"
#import "ImageMessage.h"
#import "NibUtil.h"
#import "ChatViewController.h"
#import "EntityManager.h"
#import "RectUtil.h"
#import "BallotListTableViewController.h"
#import "AvatarMaker.h"
#import "HairlineView.h"
#import "VideoCaptionView.h"
#import "FileCaptionView.h"
#import "PhotoCaptionView.h"
#import "MediaBrowserPhoto.h"
#import "MediaBrowserVideo.h"
#import "MediaBrowserFile.h"
#import "StatusNavigationBar.h"
#import "UIDefines.h"
#import "ChatViewSearchHeader.h"
#import "BundleUtil.h"
#import "UIImage+ColoredImage.h"
#import "FileMessagePreview.h"
#import "PreviewActionNavigationController.h"
#import "FeatureMask.h"
#import "ServerConnector.h"
#import "UserSettings.h"
#import "Utils.h"
#import "ImageUtils.h"
#import "Threema-Swift.h"
#define CONVERSATION_KEYPATHS @[@"contact.verificationLevel", @"contact.cnContactId", @"contact.imageData", @"messages", @"displayName", @"groupImage"]
@interface ChatViewHeader ()
@property NSArray *mediaMessages;
@property MWPhotoBrowser *photoBrowser;
@property NSMutableArray *callNumbers;
@property NSUInteger deletePhotoIndex;
@property UIScrollView *groupImagesView;
@property EntityManager *entityManager;
@property NSMutableSet *photoSelection;
@property ChatViewSearchHeader *searchView;
@property UIVisualEffectView *effectView;
@property FileMessagePreview *fileMessagPreview;
@property MaterialShowcase *showCase;
@end
@implementation ChatViewHeader
- (void)awakeFromNib {
[self fixLinePosition:self.horizontalDividerLine1];
[self fixLinePosition:self.horizontalDividerLine2];
[self setupButtons];
_callButton.accessibilityLabel = NSLocalizedString(@"call", nil);
_entityManager = [[EntityManager alloc] init];
_searchButton.accessibilityLabel = [BundleUtil localizedStringForKey:@"search"];
_notificationsSettingsButton.accessibilityLabel = NSLocalizedString(@"pushSetting_title", @"");
_threemaTypeIcon.image = [Utils threemaTypeIcon];
[self setupColors];
[super awakeFromNib];
}
- (void)setupColors {
_callButton.tintColor = [Colors main];
_mediaButton.tintColor = [Colors main];
_ballotsButton.tintColor = [Colors main];
UIImage *image = [UIImage imageNamed:@"Search" inColor:[Colors main]];
[_searchButton setImage:image forState:UIControlStateNormal];
PushSetting *pushSetting = [PushSetting findPushSettingForConversation:_conversation];
UIImage *pushSettingIcon = [UIImage imageNamed:@"Bell"];
if (pushSetting) {
pushSettingIcon = [pushSetting imageForPushSetting];
}
[_notificationsSettingsButton setImage:[pushSettingIcon imageWithTint:[Colors main]] forState:UIControlStateNormal];
UIImage *phoneImage = [ImageUtils imageWithImage:[UIImage imageNamed:@"ThreemaPhone" inColor:[Colors main]] scaledToSize:CGSizeMake(30, 30)];
[_callButton setImage:phoneImage forState:UIControlStateNormal];
_callButton.imageView.contentMode = UIViewContentModeCenter;
_callButton.enabled = [UserSettings sharedUserSettings].enableThreemaCall && is64Bit == 1;
_verticalDividerLine1.backgroundColor = [Colors hairline];
_verticalDividerLine2.backgroundColor = [Colors hairline];
_verticalDividerLine3.backgroundColor = [Colors hairline];
_horizontalDividerLine1.backgroundColor = [Colors hairline];
_horizontalDividerLine2.backgroundColor = [Colors hairline];
if (@available(iOS 11.0, *)) {
_avatarButton.accessibilityIgnoresInvertColors = true;
}
[self addBlur];
}
- (void)refresh {
[self setupColors];
[self setup];
}
- (void)cancelSearch {
[_searchView cancelAction:self];
}
- (void)dealloc {
[self removeObservers];
}
- (void)layoutSubviews {
CGFloat currentHeight = CGRectGetHeight(self.frame);
CGFloat height = [self getHeight];
self.frame = [RectUtil setHeightOf:self.frame height:height];
if (_chatViewController.searching == NO) {
_mainView.hidden = NO;
_horizontalDividerLine1.hidden = NO;
[self layoutButtons];
} else {
_mainView.hidden = YES;
_optionalButtonsView.hidden = YES;
_horizontalDividerLine1.hidden = YES;
}
if (currentHeight != height && _delegate != nil) {
[_delegate didChangeHeightTo:height];
}
_searchView.frame = [RectUtil setWidthOf:_searchView.frame width:self.frame.size.width];
}
- (void)layoutButtons {
if ([self showOptionalButtons] == NO) {
_optionalButtonsView.hidden = YES;
} else {
_optionalButtonsView.hidden = NO;
CGFloat x = CGRectGetMaxX(_notificationsSettingsButton.frame);
CGFloat buttonsTotalWidth = CGRectGetWidth(_optionalButtonsView.frame) - _searchButton.frame.size.width - _notificationsSettingsButton.frame.size.width;
if (_mediaButton.hidden) {
_ballotsButton.frame = [RectUtil setWidthOf:_ballotsButton.frame width:buttonsTotalWidth];
_ballotsButton.frame = [RectUtil setXPositionOf:_mediaButton.frame x:x];
} else if (_ballotsButton.hidden) {
_mediaButton.frame = [RectUtil setWidthOf:_mediaButton.frame width:buttonsTotalWidth];
_mediaButton.frame = [RectUtil setXPositionOf:_mediaButton.frame x:x];
} else {
CGFloat buttonWidth = round(buttonsTotalWidth/2.0);
_mediaButton.frame = [RectUtil setWidthOf:_mediaButton.frame width:buttonWidth];
_mediaButton.frame = [RectUtil setXPositionOf:_mediaButton.frame x:x];
_ballotsButton.frame = [RectUtil setWidthOf:_ballotsButton.frame width:buttonWidth];
CGFloat ballotButtonOffset = CGRectGetMaxX(_mediaButton.frame);
_ballotsButton.frame = [RectUtil setPositionOf:_ballotsButton.frame x: ballotButtonOffset y: _ballotsButton.frame.origin.y];
CGFloat verticalDividerLine3Offset = CGRectGetMaxX(_mediaButton.frame);
_verticalDividerLine3.frame = [RectUtil setPositionOf:_verticalDividerLine3.frame x: verticalDividerLine3Offset y: _verticalDividerLine3.frame.origin.y];
}
_verticalDividerLine3.hidden = _ballotsButton.hidden || _mediaButton.hidden;
CGFloat ballotTextRightEdge = CGRectGetMaxX([_ballotsButton titleRectForContentRect:_ballotsButton.frame]);
CGFloat badgeXOffset = fminf(ballotTextRightEdge, CGRectGetMaxX(_ballotsButton.frame) - CGRectGetWidth(_ballotBadge.frame));
if (_mediaButton.hidden == true) {
badgeXOffset = badgeXOffset + (_ballotBadge.frame.size.width/2);
}
_ballotBadge.frame = [RectUtil setXPositionOf:_ballotBadge.frame x:badgeXOffset];
}
if (_conversation.isGroup == true) {
GroupProxy *group = [GroupProxy groupProxyForConversation:_conversation];
_notificationsSettingsButton.enabled = group.isSelfMember;
} else {
_notificationsSettingsButton.enabled = true;
}
}
- (BOOL)resignFirstResponder {
return [_searchView resignFirstResponder];
}
- (BOOL)showOptionalButtons {
return YES;
}
- (CGFloat)getHeight {
if (_searchView) {
return _searchView.frame.size.height;
} else if ([self showOptionalButtons]) {
return CGRectGetMaxY(_optionalButtonsView.frame);
} else {
return CGRectGetHeight(_mainView.frame);
}
}
- (void)fixLinePosition:(UIView*)view {
CGRect frame = view.frame;
frame.origin.y -= 0.5;
view.frame = frame;
}
- (void)addBlur {
if (_effectView) {
[_effectView removeFromSuperview];
_effectView = nil;
}
UIBlurEffectStyle blurStyle = UIBlurEffectStyleExtraLight;
switch ([Colors getTheme]) {
case ColorThemeDark:
case ColorThemeDarkWork:
blurStyle = UIBlurEffectStyleDark;
break;
case ColorThemeLight:
case ColorThemeLightWork:
case ColorThemeUndefined:
break;
}
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:blurStyle];
_effectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
_effectView.frame = self.bounds;
_effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.wrapperView.backgroundColor = [UIColor clearColor];
[_effectView.contentView addSubview:self.wrapperView];
[self addSubview:_effectView];
}
- (void)setup {
if (_conversation.groupId != nil) {
[self setupForGroup];
} else {
[self setupForIndividual];
}
[self updateBallotButton];
[self updateMediaButton];
[self checkEnableCallButtons];
}
- (void)setupButtons {
[_avatarButton addTarget:self action:@selector(showContactDetails) forControlEvents:UIControlEventTouchUpInside];
[_verificationLevel addTarget:self action:@selector(showContactDetails) forControlEvents:UIControlEventTouchUpInside];
[_mediaButton setTitle:NSLocalizedString(@"media_overview", nil) forState:UIControlStateNormal];
[_ballotsButton setTitle:NSLocalizedStringFromTable(@"ballots", @"Ballot", nil) forState:UIControlStateNormal];
}
- (void)updateMediaButton {
NSInteger numMedia = [_entityManager.entityFetcher countMediaMessagesForConversation:_conversation];
_mediaButton.hidden = numMedia < 1;
[self setNeedsLayout];
}
- (void)updateBallotButton {
NSInteger numBallots = [_entityManager.entityFetcher countBallotsForConversation:_conversation];
if (numBallots > 0) {
NSInteger numOpenBallots = [_entityManager.entityFetcher countOpenBallotsForConversation:_conversation];
if (numOpenBallots > 0) {
_ballotBadge.value = numOpenBallots;
_ballotBadge.hidden = NO;
} else {
_ballotBadge.hidden = YES;
}
_ballotsButton.hidden = NO;
} else {
_ballotBadge.hidden = YES;
_ballotsButton.hidden = YES;
}
[self setNeedsLayout];
}
- (void)setupForGroup {
_verificationLevel.hidden = YES;
_callButton.hidden = YES;
_avatarButton.hidden = YES;
if (_groupImagesView) {
[_groupImagesView removeFromSuperview];
}
UIView *imageContainer = [self makeContactImagesForGroup];
_groupImagesView = [[UIScrollView alloc] initWithFrame: _mainView.bounds];
_groupImagesView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[_groupImagesView addSubview: imageContainer];
_groupImagesView.contentSize = imageContainer.bounds.size;
_groupImagesView.accessibilityLabel = [_conversation sortedMemberNames];
_groupImagesView.accessibilityTraits = UIAccessibilityTraitButton;
_groupImagesView.isAccessibilityElement = YES;
if (@available(iOS 11.0, *)) {
_groupImagesView.accessibilityIgnoresInvertColors = true;
}
//otherwise containing scrollview does not get this event
_groupImagesView.scrollsToTop = NO;
UITapGestureRecognizer *photoTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showContactDetails)];
[_groupImagesView addGestureRecognizer:photoTapGesture];
_groupImagesView.accessibilityIdentifier = @"GroupImageView";
[_mainView addSubview:_groupImagesView];
_threemaTypeIcon.hidden = YES;
[self setNeedsLayout];
}
- (UIView *)makeContactImagesForGroup {
CGFloat width = _avatarButton.bounds.size.width;
CGFloat margin = 6.0f;
NSInteger memberCount = [_conversation.members count];
CGFloat height = _mainView.bounds.size.height;
CGFloat totalWidth = memberCount * width + (memberCount + 1) * margin;
CGRect containerRect = CGRectMake(0.0, 0.0, totalWidth, height);
UIView *imageContainer = [[UIView alloc] initWithFrame: containerRect];
CGRect imageRect = CGRectMake(margin, 0.0, width, width);
imageRect = [RectUtil rect:imageRect centerVerticalIn:imageContainer.frame];
for (Contact *contact in _conversation.sortedMembers) {
if (contact.state.intValue == kStateInvalid)
continue;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageRect];
imageView.image = [[AvatarMaker sharedAvatarMaker] avatarForContact:contact size:width masked:YES];
[imageContainer addSubview: imageView];
if (![Utils hideThreemaTypeIconForContact:contact]) {
UIImageView *littleThreemaTypeIcon = [[UIImageView alloc] initWithImage:[Utils threemaTypeIcon]];
littleThreemaTypeIcon.frame = CGRectMake(imageRect.origin.x - 3.0, (imageRect.origin.y + imageRect.size.height) - (imageRect.size.width / 2.5) - 1.0, imageRect.size.width / 2.5, imageRect.size.height / 2.5);
[imageContainer addSubview:littleThreemaTypeIcon];
}
imageRect = [RectUtil offsetRect:imageRect byX:width + margin byY:0.0];
}
return imageContainer;
}
- (void)setupForIndividual {
[_verificationLevel setImage:[_conversation.contact verificationLevelImage] forState:UIControlStateNormal];
_verificationLevel.hidden = NO;
_verificationLevel.accessibilityLabel = [_conversation.contact verificationLevelAccessibilityLabel];
[_avatarButton setImage:[[AvatarMaker sharedAvatarMaker] avatarForContact:_conversation.contact size:_avatarButton.frame.size.width masked:YES] forState:UIControlStateNormal];
_avatarButton.accessibilityLabel = _conversation.contact.displayName;
_callButton.alpha = 1.0;
_callButton.enabled = [UserSettings sharedUserSettings].enableThreemaCall && is64Bit == 1;
_threemaTypeIcon.hidden = [Utils hideThreemaTypeIconForContact:_conversation.contact];
}
- (void)setConversation:(Conversation *)newConversation {
if (_conversation != newConversation) {
[self removeObservers];
_conversation = newConversation;
[self setup];
[self addObservers];
}
}
- (void)addObservers {
for (NSString *keyPath in CONVERSATION_KEYPATHS) {
[_conversation addObserver:self forKeyPath:keyPath options:0 context:nil];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(newMessageReceived:) name:@"ThreemaNewMessageReceived" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(avatarChanged:) name:kNotificationIdentityAvatarChanged object:nil];
// Listen for connection status changes so we can enable/disable the call button
[[ServerConnector sharedServerConnector] addObserver:self forKeyPath:@"connectionState" options:0 context:nil];
}
- (void)removeObservers {
for (NSString *keyPath in CONVERSATION_KEYPATHS) {
[_conversation removeObserver:self forKeyPath:keyPath];
}
@try {
[[ServerConnector sharedServerConnector] removeObserver:self forKeyPath:@"connectionState"];
} @catch(id anException) {
// ServerConnector observer does not exist
}
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
- (void)cleanupMedia {
_mediaMessages = nil;
_photoBrowser = nil;
}
- (void)checkEnableCallButtons {
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FASTLANE_SNAPSHOT"]) {
_callButton.enabled = YES;
} else {
_callButton.enabled = [UserSettings sharedUserSettings].enableThreemaCall && is64Bit == 1 && [ServerConnector sharedServerConnector].connectionState == ConnectionStateLoggedIn;
}
}
- (NSUInteger)mediaSelectionCount {
return _photoSelection.count;
}
- (NSSet *)mediaPhotoSelection {
return _photoSelection;
}
- (void)startVoipCall:(BOOL)withVideo {
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FASTLANE_SNAPSHOT"]) {
VoIPCallUserAction *action = [[VoIPCallUserAction alloc] initWithAction:withVideo ? ActionCallWithVideo : ActionCall contact:self.conversation.contact callId:nil completion:nil];
[[VoIPCallStateManager shared] processUserAction:action];
} else {
if ([UserSettings sharedUserSettings].enableThreemaCall && is64Bit == 1) {
[_chatViewController startVoipCall:withVideo];
}
}
}
- (void)showThreemaVideoCallInfo {
if (_conversation.isGroup == false) {
if ([UserSettings sharedUserSettings].videoCallInChatInfoShown == false && [UserSettings sharedUserSettings].enableVideoCall && self.conversation.contact.isVideoCallAvailable) {
if (_showCase == nil) {
_showCase = [[MaterialShowcase alloc] init];
[_showCase setTargetViewWithView:_callButton];
_showCase.primaryText = [BundleUtil localizedStringForKey:@"call_threema_video_in_chat_info_title"];
_showCase.secondaryText = [BundleUtil localizedStringForKey:@"call_threema_video_in_chat_info_description"];
_showCase.backgroundPromptColor = [Colors main];
_showCase.backgroundPromptColorAlpha = 0.93;
_showCase.primaryTextSize = 24.0;
_showCase.secondaryTextSize = 18.0;
_showCase.primaryTextColor = [Colors white];
_showCase.delegate = self;
}
if (!_chatViewController.showHeader) {
[_chatViewController showHeaderWithDuration:0.3 completion:^(BOOL finished) {
[_showCase showWithAnimated:true hasShadow:true hasSkipButton:false completion:nil];
}];
} else {
[_showCase showWithAnimated:true hasShadow:true hasSkipButton:false completion:nil];
}
}
}
}
#pragma mark - actions
- (IBAction)callAction:(id)sender {
[self startVoipCall:false];
}
- (IBAction)videoCallAction:(id)sender {
[self startVoipCall:true];
}
- (IBAction)mediaAction:(id)sender {
[self showPhotoBrowser];
}
- (IBAction)ballotAction:(id)sender {
UIViewController *viewController = [BallotListTableViewController ballotListViewControllerForConversation: _conversation];
[_chatViewController.navigationController pushViewController:viewController animated:YES];
}
- (IBAction)searchAction:(id)sender {
[self showSearchView:YES];
}
- (IBAction)notificationsSettingsAction:(id)sender {
[_chatViewController openPushSettings];
}
- (void)showSearchView:(BOOL)show {
if (show) {
_searchView = (ChatViewSearchHeader *)[NibUtil loadViewFromNibWithName:@"ChatViewSearchHeader"];
_searchView.chatViewController = _chatViewController;
_searchView.delegate = self;
[_wrapperView addSubview:_searchView];
[self setNeedsLayout];
_chatViewController.searching = YES;
[_searchView becomeFirstResponder];
} else {
_chatViewController.searching = NO;
[_searchView resignFirstResponder];
[_searchView removeFromSuperview];
_searchView = nil;
[self setNeedsLayout];
}
}
- (void)showContactDetails {
if (_conversation.groupId != nil) {
[_chatViewController performSegueWithIdentifier:@"ShowGroupInfo" sender:_chatViewController];
} else {
[_chatViewController performSegueWithIdentifier:@"ShowContact" sender:_chatViewController];
}
}
- (void)prepareMediaMessages {
NSArray *imageMessages = [_entityManager.entityFetcher imageMessagesForConversation: _conversation];
NSArray *videoMessages = [_entityManager.entityFetcher videoMessagesForConversation: _conversation];
NSArray *fileMessages = [_entityManager.entityFetcher fileMessagesForConversation: _conversation];
NSMutableArray *allMediaMessages = [NSMutableArray arrayWithArray:imageMessages];
[allMediaMessages addObjectsFromArray:videoMessages];
[allMediaMessages addObjectsFromArray:fileMessages];
_mediaMessages = [allMediaMessages sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(BaseMessage *msg1, BaseMessage *msg2) {
return [msg1.date compare:msg2.date];
}];
}
- (UIViewController *)getPhotoBrowserAtMessage:(BaseMessage*)msg forPeeking:(BOOL)peeking {
[self prepareMediaMessages];
NSUInteger initialIndex = [_mediaMessages indexOfObject:msg];
if (initialIndex == NSNotFound) {
initialIndex = 0;
}
[self setupPhotoBrowser];
_photoBrowser.enableSwipeToDismiss = YES;
_photoBrowser.currentPhotoIndex = initialIndex;
if (peeking) {
_photoBrowser.peeking = YES;
if (@available(iOS 13.0, *)) {
return _photoBrowser;
}
}
UINavigationController *navigationController = [[PreviewActionNavigationController alloc] initWithRootViewController:_photoBrowser];
return navigationController;
}
- (void)showPhotoBrowser {
[self prepareMediaMessages];
[self setupPhotoBrowser];
_photoBrowser.startOnGrid = [_mediaMessages count] > 1;
[_photoBrowser setCurrentPhotoIndex:[_mediaMessages count]];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:_photoBrowser];
navigationController.modalPresentationStyle = UIModalPresentationFullScreen;
[_chatViewController presentViewController:navigationController animated:YES completion:nil];
}
- (void)setupPhotoBrowser {
_photoBrowser = [[MWPhotoBrowser alloc] initWithDelegate:self];
_photoBrowser.displayDeleteButton = YES;
_photoSelection = [NSMutableSet set];
}
- (NSURL*)makeTelUrlForPhone:(NSString*)phoneNumber {
return [NSURL URLWithString:[NSString stringWithFormat:@"tel:%@", [phoneNumber stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]]];
}
#pragma mark - key value observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
dispatch_async(dispatch_get_main_queue(), ^{
if (object == _conversation) {
[self setup];
}
if (object == [ServerConnector sharedServerConnector] && [keyPath isEqualToString:@"connectionState"]) {
[self checkEnableCallButtons];
}
});
}
- (void)newMessageReceived:(NSNotification*)notification {
[self setup];
}
#pragma mark - Photo browser delegate
- (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser {
return _mediaMessages.count;
}
- (id)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index {
return [self mwPhotoAtIndex:index forThumbnail:NO];
}
- (id)photoBrowser:(MWPhotoBrowser *)photoBrowser thumbPhotoAtIndex:(NSUInteger)index {
id photo = [self mwPhotoAtIndex:index forThumbnail:YES];
[photo loadUnderlyingImageAndNotify]; // ensure the underlying image is set to keep loading indicator from appearing
return photo;
}
- (id)mwPhotoAtIndex:(NSUInteger)index forThumbnail:(BOOL)thumbnail {
id media = nil;
if (index < _mediaMessages.count) {
BaseMessage *message = _mediaMessages[index];
if ([message isKindOfClass:[VideoMessage class]]) {
MediaBrowserVideo *video = [MediaBrowserVideo videoWithThumbnail: ((VideoMessage *)message).thumbnail.uiImage];
video.delegate = self;
video.sourceReference = (VideoMessage *)message;
video.caption = [DateFormatter shortStyleDateTime:message.remoteSentDate];
media = video;
} else if ([message isKindOfClass:[ImageMessage class]]) {
MediaBrowserPhoto *photo = [MediaBrowserPhoto photoWithImageMessage:(ImageMessage*)message thumbnail:thumbnail];
photo.caption = [DateFormatter shortStyleDateTime:message.remoteSentDate];
media = photo;
} else if ([message isKindOfClass:[FileMessage class]]) {
MediaBrowserFile *file;
file = [MediaBrowserFile fileWithFileMessage:(FileMessage *)message thumbnail:thumbnail];
file.delegate = self;
file.caption = [DateFormatter shortStyleDateTime:message.remoteSentDate];
media = file;
}
}
return media;
}
- (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index {
id media = [photoBrowser photoAtIndex: index];
if ([media isKindOfClass:[MediaBrowserVideo class]]) {
VideoCaptionView *videoCaption = [[VideoCaptionView alloc] initWithPhoto:media];
return videoCaption;
} else if ([media isKindOfClass:[MediaBrowserPhoto class]]) {
PhotoCaptionView *photoCaptionView = [[PhotoCaptionView alloc] initWithPhoto:media];
return photoCaptionView;
} else if ([media isKindOfClass:[MediaBrowserFile class]]) {
FileCaptionView *fileCaptionView = [[FileCaptionView alloc] initWithPhoto:media];
return fileCaptionView;
} else {
return nil;
}
}
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser deleteButton:(UIBarButtonItem *)deleteButton pressedForPhotoAtIndex:(NSUInteger)index {
_deletePhotoIndex = index;
id media = [photoBrowser photoAtIndex: index];
NSString *deleteButtonTitle = nil;
if ([media isKindOfClass:[MediaBrowserVideo class]]) {
deleteButtonTitle = NSLocalizedString(@"delete_video", nil);
} else if ([media isKindOfClass:[MediaBrowserPhoto class]]) {
deleteButtonTitle = NSLocalizedString(@"delete_photo", nil);
} else if ([media isKindOfClass:[MediaBrowserFile class]]) {
deleteButtonTitle = NSLocalizedString(@"delete_file", nil);
}
UIAlertController *deletePhotoActionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[deletePhotoActionSheet addAction:[UIAlertAction actionWithTitle:deleteButtonTitle style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {
[_entityManager performSyncBlockAndSafe:^{
ImageMessage *imageMessage = _mediaMessages[_deletePhotoIndex];
imageMessage.conversation = nil;
[[_entityManager entityDestroyer] deleteObjectWithObject:imageMessage];
[_chatViewController updateConversationLastMessage];
}];
[_chatViewController updateConversation];
[self prepareMediaMessages];
if (_mediaMessages.count == 0) {
[_chatViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[_photoBrowser reloadData:true];
}
}]];
[deletePhotoActionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
if (SYSTEM_IS_IPAD) {
deletePhotoActionSheet.popoverPresentationController.barButtonItem = deleteButton;
}
[photoBrowser presentViewController:deletePhotoActionSheet animated:YES completion:nil];
}
- (BOOL)photoBrowser:(MWPhotoBrowser *)photoBrowser isPhotoSelectedAtIndex:(NSUInteger)index {
return [_photoSelection containsObject:[NSNumber numberWithInteger:index]];
}
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index selectedChanged:(BOOL)selected {
if (selected) {
[_photoSelection addObject:[NSNumber numberWithInteger:index]];
} else {
[_photoSelection removeObject:[NSNumber numberWithInteger:index]];
}
}
- (void)photoBrowserResetSelection:(MWPhotoBrowser *)photoBrowser {
[_photoSelection removeAllObjects];
}
- (void)photoBrowserSelectAll:(MWPhotoBrowser *)photoBrowser {
[_photoSelection removeAllObjects];
for (int i = 0; i < _mediaMessages.count; i++) {
[_photoSelection addObject:[NSNumber numberWithInt:i]];
}
}
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser deleteButton:(UIBarButtonItem *)deleteButton {
if (_photoSelection.count > 0) {
_chatViewController.deleteMediaTotal = (int)_photoSelection.count;
[_entityManager performSyncBlockAndSafe:^{
[_photoSelection enumerateObjectsUsingBlock:^(NSNumber *index, BOOL * _Nonnull stop) {
ImageMessage *imageMessage = _mediaMessages[[index integerValue]];
imageMessage.conversation = nil;
[[_entityManager entityDestroyer] deleteObjectWithObject:imageMessage];
}];
[_chatViewController updateConversationLastMessage];
}];
} else {
_chatViewController.deleteMediaTotal = (int)[self numberOfPhotosInPhotoBrowser:photoBrowser];
[_entityManager performSyncBlockAndSafe:^{
for (int i = 0; i < [self numberOfPhotosInPhotoBrowser:photoBrowser]; i++ ) {
ImageMessage *imageMessage = _mediaMessages[i];
imageMessage.conversation = nil;
[[_entityManager entityDestroyer] deleteObjectWithObject:imageMessage];
};
[_chatViewController updateConversationLastMessage];
}];
}
[_chatViewController updateConversation];
[self prepareMediaMessages];
if (_mediaMessages.count == 0) {
[_chatViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[_photoBrowser finishedDeleteMedia];
}
}
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser actionButtonPressedForPhotoAtIndex:(NSUInteger)index {
MWPhoto *item;
if ([_mediaMessages[index] isKindOfClass:[FileMessage class]]) {
FileMessage *fileMessage = _mediaMessages[index];
if (fileMessage.data != nil) {
item = [photoBrowser photoAtIndex:index];
}
}
else if ([_mediaMessages[index] isKindOfClass:[ImageMessage class]]) {
ImageMessage *imageMessage = _mediaMessages[index];
if (imageMessage.image != nil) {
item = [photoBrowser photoAtIndex:index];
}
}
else if ([_mediaMessages[index] isKindOfClass:[VideoMessage class]]) {
VideoMessage *videoMessage = _mediaMessages[index];
if (videoMessage.video != nil) {
item = [photoBrowser photoAtIndex:index];
}
}
if (item != nil) {
[photoBrowser shareMedia:item];
}
else {
[photoBrowser showAlert:@"" message:[BundleUtil localizedStringForKey:@"media_file_not_found"]];
}
}
#pragma mark - MWVideoDelegate
- (void)playVideo:(MediaBrowserVideo *)video {
VideoMessage *message = (VideoMessage *)video.sourceReference;
if (message) {
[_chatViewController videoMessageTapped:message];
}
}
#pragma mark - MWFileDelegate
- (void)showFile:(FileMessage *)fileMessage {
if (fileMessage) {
_fileMessagPreview = [FileMessagePreview fileMessagePreviewFor:fileMessage];
[_fileMessagPreview showOn:_photoBrowser];
}
}
- (void)playFileVideo:(FileMessage *)fileMessage {
if (fileMessage) {
[_chatViewController fileVideoMessageTapped:fileMessage];
}
}
- (void)toggleControls {
[_photoBrowser toggleControls];
}
#pragma mark - ChatViewSearchHeaderDelegate
- (void)didCancelSearch {
[self showSearchView:NO];
}
#pragma mark - MaterialShowCaseDelegate
- (void)showCaseDidDismissWithShowcase:(MaterialShowcase *)showcase didTapTarget:(BOOL)didTapTarget {
[[UserSettings sharedUserSettings] setVideoCallInChatInfoShown:true];
}
#pragma mark - Notification
- (void)avatarChanged:(NSNotification*)notification
{
if (notification.object && [self needsUpdateAvatarsForNotification:notification]) {
[self setup];
}
}
- (BOOL)needsUpdateAvatarsForNotification:(NSNotification *)notification {
if (_conversation.isGroup) {
for (Contact *contact in _conversation.members) {
if ([contact.identity isEqualToString:notification.object]) {
return YES;
}
}
return NO;
} else {
return [_conversation.contact.identity isEqualToString:notification.object];
}
}
@end