123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2017-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 <https://www.gnu.org/licenses/>.
- #import "ChatCallMessageCell.h"
- #import "SystemMessage.h"
- #import "ZSWTappableLabel.h"
- #import "UILabel+Markup.h"
- #import "ChatDefines.h"
- #import "UserSettings.h"
- #import "UIImage+ColoredImage.h"
- #import "ImageUtils.h"
- #import "QBPopupMenuItem.h"
- #import "QBPopupMenu.h"
- #import "Contact.h"
- #import "UIDefines.h"
- #import "TextStyleUtils.h"
- #import "BundleUtil.h"
- #import "Utils.h"
- #import "ServerConnector.h"
- #import "ChatTableDataSource.h"
- static CGFloat sideMargin = 2.0f;
- static CGFloat ZSWTappableLabelSpace = 16.0f;
- static ColorTheme currentTheme;
- @implementation ChatCallMessageCell {
- ZSWTappableLabel *titleLabel;
- ZSWTappableLabel *descriptionLabel;
- UIImageView *imageView;
- UIImageView *callIcon;
-
- UIAccessibilityElement *cellElement;
- NSString *titleText;
- NSString *descriptionText;
- }
- + (CGFloat)heightForMessage:(BaseMessage*)message forTableWidth:(CGFloat)tableWidth {
- CGSize titleSize;
- CGSize descriptionSize;
- CGSize maxSize = CGSizeMake([ChatMessageCell maxContentWidthForTableWidth:tableWidth] - ZSWTappableLabelSpace - sideMargin, CGFLOAT_MAX);
- NSString *text = [(SystemMessage *)message format];
- NSString *description = [(SystemMessage *)message callDetail];
- static ZSWTappableLabel *dummyTitleLabel = nil;
-
- if (dummyTitleLabel == nil) {
- dummyTitleLabel = [ChatCallMessageCell makeAttributedLabelWithFrame:CGRectMake(0.0, 0.0, maxSize.width, maxSize.height)];
- }
-
- dummyTitleLabel.font = [UIFont boldSystemFontOfSize:[ChatMessageCell textFontSize]];
- static dispatch_once_t onceToken;
- static BOOL canOpenPhoneLinks;
- dispatch_once(&onceToken, ^{
- canOpenPhoneLinks = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel:0"]];
- });
-
- NSString *spaces = @"";
- for (int i = 0; i < (dummyTitleLabel.font.pointSize / 2.2); i++) {
- spaces = [NSString stringWithFormat:@"%@ ", spaces];
- }
-
- text = [NSString stringWithFormat:@"%@%@",spaces, [(SystemMessage *)message format]];
-
- NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc] initWithAttributedString:[dummyTitleLabel applyMarkupFor:[TextStyleUtils makeAttributedStringFromString:text withFont:dummyTitleLabel.font textColor:nil isOwn:true application:[UIApplication sharedApplication]]]];
- dummyTitleLabel.attributedText = titleString;
-
- titleSize = [dummyTitleLabel sizeThatFits:maxSize];
-
- static ZSWTappableLabel *dummyDescriptionLabel = nil;
-
- if (dummyDescriptionLabel == nil) {
- dummyDescriptionLabel = [ChatCallMessageCell makeAttributedLabelWithFrame:CGRectMake(0.0, 0.0, maxSize.width, maxSize.height)];
- }
-
- dummyDescriptionLabel.font = [UIFont systemFontOfSize:[ChatMessageCell textFontSize] - 2.0];
- if (description == nil || description.length == 0) {
- description = @" ";
- }
- dummyDescriptionLabel.attributedText = [dummyDescriptionLabel applyMarkupFor:[TextStyleUtils makeAttributedStringFromString:description withFont:dummyDescriptionLabel.font textColor:nil isOwn:true application:[UIApplication sharedApplication]]];
-
- descriptionSize = [dummyDescriptionLabel sizeThatFits:maxSize];
-
- return titleSize.height + descriptionSize.height;
- }
- - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier transparent:(BOOL)transparent
- {
- self = [super initWithStyle:style reuseIdentifier:reuseIdentifier transparent:transparent];
- if (self) {
-
- // Create message text label
- titleLabel = [ChatCallMessageCell makeAttributedLabelWithFrame:self.bounds];
-
- descriptionLabel = [ChatCallMessageCell makeAttributedLabelWithFrame:self.bounds];
- descriptionLabel.textAlignment = NSTextAlignmentRight;
-
- imageView = [UIImageView new];
- imageView.contentMode = UIViewContentModeCenter;
- imageView.image = [ImageUtils imageWithImage:[UIImage imageNamed:@"ThreemaPhone" inColor:[UIColor whiteColor]] scaledToSize:CGSizeMake(25, 25)];
- imageView.clipsToBounds = YES;
-
- callIcon = [UIImageView new];
- callIcon.contentMode = UIViewContentModeScaleAspectFit;
-
- [self.contentView addSubview:titleLabel];
- [self.contentView addSubview:descriptionLabel];
- [self.contentView addSubview:imageView];
- [self.contentView addSubview:callIcon];
-
- UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
- tgr.numberOfTapsRequired = 1;
- [self addGestureRecognizer:tgr];
-
- if (self.dtgr != nil) {
- [tgr requireGestureRecognizerToFail:self.dtgr];
- }
-
- self.statusImage.hidden = YES;
- }
- return self;
- }
- - (void)setupColors {
- [super setupColors];
-
- titleLabel.textColor = [Colors fontNormal];
- descriptionLabel.textColor = [Colors fontNormal];
-
- [self updateLinkColors];
- }
- - (void)updateLinkColors {
- if (currentTheme != [Colors getTheme]) {
- currentTheme = [Colors getTheme];
- titleLabel.attributedText = [titleLabel applyMarkupFor:[TextStyleUtils makeAttributedStringFromString:titleText withFont:titleLabel.font textColor:nil isOwn:self.message.isOwn.boolValue application:[UIApplication sharedApplication]]];
- descriptionLabel.attributedText = [descriptionLabel applyMarkupFor:[TextStyleUtils makeAttributedStringFromString:descriptionText withFont:descriptionLabel.font textColor:nil isOwn:self.message.isOwn.boolValue application:[UIApplication sharedApplication]]];
- }
- }
- - (void)layoutSubviews {
- CGFloat messageTextWidth;
- if (@available(iOS 11.0, *)) {
- messageTextWidth = [ChatMessageCell maxContentWidthForTableWidth:self.safeAreaLayoutGuide.layoutFrame.size.width];
- } else {
- messageTextWidth = [ChatMessageCell maxContentWidthForTableWidth:self.frame.size.width];
- }
- CGSize titleSize = [titleLabel sizeThatFits:CGSizeMake(messageTextWidth, CGFLOAT_MAX)];
- CGSize descriptionSize = [descriptionLabel sizeThatFits:CGSizeMake(messageTextWidth, CGFLOAT_MAX)];
- CGFloat callImageWidth = 40.0f;
-
- CGSize bubbleSize = CGSizeMake(MAX(titleSize.width, descriptionSize.width), titleSize.height + descriptionSize.height);
- [self setBubbleContentSize:bubbleSize];
-
- [super layoutSubviews];
-
- CGFloat x;
- if (self.message.isOwn.boolValue) {
- x = self.contentView.frame.size.width-bubbleSize.width-21.0f-sideMargin;
- } else {
- x = 20.0f + self.contentLeftOffset;
- }
-
- CGFloat y = 14.0f;
-
- if (descriptionSize.height == 0 || [descriptionLabel.text isEqualToString:@" "]) {
- titleLabel.frame = CGRectMake(x, (y/2) + (bubbleSize.height/2) - (titleSize.height/2), bubbleSize.width, titleSize.height);
- descriptionLabel.hidden = YES;
- } else {
- titleLabel.frame = CGRectMake(x, y - (ZSWTappableLabelSpace/2), bubbleSize.width, titleSize.height + (ZSWTappableLabelSpace/3));
- descriptionLabel.frame = CGRectMake(x, titleLabel.frame.origin.y + titleLabel.frame.size.height - (ZSWTappableLabelSpace/1.5), bubbleSize.width, descriptionSize.height + ZSWTappableLabelSpace);
- descriptionLabel.hidden = NO;
- }
-
- CAShapeLayer * maskLayer = [CAShapeLayer layer];
-
- if (self.message.isOwn.boolValue) {
- imageView.frame = CGRectMake(self.msgBackground.frame.origin.x, self.msgBackground.frame.origin.y + 1.0, callImageWidth, self.msgBackground.frame.size.height - 7.0);
- maskLayer.path = [UIBezierPath bezierPathWithRoundedRect: imageView.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: (CGSize){10.0, 10.}].CGPath;
- } else {
- imageView.frame = CGRectMake(self.msgBackground.frame.origin.x + self.msgBackground.frame.size.width - callImageWidth, self.msgBackground.frame.origin.y + 1, callImageWidth, self.msgBackground.frame.size.height - 7.0);
- maskLayer.path = [UIBezierPath bezierPathWithRoundedRect: imageView.bounds byRoundingCorners: UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii: (CGSize){10.0, 10.}].CGPath;
- }
- imageView.layer.mask = maskLayer;
- imageView.backgroundColor = [Colors bubbleCallButton];
-
- CGFloat lineHeight = titleLabel.font.lineHeight;
- if (descriptionSize.height == 0 || [descriptionLabel.text isEqualToString:@" "]) {
- callIcon.frame = CGRectMake(titleLabel.frame.origin.x, titleLabel.frame.origin.y + lineHeight - titleLabel.font.pointSize - 2.0, (titleLabel.font.pointSize/2)*3, titleLabel.font.pointSize);
- } else {
- callIcon.frame = CGRectMake(titleLabel.frame.origin.x, titleLabel.frame.origin.y + lineHeight - titleLabel.font.pointSize, (titleLabel.font.pointSize/2)*3, titleLabel.font.pointSize);
- }
- }
- - (NSString *)accessibilityLabelForContent {
- return [NSString stringWithFormat:@"%@, %@", titleLabel.text, descriptionLabel.text];
- }
- - (void)setMessage:(BaseMessage *)newMessage {
- [super setMessage:newMessage];
-
- NSError *error;
- SystemMessage *systemMessage = (SystemMessage *)self.message;
- if (systemMessage.arg) {
- NSDictionary *argDict = [NSJSONSerialization JSONObjectWithData:systemMessage.arg options:NSJSONReadingAllowFragments error:&error];
- if (error) {
- self.message.isOwn = @0;
- } else {
- self.message.isOwn = argDict[@"CallInitiator"];
- }
- } else {
- self.message.isOwn = @1;
- }
-
- NSString *spaces = @"";
- for (int i = 0; i < (titleLabel.font.pointSize / 2.2); i++) {
- spaces = [NSString stringWithFormat:@"%@ ", spaces];
- }
-
- titleText = [NSString stringWithFormat:@"%@%@",spaces, [systemMessage format]];
- descriptionText = systemMessage.callDetail;
- if (descriptionText == nil || descriptionText.length == 0) {
- descriptionText = @" ";
- }
-
- titleLabel.font = [UIFont boldSystemFontOfSize:[ChatMessageCell textFontSize]];
- descriptionLabel.font = [UIFont systemFontOfSize:[ChatMessageCell textFontSize] - 2.0];
-
- NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc] initWithAttributedString:[titleLabel applyMarkupFor:[TextStyleUtils makeAttributedStringFromString:titleText withFont:titleLabel.font textColor:nil isOwn:self.message.isOwn.boolValue application:[UIApplication sharedApplication]]]];
-
- switch ([systemMessage.type integerValue]) {
- case kSystemMessageCallEnded:
- if (systemMessage.haveCallTime) {
- if (!self.message.isOwn.boolValue) {
- callIcon.image = [UIImage imageNamed:@"CallDownGreen" inColor:[Colors green]];
- } else {
- callIcon.image = [UIImage imageNamed:@"CallUpGreen" inColor:[Colors green]];
- }
- } else {
- if (!self.message.isOwn.boolValue) {
- callIcon.image = [UIImage imageNamed:@"CallLeftRed"];
- } else {
- callIcon.image = [UIImage imageNamed:@"CallUpRed"];
- }
- }
- break;
- case kSystemMessageCallRejected:
- if (!self.message.isOwn.boolValue) {
- callIcon.image = [UIImage imageNamed:@"CallLeftOrange"];
- } else {
- callIcon.image = [UIImage imageNamed:@"CallRightRed"];
- }
- break;
- case kSystemMessageCallRejectedBusy:
- if (!self.message.isOwn.boolValue) {
- callIcon.image = [UIImage imageNamed:@"CallLeftRed"];
- } else {
- callIcon.image = [UIImage imageNamed:@"CallRightRed"];
- }
- break;
- case kSystemMessageCallRejectedTimeout:
- if (!self.message.isOwn.boolValue) {
- callIcon.image = [UIImage imageNamed:@"CallLeftRed"];
- } else {
- callIcon.image = [UIImage imageNamed:@"CallRightRed"];
- }
- break;
- case kSystemMessageCallRejectedDisabled:
- callIcon.image = [UIImage imageNamed:@"CallRightRed"];
- break;
- case kSystemMessageCallMissed:
- callIcon.image = [UIImage imageNamed:@"CallLeftRed"];
- break;
- default:
- callIcon.image = [UIImage imageNamed:@"CallUpGreen" inColor:[Colors green]];
- break;
- }
-
- titleLabel.attributedText = titleString;
-
- descriptionLabel.attributedText = [descriptionLabel applyMarkupFor:[TextStyleUtils makeAttributedStringFromString:descriptionText withFont:descriptionLabel.font textColor:nil isOwn:self.message.isOwn.boolValue application:[UIApplication sharedApplication]]];
- if (self.message.isOwn.boolValue) {
- titleLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
- descriptionLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
- } else {
- titleLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
- descriptionLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
- }
-
- cellElement = nil;
-
- [self setNeedsLayout];
- }
- + (ZSWTappableLabel*)makeAttributedLabelWithFrame:(CGRect)rect {
- ZSWTappableLabel *label = [[ZSWTappableLabel alloc] initWithFrame:rect];
- label.clearsContextBeforeDrawing = NO;
- label.backgroundColor = [UIColor clearColor];
- label.numberOfLines = 0;
- label.lineBreakMode = NSLineBreakByWordWrapping;
- label.font = [ChatMessageCell textFont];
- label.contentMode = UIViewContentModeScaleToFill;
-
- return label;
- }
- #pragma mark - Private functions
- + (NSAttributedString*)attachmentWithImage:(UIImage*)attachment label:(UILabel *)label boundsImage:(BOOL)boundsImage {
- NSTextAttachment* textAttachment = [[NSTextAttachment alloc] initWithData:nil ofType:nil];
- textAttachment.image = attachment;
- if (boundsImage) {
- textAttachment.bounds = CGRectMake(0.0, -label.font.pointSize/6, label.font.pointSize, label.font.pointSize);
- }
-
- NSAttributedString* string = [NSAttributedString attributedStringWithAttachment:textAttachment];
- return string;
- }
- - (void)handleTap:(UIGestureRecognizer *)gestureRecognizer {
- CGPoint p = [gestureRecognizer locationInView:self];
- if (CGRectContainsPoint(self.msgBackground.frame, p)) {
- if ([UserSettings sharedUserSettings].enableThreemaCall && is64Bit == 1) {
- if (gestureRecognizer.state == UIGestureRecognizerStateEnded && self.editing == NO) {
-
- if ([ServerConnector sharedServerConnector].connectionState == ConnectionStateLoggedIn) {
- NSString *message = [NSString stringWithFormat:NSLocalizedString(@"call_contact_alert", nil), self.chatVc.conversation.contact.displayName];
-
- UIAlertController *errAlert = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert];
- [errAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"call", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
- [self.chatVc startVoipCall:false];
- }]];
- [errAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
- }]];
- [self.chatVc presentViewController:errAlert animated:YES completion:nil];
- } else {
- // Alert no internet connection
- NSString *title = NSLocalizedString(@"cannot_connect_title", nil);
- NSString *message = NSLocalizedString(@"cannot_connect_message", nil);
- [UIAlertTemplate showAlertWithOwner:self.chatVc title:title message:message actionOk:nil];
- }
- } else {
- UITableView *tableView = (UITableView *)self.superview;
- CGPoint p2 = [gestureRecognizer locationInView:tableView];
- NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:p2];
- UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
- if (cell.selected) {
- [tableView deselectRowAtIndexPath:indexPath animated:NO];
- [((ChatTableDataSource *) tableView.dataSource) tableView:tableView didDeselectRowAtIndexPath:indexPath];
- } else {
- [tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
- [((ChatTableDataSource *) tableView.dataSource) tableView:tableView didSelectRowAtIndexPath:indexPath];
- }
- }
- }
- } else {
- if (self.editing) {
- UITableView *tableView = (UITableView *)self.superview;
- CGPoint p2 = [gestureRecognizer locationInView:tableView];
- NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:p2];
- UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
- if (cell.selected) {
- [tableView deselectRowAtIndexPath:indexPath animated:NO];
- [((ChatTableDataSource *) tableView.dataSource) tableView:tableView didDeselectRowAtIndexPath:indexPath];
- } else {
- [tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
- [((ChatTableDataSource *) tableView.dataSource) tableView:tableView didSelectRowAtIndexPath:indexPath];
- }
- }
- }
- }
- #pragma mark - Override functions
- - (void)updateStatusImage {
- self.statusImage.hidden = YES;
- [self setNeedsLayout];
- }
- - (UIImage*)bubbleImageWithHighlight:(BOOL)bubbleHighlight {
- if (self.shouldHideBubbleBackground) {
- return nil;
- }
-
- if (self.message.isOwn.boolValue) {
- NSString *name = @"ChatBubbleSentMask";
- if (bubbleHighlight) {
- return [[UIImage imageNamed:name inColor:[Colors bubbleSent]] stretchableImageWithLeftCapWidth:15 topCapHeight:13];
- } else {
- return [[UIImage imageNamed:name inColor:[Colors bubbleSent]] stretchableImageWithLeftCapWidth:15 topCapHeight:13];
- }
- } else {
- NSString *name = @"ChatBubbleReceivedMask";
- if (bubbleHighlight) {
- return [[UIImage imageNamed:name inColor:[Colors bubbleReceived]] stretchableImageWithLeftCapWidth:23 topCapHeight:15];
- } else {
- return [[UIImage imageNamed:name inColor:[Colors bubbleReceived]] stretchableImageWithLeftCapWidth:23 topCapHeight:15];
- }
- }
- }
- - (void)setBubbleContentSize:(CGSize)size {
- CGFloat bgWidthMargin = 34.0f;
- CGFloat bgHeightMargin = 16.0f;
- CGFloat callImageWidth = 40.0f;
-
- self.bubbleSize = CGSizeMake(size.width+bgWidthMargin+callImageWidth, size.height+bgHeightMargin);
- }
- - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
- if (action == @selector(deleteMessage:)) {
- return YES;
- } else {
- return NO;
- }
- }
- - (NSString *)textForQuote {
- return titleText;
- }
- #pragma mark - ZSWTappableLabel delegate
- - (void)tappableLabel:(ZSWTappableLabel *)tappableLabel tappedAtIndex:(NSInteger)idx withAttributes:(NSDictionary *)attributes {
- }
- - (void)tappableLabel:(ZSWTappableLabel *)tappableLabel longPressedAtIndex:(NSInteger)idx withAttributes:(NSDictionary<NSString *,id> *)attributes {
- }
- - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
- if (self.editing) {
- // don't event forward to label
- return self;
- }
-
- return [super hitTest:point withEvent:event];
- }
- #pragma mark - UIAccessibilityContainer
- - (BOOL)isAccessibilityElement {
- return NO;
- }
- - (NSInteger)accessibilityElementCount {
- return [titleLabel accessibilityElementCount]+1;
- }
- - (id)accessibilityElementAtIndex:(NSInteger)index {
- // Fake an additional last element that encompasses the entire cell
- // and adds additional information about the message.
- if (index == ([titleLabel accessibilityElementCount])) {
- return [self cellElement];
- } else {
- return [titleLabel accessibilityElementAtIndex:index];
- }
- }
- - (NSInteger)indexOfAccessibilityElement:(id)element {
- if (element == [self cellElement])
- return [titleLabel accessibilityElementCount];
- else
- return [titleLabel indexOfAccessibilityElement:element];
- }
- - (UIAccessibilityElement*)cellElement {
- if (cellElement == nil) {
- cellElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
- cellElement.accessibilityLabel = [self accessibilityLabel];
- cellElement.accessibilityTraits = UIAccessibilityTraitStaticText;
- }
- cellElement.accessibilityFrame = [self convertRect:self.bounds toView:nil];
- return cellElement;
- }
- @end
|