diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 0a5a4b1866..cb0c369e6a 100755 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 02543BAE206E6C59001CDAB2 /* RoomSelectedStickerBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 02543BAB206E6C58001CDAB2 /* RoomSelectedStickerBubbleCell.m */; }; + 02543BAF206E6C59001CDAB2 /* RoomSelectedStickerBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 02543BAC206E6C58001CDAB2 /* RoomSelectedStickerBubbleCell.xib */; }; 2435179C1F375B9400D0683E /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2466B7551F2F80B800AE27B0 /* Info.plist */; }; 2435179F1F375C0F00D0683E /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382C01F276AED00356143 /* Vector.strings */; }; 2439DD621F6BBE760090F42D /* RecentRoomTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2439DD611F6BBE760090F42D /* RecentRoomTableViewCell.m */; }; @@ -656,6 +658,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 02543BAB206E6C58001CDAB2 /* RoomSelectedStickerBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomSelectedStickerBubbleCell.m; sourceTree = ""; }; + 02543BAC206E6C58001CDAB2 /* RoomSelectedStickerBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomSelectedStickerBubbleCell.xib; sourceTree = ""; }; + 02543BAD206E6C59001CDAB2 /* RoomSelectedStickerBubbleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomSelectedStickerBubbleCell.h; sourceTree = ""; }; 12AA0005C8B3D8D8162584C5 /* Pods-RiotShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RiotShareExtension/Pods-RiotShareExtension.debug.xcconfig"; sourceTree = ""; }; 22D76C11C202B6BC5917A049 /* Pods-RiotPods-RiotShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-RiotShareExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-RiotPods-RiotShareExtension/Pods-RiotPods-RiotShareExtension.release.xcconfig"; sourceTree = ""; }; 23D7292481328A48B8D5D4ED /* Pods_RiotPods_RiotShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_RiotShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2428,6 +2433,9 @@ F083BCD51E7009EC00A9B29C /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.h */, F083BCD61E7009EC00A9B29C /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m */, F083BCD71E7009EC00A9B29C /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib */, + 02543BAD206E6C59001CDAB2 /* RoomSelectedStickerBubbleCell.h */, + 02543BAB206E6C58001CDAB2 /* RoomSelectedStickerBubbleCell.m */, + 02543BAC206E6C58001CDAB2 /* RoomSelectedStickerBubbleCell.xib */, ); path = RoomBubbleList; sourceTree = ""; @@ -3217,6 +3225,7 @@ F083BD3A1E7009ED00A9B29C /* call_chat_icon@2x.png in Resources */, F083BE711E7009ED00A9B29C /* RoomOutgoingTextMsgBubbleCell.xib in Resources */, F083BD5E1E7009ED00A9B29C /* create_room@3x.png in Resources */, + 02543BAF206E6C59001CDAB2 /* RoomSelectedStickerBubbleCell.xib in Resources */, F083BE6B1E7009ED00A9B29C /* RoomOutgoingAttachmentBubbleCell.xib in Resources */, F083BD4C1E7009ED00A9B29C /* camera_capture@2x.png in Resources */, F083BD8F1E7009ED00A9B29C /* file_video_icon.png in Resources */, @@ -3635,6 +3644,7 @@ F083BE601E7009ED00A9B29C /* RoomIncomingTextMsgBubbleCell.m in Sources */, F083BE2F1E7009ED00A9B29C /* ContactTableViewCell.m in Sources */, F083BE901E7009ED00A9B29C /* RoomTitleView.m in Sources */, + 02543BAE206E6C59001CDAB2 /* RoomSelectedStickerBubbleCell.m in Sources */, F083BE6E1E7009ED00A9B29C /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, 3233F7311F31F4BF006ACA81 /* JitsiViewController.m in Sources */, F083BE2A1E7009ED00A9B29C /* UsersDevicesViewController.m in Sources */, diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 195b7a4d2f..f8a79e3268 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1587,6 +1588,15 @@ - (nullable NSString *)notificationBodyForEvent:(MXEvent *)event pushRule:(MXPus else notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; } + else if (event.eventType == MXEventTypeSticker) + { + NSString *roomDisplayName = room.summary.displayname; + + if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; + else + notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; + } return notificationBody; } diff --git a/Riot/Model/Search/FilesSearchCellData.m b/Riot/Model/Search/FilesSearchCellData.m index 3eff018a79..c95adeb96b 100644 --- a/Riot/Model/Search/FilesSearchCellData.m +++ b/Riot/Model/Search/FilesSearchCellData.m @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -39,7 +40,7 @@ - (instancetype)initWithSearchResult:(MXSearchResult *)searchResult2 andSearchDa // Check attachment if any if ([searchDataSource.eventFormatter isSupportedAttachment:event]) { - // Note: event.eventType is equal here to MXEventTypeRoomMessage + // Note: event.eventType may be equal here to MXEventTypeRoomMessage or MXEventTypeSticker attachment = [[MXKAttachment alloc] initWithEvent:event andMatrixSession:searchDataSource.mxSession]; } @@ -101,7 +102,7 @@ - (void)setShouldShowRoomDisplayName:(BOOL)shouldShowRoomDisplayName2 - (BOOL)isAttachmentWithThumbnail { - return (attachment && (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo)); + return (attachment && (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo || attachment.type == MXKAttachmentTypeSticker)); } - (UIImage*)attachmentIcon diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index b434af949a..a3852547f6 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -1,6 +1,7 @@ /* Copyright 2014 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -97,6 +98,8 @@ #import "RoomMembershipExpandedBubbleCell.h" #import "RoomMembershipExpandedWithPaginationTitleBubbleCell.h" +#import "RoomSelectedStickerBubbleCell.h" + #import "MXKRoomBubbleTableViewCell+Riot.h" #import "AvatarGenerator.h" @@ -310,6 +313,8 @@ - (void)viewDidLoad [self.bubblesTableView registerClass:RoomMembershipExpandedBubbleCell.class forCellReuseIdentifier:RoomMembershipExpandedBubbleCell.defaultReuseIdentifier]; [self.bubblesTableView registerClass:RoomMembershipExpandedWithPaginationTitleBubbleCell.class forCellReuseIdentifier:RoomMembershipExpandedWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:RoomSelectedStickerBubbleCell.class forCellReuseIdentifier:RoomSelectedStickerBubbleCell.defaultReuseIdentifier]; + // Prepare expanded header expandedHeader = [ExpandedRoomTitleView roomTitleView]; expandedHeader.delegate = self; @@ -1760,7 +1765,12 @@ - (void)displayRoomPreview:(RoomPreviewData *)previewData { if (bubbleData.isAttachmentWithThumbnail) { - if (bubbleData.isPaginationFirstBubble) + // Check whether the provided celldata corresponds to a selected sticker + if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId]) + { + cellViewClass = RoomSelectedStickerBubbleCell.class; + } + else if (bubbleData.isPaginationFirstBubble) { cellViewClass = isEncryptedRoom ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class; } @@ -1805,7 +1815,12 @@ - (void)displayRoomPreview:(RoomPreviewData *)previewData // Handle here outgoing bubbles if (bubbleData.isAttachmentWithThumbnail) { - if (bubbleData.isPaginationFirstBubble) + // Check whether the provided celldata corresponds to a selected sticker + if (customizedRoomDataSource.selectedEventId && (bubbleData.attachment.type == MXKAttachmentTypeSticker) && [bubbleData.attachment.eventId isEqualToString:customizedRoomDataSource.selectedEventId]) + { + cellViewClass = RoomSelectedStickerBubbleCell.class; + } + else if (bubbleData.isPaginationFirstBubble) { cellViewClass = isEncryptedRoom ? RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.class :RoomOutgoingAttachmentWithPaginationTitleBubbleCell.class; } @@ -1909,12 +1924,33 @@ - (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)ac [self showEditButtonAlertMenuForEvent:selectedEvent inCell:cell level:0]; } } - else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAttachmentView] - && ((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventSentState == MXEventSentStateFailed) + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAttachmentView]) { - // Shortcut: when clicking on an unsent media, show the action sheet to resend it - MXEvent *selectedEvent = [self.roomDataSource eventWithEventId:((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventId]; - [self dataSource:dataSource didRecognizeAction:kMXKRoomBubbleCellRiotEditButtonPressed inCell:cell userInfo:@{kMXKRoomBubbleCellEventKey:selectedEvent}]; + if (((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventSentState == MXEventSentStateFailed) + { + // Shortcut: when clicking on an unsent media, show the action sheet to resend it + MXEvent *selectedEvent = [self.roomDataSource eventWithEventId:((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventId]; + [self dataSource:dataSource didRecognizeAction:kMXKRoomBubbleCellRiotEditButtonPressed inCell:cell userInfo:@{kMXKRoomBubbleCellEventKey:selectedEvent}]; + } + else if (((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.type == MXKAttachmentTypeSticker) + { + // We don't open the attachments viewer when the user taps on a sticker. + // We consider this tap like a selection. + + // Check whether a selection already exist or not + if (customizedRoomDataSource.selectedEventId) + { + [self cancelEventSelection]; + } + else + { + // Highlight this event in displayed message + customizedRoomDataSource.selectedEventId = ((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventId; + } + + // Force table refresh + [self dataSource:self.roomDataSource didCellChange:nil]; + } } else if ([actionIdentifier isEqualToString:kRoomEncryptedDataBubbleCellTapOnEncryptionIcon]) { @@ -2156,38 +2192,41 @@ - (void)showEditButtonAlertMenuForEvent:(MXEvent*)selectedEvent inCell:(id modelCellViewClass = nil; + if (bubbleData.shouldHideSenderInformation) + { + modelCellViewClass = bubbleData.isEncryptedRoom ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class; + + self.paginationTitleView.hidden = YES; + self.pictureView.hidden = YES; + self.userNameLabel.hidden = YES; + self.userNameTapGestureMaskView.userInteractionEnabled = NO; + } + else + { + if (bubbleData.isPaginationFirstBubble) + { + modelCellViewClass = bubbleData.isEncryptedRoom ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class; + + self.paginationTitleView.hidden = NO; + self.paginationLabel.text = [[bubbleData.eventFormatter dateStringFromDate:bubbleData.date withTime:NO] uppercaseString]; + } + else + { + modelCellViewClass = bubbleData.isEncryptedRoom ? RoomIncomingEncryptedAttachmentBubbleCell.class : RoomIncomingAttachmentBubbleCell.class; + + self.paginationTitleView.hidden = YES; + } + + // Hanlde sender avatar + self.pictureView.hidden = NO; + NSString *avatarThumbURL = nil; + if (bubbleData.senderAvatarUrl) + { + // Suppose this url is a matrix content uri, we use SDK to get the well adapted thumbnail from server + avatarThumbURL = [bubbleData.mxSession.matrixRestClient urlOfContentThumbnail:bubbleData.senderAvatarUrl toFitViewSize:self.pictureView.frame.size withMethod:MXThumbnailingMethodCrop]; + } + self.pictureView.enableInMemoryCache = YES; + [self.pictureView setImageURL:avatarThumbURL withType:nil andImageOrientation:UIImageOrientationUp previewImage: bubbleData.senderAvatarPlaceholder ? bubbleData.senderAvatarPlaceholder : self.picturePlaceholder]; + + // Display sender's name except if the name appears in the displayed text (see emote and membership events) + if (bubbleData.shouldHideSenderName == NO) + { + if (bubbleData.senderFlair) + { + [self renderSenderFlair]; + } + else + { + self.userNameLabel.text = bubbleData.senderDisplayName; + } + + self.userNameLabel.hidden = NO; + self.userNameTapGestureMaskView.userInteractionEnabled = YES; + } + else + { + self.userNameLabel.hidden = YES; + self.userNameTapGestureMaskView.userInteractionEnabled = NO; + } + } + + // Retrieve the suitable content size for the attachment thumbnail + CGSize contentSize = bubbleData.contentSize; + // Update image view frame in order to center loading wheel (if any) + CGRect frame = self.attachmentView.frame; + frame.size.width = contentSize.width; + frame.size.height = contentSize.height; + self.attachmentView.frame = frame; + // Retrieve the MIME type + NSString *mimetype = nil; + if (bubbleData.attachment.thumbnailInfo) + { + mimetype = bubbleData.attachment.thumbnailInfo[@"mimetype"]; + } + else if (bubbleData.attachment.contentInfo) + { + mimetype = bubbleData.attachment.contentInfo[@"mimetype"]; + } + if ([mimetype isEqualToString:@"image/gif"]) + { + self.fileTypeIconView.image = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"filetype-gif"]; + self.fileTypeIconView.hidden = NO; + } + else + { + self.fileTypeIconView.hidden = YES; + } + // Display the sticker + self.attachmentView.backgroundColor = [UIColor clearColor]; + self.attachmentView.mediaFolder = bubbleData.roomId; + self.attachmentView.enableInMemoryCache = YES; + [self.attachmentView setAttachmentThumb:bubbleData.attachment]; + + // Set the description + NSAttributedString *description = component.attributedTextMessage; + if (description.length) + { + self.descriptionContainerView.hidden = NO; + self.descriptionLabel.attributedText = description; + } + else + { + self.descriptionContainerView.hidden = YES; + } + + // @TODO: check whether we need the progress view or not. Remove it if it is useless. + self.progressView.hidden = YES; + + // Adjust Attachment width constant + self.attachViewWidthConstraint.constant = contentSize.width; + +// // Add a long gesture recognizer on progressView to cancel the current operation (Note: only the download can be cancelled). +// UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPressGesture:)]; +// [self.progressView addGestureRecognizer:longPress]; + + // Handle the encryption view + if (bubbleData.isEncryptedRoom) + { + // Set the right device info icon + self.encryptionStatusView.hidden = NO; + self.encryptionStatusView.image = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; + } + else + { + self.encryptionStatusView.hidden = YES; + } + + // Hide by default the info container + self.bubbleInfoContainer.hidden = YES; + + // Adjust the layout according to the original cell, the one used to display the sticker unselected. + if ([modelCellViewClass nib]) + { + MXKRoomBubbleTableViewCell* cell= (MXKRoomBubbleTableViewCell*)[[modelCellViewClass nib] instantiateWithOwner:nil options:nil].firstObject; + + if (cell.userNameLabel) + { + frame = cell.userNameLabel.frame; + self.userNameLabelTopConstraint.constant = frame.origin.y; + } + frame = cell.attachmentView.frame; + self.attachViewLeadingConstraint.constant = frame.origin.x; + self.pictureView.frame = cell.pictureView.frame; + self.attachViewTopConstraint.constant = cell.attachViewTopConstraint.constant; + self.attachViewBottomConstraint.constant = cell.attachViewBottomConstraint.constant; + self.bubbleInfoContainerTopConstraint.constant = cell.bubbleInfoContainerTopConstraint.constant; + } + } +} + ++ (CGFloat)heightForCellData:(MXKCellData*)cellData withMaximumWidth:(CGFloat)maxWidth +{ + // Sanity check: accept only object of MXKRoomBubbleCellData classes or sub-classes + NSParameterAssert([cellData isKindOfClass:[MXKRoomBubbleCellData class]]); + MXKRoomBubbleCellData *bubbleData = (MXKRoomBubbleCellData*)cellData; + + // Look for the original cell class to extract the constraints value. + Class modelCellViewClass = nil; + if (bubbleData.shouldHideSenderInformation) + { + modelCellViewClass = bubbleData.isEncryptedRoom ? RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.class : RoomIncomingAttachmentWithoutSenderInfoBubbleCell.class; + } + else + { + if (bubbleData.isPaginationFirstBubble) + { + modelCellViewClass = bubbleData.isEncryptedRoom ? RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.class : RoomIncomingAttachmentWithPaginationTitleBubbleCell.class; + } + else + { + modelCellViewClass = bubbleData.isEncryptedRoom ? RoomIncomingEncryptedAttachmentBubbleCell.class : RoomIncomingAttachmentBubbleCell.class; + } + } + + CGFloat rowHeight = [modelCellViewClass heightForCellData:cellData withMaximumWidth:maxWidth]; + + // Finalize the cell height by adding the height of the description. + // Retrieve the component which stores the sticker (Only one component is handled by the bubble in case of sticker). + MXKRoomBubbleComponent *component = bubbleData.bubbleComponents.firstObject; + NSAttributedString *description = component.attributedTextMessage; + if (description.length) + { + RoomSelectedStickerBubbleCell* cell = (RoomSelectedStickerBubbleCell*)[self cellWithOriginalXib]; + CGRect frame = cell.frame; + frame.size.width = maxWidth; + frame.size.height = 300; + cell.frame = frame; + + cell.descriptionLabel.attributedText = description; + [cell layoutIfNeeded]; + + rowHeight += cell.descriptionContainerView.frame.size.height + cell.descriptionContainerViewBottomConstraint.constant; + } + + return rowHeight; +} + +@end diff --git a/Riot/Views/RoomBubbleList/RoomSelectedStickerBubbleCell.xib b/Riot/Views/RoomBubbleList/RoomSelectedStickerBubbleCell.xib new file mode 100644 index 0000000000..f0ae788786 --- /dev/null +++ b/Riot/Views/RoomBubbleList/RoomSelectedStickerBubbleCell.xib @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +