diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 228729ca0..fa0e22733 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -446,6 +446,7 @@ Tap the + to start adding people."; "threads_empty_info_my" = "Reply to an ongoing thread or tap a message and use “Thread” to start a new one."; "threads_empty_tip" = "Tip: Tap a message and use “Thread” to start one."; "threads_empty_show_all_threads" = "Show all threads"; +"message_from_a_thread" = "From a thread"; "media_type_accessibility_image" = "Image"; "media_type_accessibility_audio" = "Audio"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 49ebc94ff..7a9b3f80c 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2319,6 +2319,10 @@ public class VectorL10n: NSObject { public static var mediaTypeAccessibilityVideo: String { return VectorL10n.tr("Vector", "media_type_accessibility_video") } + /// From a thread + public static var messageFromAThread: String { + return VectorL10n.tr("Vector", "message_from_a_thread") + } /// More public static var more: String { return VectorL10n.tr("Vector", "more") diff --git a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m index 950715c17..6339a51b4 100644 --- a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m +++ b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m @@ -115,6 +115,60 @@ // Display date for each message [bubbleCell addDateLabel]; + + RoomBubbleCellData *cellData = (RoomBubbleCellData*)[self cellDataAtIndex:indexPath.row]; + MXEvent *event = cellData.events.firstObject; + + if (event) + { + if (cellData.hasThreadRoot) + { + MXThread *thread = cellData.bubbleComponents.firstObject.thread; + ThreadSummaryView *threadSummaryView = [[ThreadSummaryView alloc] initWithThread:thread]; + [bubbleCell.tmpSubviews addObject:threadSummaryView]; + + threadSummaryView.translatesAutoresizingMaskIntoConstraints = NO; + [bubbleCell.contentView addSubview:threadSummaryView]; + + CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; + + CGRect bubbleComponentFrame = [bubbleCell componentFrameInContentViewForIndex:0]; + CGFloat bottomPositionY = bubbleComponentFrame.origin.y + bubbleComponentFrame.size.height; + + // Set constraints for the summary view + [NSLayoutConstraint activateConstraints: @[ + [threadSummaryView.leadingAnchor constraintEqualToAnchor:threadSummaryView.superview.leadingAnchor + constant:leftMargin], + [threadSummaryView.topAnchor constraintEqualToAnchor:threadSummaryView.superview.topAnchor + constant:bottomPositionY + RoomBubbleCellLayout.threadSummaryViewTopMargin], + [threadSummaryView.heightAnchor constraintEqualToConstant:[ThreadSummaryView contentViewHeightForThread:thread fitting:cellData.maxTextViewWidth]], + [threadSummaryView.trailingAnchor constraintLessThanOrEqualToAnchor:threadSummaryView.superview.trailingAnchor constant:-RoomBubbleCellLayout.reactionsViewRightMargin] + ]]; + } + else if (event.isInThread) + { + FromThreadView *fromThreadView = [FromThreadView instantiate]; + [bubbleCell.tmpSubviews addObject:fromThreadView]; + + fromThreadView.translatesAutoresizingMaskIntoConstraints = NO; + [bubbleCell.contentView addSubview:fromThreadView]; + + CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; + + CGRect bubbleComponentFrame = [bubbleCell componentFrameInContentViewForIndex:0]; + CGFloat bottomPositionY = bubbleComponentFrame.origin.y + bubbleComponentFrame.size.height; + + // Set constraints for the summary view + [NSLayoutConstraint activateConstraints: @[ + [fromThreadView.leadingAnchor constraintEqualToAnchor:fromThreadView.superview.leadingAnchor + constant:leftMargin], + [fromThreadView.topAnchor constraintEqualToAnchor:fromThreadView.superview.topAnchor + constant:bottomPositionY + RoomBubbleCellLayout.fromThreadViewTopMargin], + [fromThreadView.heightAnchor constraintEqualToConstant:[FromThreadView contentViewHeightForEvent:event fitting:cellData.maxTextViewWidth]], + [fromThreadView.trailingAnchor constraintLessThanOrEqualToAnchor:fromThreadView.superview.trailingAnchor constant:-RoomBubbleCellLayout.reactionsViewRightMargin] + ]]; + } + } } return cell; diff --git a/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m b/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m index b0dd6b913..f258b9c3e 100644 --- a/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m +++ b/Riot/Modules/GlobalSearch/Messages/HomeMessagesSearchViewController.m @@ -156,18 +156,29 @@ } - (void)showRoomWithId:(NSString*)roomId - andEventId:(NSString*)eventId + andEvent:(MXEvent*)event inMatrixSession:(MXSession*)session { - ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO]; - + ThreadParameters *threadParameters = nil; + if (event.threadId) + { + threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId + stackRoomScreen:NO]; + } + else if ([self.mainSession.threadingService isEventThreadRoot:event]) + { + threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId + stackRoomScreen:NO]; + } + + ScreenPresentationParameters *screenParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO]; + RoomNavigationParameters *parameters = [[RoomNavigationParameters alloc] initWithRoomId:roomId - eventId:eventId - mxSession:session - threadParameters:nil - presentationParameters:presentationParameters]; - - [[AppDelegate theDelegate] showRoomWithParameters:parameters]; + eventId:event.eventId + mxSession:self.mainSession + threadParameters:threadParameters + presentationParameters:screenParameters]; + [[LegacyAppDelegate theDelegate] showRoomWithParameters:parameters]; } #pragma mark - MXKDataSourceDelegate @@ -264,7 +275,7 @@ // Make the master tabBar view controller open the RoomViewController [self showRoomWithId:cellData.roomId - andEventId:_selectedEvent.eventId + andEvent:_selectedEvent inMatrixSession:cellData.mxSession]; // Reset the selected event. HomeViewController got it when here diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index 8602f4370..55c221240 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -573,6 +573,8 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat additionalVerticalHeight+= [self reactionHeightForEventId:eventId]; // Add vertical whitespace in case of a thread root additionalVerticalHeight+= [self threadSummaryViewHeightForEventId:eventId]; + // Add vertical whitespace in case of from a thread + additionalVerticalHeight+= [self fromThreadViewHeightForEventId:eventId]; // Add vertical whitespace in case of read receipts. additionalVerticalHeight+= [self readReceiptHeightForEventId:eventId]; @@ -593,6 +595,7 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat height+= [self urlPreviewHeightForEventId:eventId]; height+= [self reactionHeightForEventId:eventId]; height+= [self threadSummaryViewHeightForEventId:eventId]; + height+= [self fromThreadViewHeightForEventId:eventId]; height+= [self readReceiptHeightForEventId:eventId]; } @@ -654,7 +657,35 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat // component is not a thread root return 0; } - return RoomBubbleCellLayout.threadSummaryViewTopMargin + [ThreadSummaryView contentViewHeightForThread:component.thread fitting:self.maxTextViewWidth]; + return RoomBubbleCellLayout.threadSummaryViewTopMargin + + [ThreadSummaryView contentViewHeightForThread:component.thread fitting:self.maxTextViewWidth]; +} + +- (CGFloat)fromThreadViewHeightForEventId:(NSString*)eventId +{ + if (!RiotSettings.shared.enableThreads) + { + // do not show from thread view if threads not enabled + return 0; + } + if (roomDataSource.threadId) + { + // do not show from thread view on threads + return 0; + } + NSInteger index = [self bubbleComponentIndexForEventId:eventId]; + if (index == NSNotFound) + { + return 0; + } + MXKRoomBubbleComponent *component = self.bubbleComponents[index]; + if (!component.event.isInThread) + { + // event is not in a thread + return 0; + } + return RoomBubbleCellLayout.fromThreadViewTopMargin + + [FromThreadView contentViewHeightForEvent:component.event fitting:self.maxTextViewWidth]; } - (CGFloat)urlPreviewHeightForEventId:(NSString*)eventId diff --git a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m index f35ac5c7f..81d938e33 100644 --- a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m +++ b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m @@ -91,11 +91,65 @@ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class]) { MXKRoomBubbleTableViewCell *bubbleCell = (MXKRoomBubbleTableViewCell*)cell; - + // Display date for each message [bubbleCell addDateLabel]; + + RoomBubbleCellData *cellData = (RoomBubbleCellData*)[self cellDataAtIndex:indexPath.row]; + MXEvent *event = cellData.events.firstObject; + + if (event) + { + if (cellData.hasThreadRoot) + { + MXThread *thread = cellData.bubbleComponents.firstObject.thread; + ThreadSummaryView *threadSummaryView = [[ThreadSummaryView alloc] initWithThread:thread]; + [bubbleCell.tmpSubviews addObject:threadSummaryView]; + + threadSummaryView.translatesAutoresizingMaskIntoConstraints = NO; + [bubbleCell.contentView addSubview:threadSummaryView]; + + CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; + + CGRect bubbleComponentFrame = [bubbleCell componentFrameInContentViewForIndex:0]; + CGFloat bottomPositionY = bubbleComponentFrame.origin.y + bubbleComponentFrame.size.height; + + // Set constraints for the summary view + [NSLayoutConstraint activateConstraints: @[ + [threadSummaryView.leadingAnchor constraintEqualToAnchor:threadSummaryView.superview.leadingAnchor + constant:leftMargin], + [threadSummaryView.topAnchor constraintEqualToAnchor:threadSummaryView.superview.topAnchor + constant:bottomPositionY + RoomBubbleCellLayout.threadSummaryViewTopMargin], + [threadSummaryView.heightAnchor constraintEqualToConstant:[ThreadSummaryView contentViewHeightForThread:thread fitting:cellData.maxTextViewWidth]], + [threadSummaryView.trailingAnchor constraintLessThanOrEqualToAnchor:threadSummaryView.superview.trailingAnchor constant:-RoomBubbleCellLayout.reactionsViewRightMargin] + ]]; + } + else if (event.isInThread) + { + FromThreadView *fromThreadView = [FromThreadView instantiate]; + [bubbleCell.tmpSubviews addObject:fromThreadView]; + + fromThreadView.translatesAutoresizingMaskIntoConstraints = NO; + [bubbleCell.contentView addSubview:fromThreadView]; + + CGFloat leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin; + + CGRect bubbleComponentFrame = [bubbleCell componentFrameInContentViewForIndex:0]; + CGFloat bottomPositionY = bubbleComponentFrame.origin.y + bubbleComponentFrame.size.height; + + // Set constraints for the summary view + [NSLayoutConstraint activateConstraints: @[ + [fromThreadView.leadingAnchor constraintEqualToAnchor:fromThreadView.superview.leadingAnchor + constant:leftMargin], + [fromThreadView.topAnchor constraintEqualToAnchor:fromThreadView.superview.topAnchor + constant:bottomPositionY + RoomBubbleCellLayout.fromThreadViewTopMargin], + [fromThreadView.heightAnchor constraintEqualToConstant:[FromThreadView contentViewHeightForEvent:event fitting:cellData.maxTextViewWidth]], + [fromThreadView.trailingAnchor constraintLessThanOrEqualToAnchor:fromThreadView.superview.trailingAnchor constant:-RoomBubbleCellLayout.reactionsViewRightMargin] + ]]; + } + } } - + return cell; } diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift index a860d942b..2295f634b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomBubbleCellLayout.swift @@ -50,4 +50,5 @@ final class RoomBubbleCellLayout: NSObject { // Threads static let threadSummaryViewTopMargin: CGFloat = 8.0 + static let fromThreadViewTopMargin: CGFloat = 8.0 } diff --git a/Riot/Modules/Room/Views/Threads/From/FromThreadView.swift b/Riot/Modules/Room/Views/Threads/From/FromThreadView.swift new file mode 100644 index 000000000..992a8b202 --- /dev/null +++ b/Riot/Modules/Room/Views/Threads/From/FromThreadView.swift @@ -0,0 +1,54 @@ +// +// Copyright 2021 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Reusable + +@objcMembers +class FromThreadView: UIView { + + private enum Constants { + static let viewHeight: CGFloat = 18 + } + + @IBOutlet private weak var iconView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + static func contentViewHeight(forEvent event: MXEvent, + fitting maxWidth: CGFloat) -> CGFloat { + return Constants.viewHeight + } + + static func instantiate() -> FromThreadView { + let view = FromThreadView.loadFromNib() + view.update(theme: ThemeService.shared().theme) + view.titleLabel.text = VectorL10n.messageFromAThread + return view + } + +} + +extension FromThreadView: NibLoadable {} + +extension FromThreadView: Themable { + + func update(theme: Theme) { + backgroundColor = .clear + iconView.tintColor = theme.colors.secondaryContent + titleLabel.textColor = theme.colors.secondaryContent + } + +} diff --git a/Riot/Modules/Room/Views/Threads/From/FromThreadView.xib b/Riot/Modules/Room/Views/Threads/From/FromThreadView.xib new file mode 100644 index 000000000..8e8e9f8b2 --- /dev/null +++ b/Riot/Modules/Room/Views/Threads/From/FromThreadView.xib @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/Threads/ThreadSummaryView.swift b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.swift similarity index 100% rename from Riot/Modules/Room/Views/Threads/ThreadSummaryView.swift rename to Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.swift diff --git a/Riot/Modules/Room/Views/Threads/ThreadSummaryView.xib b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.xib similarity index 100% rename from Riot/Modules/Room/Views/Threads/ThreadSummaryView.xib rename to Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryView.xib diff --git a/Riot/Modules/Room/Views/Threads/ThreadSummaryViewModel.swift b/Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryViewModel.swift similarity index 100% rename from Riot/Modules/Room/Views/Threads/ThreadSummaryViewModel.swift rename to Riot/Modules/Room/Views/Threads/Summary/ThreadSummaryViewModel.swift