diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index 072a882a6..6cc25bcfe 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -61,7 +61,7 @@ extern NSTimeInterval const kResizeComposerAnimationDuration; // The preview header @property (weak, nonatomic, nullable) IBOutlet UIView *previewHeaderContainer; @property (weak, nonatomic, nullable) IBOutlet NSLayoutConstraint *previewHeaderContainerHeightConstraint; -@property (weak, nonatomic, nullable) IBOutlet NSLayoutConstraint *userSuggestionContainerHeightConstraint; +@property (weak, nonatomic, nullable) IBOutlet NSLayoutConstraint *completionSuggestionContainerHeightConstraint; // The jump to last unread banner @property (weak, nonatomic, nullable) IBOutlet UIView *jumpToLastUnreadBannerContainer; diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 57a431d2e..38f71f8d1 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -97,7 +97,7 @@ static CGSize kThreadListBarButtonItemImageSize; @interface RoomViewController () + RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate, CompletionSuggestionCoordinatorBridgeDelegate, ThreadsCoordinatorBridgePresenterDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, MXThreadingServiceDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate, RoomInputToolbarViewDelegate, ComposerCreateActionListBridgePresenterDelegate> { // The preview header @@ -223,8 +223,8 @@ static CGSize kThreadListBarButtonItemImageSize; @property (nonatomic, strong) ShareManager *shareManager; @property (nonatomic, strong) EventMenuBuilder *eventMenuBuilder; -@property (nonatomic, strong) UserSuggestionCoordinatorBridge *userSuggestionCoordinator; -@property (nonatomic, weak) IBOutlet UIView *userSuggestionContainerView; +@property (nonatomic, strong) CompletionSuggestionCoordinatorBridge *completionSuggestionCoordinator; +@property (nonatomic, weak) IBOutlet UIView *completionSuggestionContainerView; @property (nonatomic, readwrite) RoomDisplayConfiguration *displayConfiguration; @@ -416,7 +416,7 @@ static CGSize kThreadListBarButtonItemImageSize; [self setupActions]; - [self setupUserSuggestionViewIfNeeded]; + [self setupCompletionSuggestionViewIfNeeded]; [self.topBannersStackView vc_removeAllSubviews]; } @@ -1088,12 +1088,12 @@ static CGSize kThreadListBarButtonItemImageSize; [VoiceMessageMediaServiceProvider.sharedProvider setCurrentRoomSummary:dataSource.room.summary]; _voiceMessageController.roomId = dataSource.roomId; - _userSuggestionCoordinator = [[UserSuggestionCoordinatorBridge alloc] initWithMediaManager:self.roomDataSource.mxSession.mediaManager + _completionSuggestionCoordinator = [[CompletionSuggestionCoordinatorBridge alloc] initWithMediaManager:self.roomDataSource.mxSession.mediaManager room:dataSource.room userID:self.roomDataSource.mxSession.myUserId]; - _userSuggestionCoordinator.delegate = self; + _completionSuggestionCoordinator.delegate = self; - [self setupUserSuggestionViewIfNeeded]; + [self setupCompletionSuggestionViewIfNeeded]; [self updateTopBanners]; } @@ -2726,13 +2726,13 @@ static CGSize kThreadListBarButtonItemImageSize; } } -- (void)setupUserSuggestionViewIfNeeded +- (void)setupCompletionSuggestionViewIfNeeded { if(!self.isViewLoaded) { return; } - UIViewController *suggestionsViewController = self.userSuggestionCoordinator.toPresentable; + UIViewController *suggestionsViewController = self.completionSuggestionCoordinator.toPresentable; if (!suggestionsViewController) { @@ -2742,12 +2742,12 @@ static CGSize kThreadListBarButtonItemImageSize; [suggestionsViewController.view setTranslatesAutoresizingMaskIntoConstraints:NO]; [self addChildViewController:suggestionsViewController]; - [self.userSuggestionContainerView addSubview:suggestionsViewController.view]; + [self.completionSuggestionContainerView addSubview:suggestionsViewController.view]; - [NSLayoutConstraint activateConstraints:@[[suggestionsViewController.view.topAnchor constraintEqualToAnchor:self.userSuggestionContainerView.topAnchor], - [suggestionsViewController.view.leadingAnchor constraintEqualToAnchor:self.userSuggestionContainerView.leadingAnchor], - [suggestionsViewController.view.trailingAnchor constraintEqualToAnchor:self.userSuggestionContainerView.trailingAnchor], - [suggestionsViewController.view.bottomAnchor constraintEqualToAnchor:self.userSuggestionContainerView.bottomAnchor],]]; + [NSLayoutConstraint activateConstraints:@[[suggestionsViewController.view.topAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.topAnchor], + [suggestionsViewController.view.leadingAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.leadingAnchor], + [suggestionsViewController.view.trailingAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.trailingAnchor], + [suggestionsViewController.view.bottomAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.bottomAnchor],]]; [suggestionsViewController didMoveToParentViewController:self]; } @@ -5147,17 +5147,17 @@ static CGSize kThreadListBarButtonItemImageSize; - (void)roomInputToolbarViewDidChangeTextMessage:(RoomInputToolbarView *)toolbarView { - [self.userSuggestionCoordinator processTextMessage:toolbarView.textMessage]; + [self.completionSuggestionCoordinator processTextMessage:toolbarView.textMessage]; } - (void)didDetectTextPattern:(SuggestionPatternWrapper *)suggestionPattern { - [self.userSuggestionCoordinator processSuggestionPattern:suggestionPattern]; + [self.completionSuggestionCoordinator processSuggestionPattern:suggestionPattern]; } -- (UserSuggestionViewModelContextWrapper *)userSuggestionContext +- (CompletionSuggestionViewModelContextWrapper *)completionSuggestionContext { - return [self.userSuggestionCoordinator sharedContext]; + return [self.completionSuggestionCoordinator sharedContext]; } - (MXMediaManager *)mediaManager @@ -8059,9 +8059,9 @@ static CGSize kThreadListBarButtonItemImageSize; [[LegacyAppDelegate theDelegate] openSpaceWithId:spaceId]; } -#pragma mark - UserSuggestionCoordinatorBridgeDelegate +#pragma mark - CompletionSuggestionCoordinatorBridgeDelegate -- (void)userSuggestionCoordinatorBridge:(UserSuggestionCoordinatorBridge *)coordinator +- (void)completionSuggestionCoordinatorBridge:(CompletionSuggestionCoordinatorBridge *)coordinator didRequestMentionForMember:(MXRoomMember *)member textTrigger:(NSString *)textTrigger { @@ -8069,16 +8069,16 @@ static CGSize kThreadListBarButtonItemImageSize; [self mention:member]; } -- (void)userSuggestionCoordinatorBridgeDidRequestMentionForRoom:(UserSuggestionCoordinatorBridge *)coordinator +- (void)completionSuggestionCoordinatorBridgeDidRequestMentionForRoom:(CompletionSuggestionCoordinatorBridge *)coordinator textTrigger:(NSString *)textTrigger { [self removeTriggerTextFromComposer:textTrigger]; - [self.inputToolbarView pasteText:[UserSuggestionID.room stringByAppendingString:@" "]]; + [self.inputToolbarView pasteText:[CompletionSuggestionUserID.room stringByAppendingString:@" "]]; } -- (void)userSuggestionCoordinatorBridge:(UserSuggestionCoordinatorBridge *)coordinator - didRequestCommand:(NSString *)command - textTrigger:(NSString *)textTrigger +- (void)completionSuggestionCoordinatorBridge:(CompletionSuggestionCoordinatorBridge *)coordinator + didRequestCommand:(NSString *)command + textTrigger:(NSString *)textTrigger { [self removeTriggerTextFromComposer:textTrigger]; [self setCommand:command]; @@ -8097,11 +8097,11 @@ static CGSize kThreadListBarButtonItemImageSize; } } -- (void)userSuggestionCoordinatorBridge:(UserSuggestionCoordinatorBridge *)coordinator didUpdateViewHeight:(CGFloat)height +- (void)completionSuggestionCoordinatorBridge:(CompletionSuggestionCoordinatorBridge *)coordinator didUpdateViewHeight:(CGFloat)height { - if (self.userSuggestionContainerHeightConstraint.constant != height) + if (self.completionSuggestionContainerHeightConstraint.constant != height) { - self.userSuggestionContainerHeightConstraint.constant = height; + self.completionSuggestionContainerHeightConstraint.constant = height; [self.view layoutIfNeeded]; } diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index b7a62a8bf..cdb656508 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -1,9 +1,8 @@ - + - - + @@ -13,6 +12,8 @@ + + @@ -32,8 +33,6 @@ - - @@ -48,20 +47,20 @@ - + - + - + - + @@ -237,11 +236,6 @@ - - - - - diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index df71790be..5bbdeaa51 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -22,7 +22,7 @@ @class RoomInputToolbarView; @class LinkActionWrapper; @class SuggestionPatternWrapper; -@class UserSuggestionViewModelContextWrapper; +@class CompletionSuggestionViewModelContextWrapper; /** Destination of the message in the composer @@ -84,7 +84,7 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode) - (void)didDetectTextPattern: (SuggestionPatternWrapper *)suggestionPattern; -- (UserSuggestionViewModelContextWrapper *)userSuggestionContext; +- (CompletionSuggestionViewModelContextWrapper *)completionSuggestionContext; - (MXMediaManager *)mediaManager; diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index 5700909fa..9bc02c21e 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -223,7 +223,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp let composer = Composer( viewModel: viewModel.context, wysiwygViewModel: wysiwygViewModel, - userSuggestionSharedContext: toolbarViewDelegate.userSuggestionContext().context, + completionSuggestionSharedContext: toolbarViewDelegate.completionSuggestionContext().context, resizeAnimationDuration: Double(kResizeComposerAnimationDuration), sendMessageAction: { [weak self] content in guard let self = self else { return } diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index 91bf25a51..0afe12c02 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -51,7 +51,7 @@ enum MockAppScreens { MockStaticLocationViewingScreenState.self, MockLocationSharingScreenState.self, MockAnalyticsPromptScreenState.self, - MockUserSuggestionScreenState.self, + MockCompletionSuggestionScreenState.self, MockPollEditFormScreenState.self, MockSpaceCreationEmailInvitesScreenState.self, MockSpaceSettingsScreenState.self, diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionModels.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionModels.swift similarity index 76% rename from RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionModels.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionModels.swift index dbaaf9295..91fc4ffeb 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionModels.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionModels.swift @@ -16,15 +16,15 @@ import Foundation -enum UserSuggestionViewAction { - case selectedItem(UserSuggestionViewStateItem) +enum CompletionSuggestionViewAction { + case selectedItem(CompletionSuggestionViewStateItem) } -enum UserSuggestionViewModelResult { +enum CompletionSuggestionViewModelResult { case selectedItemWithIdentifier(String) } -enum UserSuggestionViewStateItem: Identifiable { +enum CompletionSuggestionViewStateItem: Identifiable { case command(name: String) case user(id: String, avatar: AvatarInputProtocol?, displayName: String?) @@ -38,6 +38,6 @@ enum UserSuggestionViewStateItem: Identifiable { } } -struct UserSuggestionViewState: BindableState { - var items: [UserSuggestionViewStateItem] +struct CompletionSuggestionViewState: BindableState { + var items: [CompletionSuggestionViewStateItem] } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionScreenState.swift similarity index 75% rename from RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionScreenState.swift index 95aea9dbe..1427c3f3f 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionScreenState.swift @@ -17,32 +17,32 @@ import Foundation import SwiftUI -enum MockUserSuggestionScreenState: MockScreenState, CaseIterable { +enum MockCompletionSuggestionScreenState: MockScreenState, CaseIterable { case multipleResults private static var members: [RoomMembersProviderMember]! var screenType: Any.Type { - UserSuggestionList.self + CompletionSuggestionList.self } var screenView: ([Any], AnyView) { - let service = UserSuggestionService(roomMemberProvider: self, commandProvider: self) - let listViewModel = UserSuggestionViewModel(userSuggestionService: service) + let service = CompletionSuggestionService(roomMemberProvider: self, commandProvider: self) + let listViewModel = CompletionSuggestionViewModel(completionSuggestionService: service) - let viewModel = UserSuggestionListWithInputViewModel(listViewModel: listViewModel) { textMessage in + let viewModel = CompletionSuggestionListWithInputViewModel(listViewModel: listViewModel) { textMessage in service.processTextMessage(textMessage) } return ( [service, listViewModel], - AnyView(UserSuggestionListWithInput(viewModel: viewModel) + AnyView(CompletionSuggestionListWithInput(viewModel: viewModel) .environmentObject(AvatarViewModel.withMockedServices())) ) } } -extension MockUserSuggestionScreenState: RoomMembersProviderProtocol { +extension MockCompletionSuggestionScreenState: RoomMembersProviderProtocol { var canMentionRoom: Bool { false } func fetchMembers(_ members: ([RoomMembersProviderMember]) -> Void) { @@ -61,7 +61,7 @@ extension MockUserSuggestionScreenState: RoomMembersProviderProtocol { } } -extension MockUserSuggestionScreenState: CommandsProviderProtocol { +extension MockCompletionSuggestionScreenState: CommandsProviderProtocol { func fetchCommands(_ commands: @escaping ([CommandsProviderCommand]) -> Void) { commands([ CommandsProviderCommand(name: "/ban"), diff --git a/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionViewModel.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionViewModel.swift new file mode 100644 index 000000000..01c881970 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionViewModel.swift @@ -0,0 +1,77 @@ +// +// 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 Combine +import SwiftUI + +typealias CompletionSuggestionViewModelType = StateStoreViewModel + +class CompletionSuggestionViewModel: CompletionSuggestionViewModelType, CompletionSuggestionViewModelProtocol { + // MARK: - Properties + + // MARK: Private + + private let completionSuggestionService: CompletionSuggestionServiceProtocol + + // MARK: Public + + var sharedContext: CompletionSuggestionViewModelType.Context { + return self.context + } + + var completion: ((CompletionSuggestionViewModelResult) -> Void)? + + // MARK: - Setup + + init(completionSuggestionService: CompletionSuggestionServiceProtocol) { + self.completionSuggestionService = completionSuggestionService + + let items = completionSuggestionService.items.value.map { suggestionItem in + switch suggestionItem { + case .command(let completionSuggestionCommandItem): + return CompletionSuggestionViewStateItem.command(name: completionSuggestionCommandItem.name) + case .user(let completionSuggestionUserItem): + return CompletionSuggestionViewStateItem.user(id: completionSuggestionUserItem.userId, + avatar: completionSuggestionUserItem, + displayName: completionSuggestionUserItem.displayName) + } + } + + super.init(initialViewState: CompletionSuggestionViewState(items: items)) + + completionSuggestionService.items.sink { [weak self] items in + self?.state.items = items.map { item in + switch item { + case .command(let completionSuggestionCommandItem): + return CompletionSuggestionViewStateItem.command(name: completionSuggestionCommandItem.name) + case .user(let completionSuggestionUserItem): + return CompletionSuggestionViewStateItem.user(id: completionSuggestionUserItem.userId, + avatar: completionSuggestionUserItem, + displayName: completionSuggestionUserItem.displayName) + } + } + }.store(in: &cancellables) + } + + // MARK: - Public + + override func process(viewAction: CompletionSuggestionViewAction) { + switch viewAction { + case .selectedItem(let item): + completion?(.selectedItemWithIdentifier(item.id)) + } + } +} diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionViewModelProtocol.swift similarity index 67% rename from RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModelProtocol.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionViewModelProtocol.swift index 33aa5bb79..d7c51909f 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/CompletionSuggestionViewModelProtocol.swift @@ -16,10 +16,10 @@ import Foundation -protocol UserSuggestionViewModelProtocol { - /// Defines a shared context providing the ability to use a single `UserSuggestionViewModel` for multiple - /// `UserSuggestionList` e.g. the list component can then be displayed seemlessly in both `RoomViewController` +protocol CompletionSuggestionViewModelProtocol { + /// Defines a shared context providing the ability to use a single `CompletionSuggestionViewModel` for multiple + /// `CompletionSuggestionList` e.g. the list component can then be displayed seemlessly in both `RoomViewController` /// UIKit hosted context, and in Rich-Text-Editor's SwiftUI fullscreen mode, without need to reload the data. - var sharedContext: UserSuggestionViewModelType.Context { get } - var completion: ((UserSuggestionViewModelResult) -> Void)? { get set } + var sharedContext: CompletionSuggestionViewModelType.Context { get } + var completion: ((CompletionSuggestionViewModelResult) -> Void)? { get set } } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift similarity index 59% rename from RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift index 1999e6c07..8da2356fd 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinator.swift @@ -20,40 +20,40 @@ import SwiftUI import UIKit import WysiwygComposer -protocol UserSuggestionCoordinatorDelegate: AnyObject { - func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) - func userSuggestionCoordinatorDidRequestMentionForRoom(_ coordinator: UserSuggestionCoordinator, textTrigger: String?) - func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestCommand command: String, textTrigger: String?) - func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didUpdateViewHeight height: CGFloat) +protocol CompletionSuggestionCoordinatorDelegate: AnyObject { + func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) + func completionSuggestionCoordinatorDidRequestMentionForRoom(_ coordinator: CompletionSuggestionCoordinator, textTrigger: String?) + func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didRequestCommand command: String, textTrigger: String?) + func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didUpdateViewHeight height: CGFloat) } -struct UserSuggestionCoordinatorParameters { +struct CompletionSuggestionCoordinatorParameters { let mediaManager: MXMediaManager let room: MXRoom let userID: String } -/// Wrapper around `UserSuggestionViewModelType.Context` to pass it through obj-c. -final class UserSuggestionViewModelContextWrapper: NSObject { - let context: UserSuggestionViewModelType.Context +/// Wrapper around `CompletionSuggestionViewModelType.Context` to pass it through obj-c. +final class CompletionSuggestionViewModelContextWrapper: NSObject { + let context: CompletionSuggestionViewModelType.Context - init(context: UserSuggestionViewModelType.Context) { + init(context: CompletionSuggestionViewModelType.Context) { self.context = context } } -final class UserSuggestionCoordinator: Coordinator, Presentable { +final class CompletionSuggestionCoordinator: Coordinator, Presentable { // MARK: - Properties // MARK: Private - private let parameters: UserSuggestionCoordinatorParameters + private let parameters: CompletionSuggestionCoordinatorParameters - private var userSuggestionHostingController: UIHostingController - private var userSuggestionService: UserSuggestionServiceProtocol - private var userSuggestionViewModel: UserSuggestionViewModelProtocol - private var roomMemberProvider: UserSuggestionCoordinatorRoomMemberProvider - private var commandProvider: UserSuggestionCoordinatorCommandProvider + private var completionSuggestionHostingController: UIHostingController + private var completionSuggestionService: CompletionSuggestionServiceProtocol + private var completionSuggestionViewModel: CompletionSuggestionViewModelProtocol + private var roomMemberProvider: CompletionSuggestionCoordinatorRoomMemberProvider + private var commandProvider: CompletionSuggestionCoordinatorCommandProvider private var cancellables = Set() @@ -63,57 +63,57 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { var childCoordinators: [Coordinator] = [] var completion: (() -> Void)? - weak var delegate: UserSuggestionCoordinatorDelegate? + weak var delegate: CompletionSuggestionCoordinatorDelegate? // MARK: - Setup - init(parameters: UserSuggestionCoordinatorParameters) { + init(parameters: CompletionSuggestionCoordinatorParameters) { self.parameters = parameters - roomMemberProvider = UserSuggestionCoordinatorRoomMemberProvider(room: parameters.room, userID: parameters.userID) - commandProvider = UserSuggestionCoordinatorCommandProvider(room: parameters.room, userID: parameters.userID) - userSuggestionService = UserSuggestionService(roomMemberProvider: roomMemberProvider, commandProvider: commandProvider) + roomMemberProvider = CompletionSuggestionCoordinatorRoomMemberProvider(room: parameters.room, userID: parameters.userID) + commandProvider = CompletionSuggestionCoordinatorCommandProvider(room: parameters.room, userID: parameters.userID) + completionSuggestionService = CompletionSuggestionService(roomMemberProvider: roomMemberProvider, commandProvider: commandProvider) - let viewModel = UserSuggestionViewModel(userSuggestionService: userSuggestionService) - let view = UserSuggestionList(viewModel: viewModel.context) + let viewModel = CompletionSuggestionViewModel(completionSuggestionService: completionSuggestionService) + let view = CompletionSuggestionList(viewModel: viewModel.context) .environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager))) - userSuggestionViewModel = viewModel - userSuggestionHostingController = VectorHostingController(rootView: view) + completionSuggestionViewModel = viewModel + completionSuggestionHostingController = VectorHostingController(rootView: view) - userSuggestionViewModel.completion = { [weak self] result in + completionSuggestionViewModel.completion = { [weak self] result in guard let self = self else { return } switch result { case .selectedItemWithIdentifier(let identifier): - if identifier == UserSuggestionID.room { - self.delegate?.userSuggestionCoordinatorDidRequestMentionForRoom(self, textTrigger: self.userSuggestionService.currentTextTrigger) + if identifier == CompletionSuggestionUserID.room { + self.delegate?.completionSuggestionCoordinatorDidRequestMentionForRoom(self, textTrigger: self.completionSuggestionService.currentTextTrigger) return } if let member = self.roomMemberProvider.roomMembers.filter({ $0.userId == identifier }).first { - self.delegate?.userSuggestionCoordinator(self, didRequestMentionForMember: member, textTrigger: self.userSuggestionService.currentTextTrigger) + self.delegate?.completionSuggestionCoordinator(self, didRequestMentionForMember: member, textTrigger: self.completionSuggestionService.currentTextTrigger) } else if let command = self.commandProvider.commands.filter({ $0 == identifier }).first { - self.delegate?.userSuggestionCoordinator(self, didRequestCommand: command, textTrigger: self.userSuggestionService.currentTextTrigger) + self.delegate?.completionSuggestionCoordinator(self, didRequestCommand: command, textTrigger: self.completionSuggestionService.currentTextTrigger) } } } - userSuggestionService.items.sink { [weak self] _ in + completionSuggestionService.items.sink { [weak self] _ in guard let self = self else { return } - self.delegate?.userSuggestionCoordinator(self, + self.delegate?.completionSuggestionCoordinator(self, didUpdateViewHeight: self.calculateViewHeight()) }.store(in: &cancellables) } func processTextMessage(_ textMessage: String) { - userSuggestionService.processTextMessage(textMessage) + completionSuggestionService.processTextMessage(textMessage) } func processSuggestionPattern(_ suggestionPattern: SuggestionPattern?) { - userSuggestionService.processSuggestionPattern(suggestionPattern) + completionSuggestionService.processSuggestionPattern(suggestionPattern) } // MARK: - Public @@ -121,18 +121,18 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { func start() { } func toPresentable() -> UIViewController { - userSuggestionHostingController + completionSuggestionHostingController } - func sharedContext() -> UserSuggestionViewModelContextWrapper { - UserSuggestionViewModelContextWrapper(context: userSuggestionViewModel.sharedContext) + func sharedContext() -> CompletionSuggestionViewModelContextWrapper { + CompletionSuggestionViewModelContextWrapper(context: completionSuggestionViewModel.sharedContext) } // MARK: - Private private func calculateViewHeight() -> CGFloat { - let viewModel = UserSuggestionViewModel(userSuggestionService: userSuggestionService) - let view = UserSuggestionList(viewModel: viewModel.context) + let viewModel = CompletionSuggestionViewModel(completionSuggestionService: completionSuggestionService) + let view = CompletionSuggestionList(viewModel: viewModel.context) .environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager))) let controller = VectorHostingController(rootView: view) @@ -156,7 +156,7 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { } } -private class UserSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderProtocol { +private class CompletionSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderProtocol { private let room: MXRoom private let userID: String @@ -194,7 +194,7 @@ private class UserSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderPr self.roomMembers = joinedMembers members(self.roomMembersToProviderMembers(joinedMembers)) } failure: { error in - MXLog.error("[UserSuggestionCoordinatorRoomMemberProvider] Failed loading room", context: error) + MXLog.error("[CompletionSuggestionCoordinatorRoomMemberProvider] Failed loading room", context: error) } } @@ -203,7 +203,7 @@ private class UserSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderPr } } -private class UserSuggestionCoordinatorCommandProvider: CommandsProviderProtocol { +private class CompletionSuggestionCoordinatorCommandProvider: CommandsProviderProtocol { private let room: MXRoom private let userID: String diff --git a/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinatorBridge.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinatorBridge.swift new file mode 100644 index 000000000..83a9ed94c --- /dev/null +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Coordinator/CompletionSuggestionCoordinatorBridge.swift @@ -0,0 +1,79 @@ +// +// 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 + +@objc +protocol CompletionSuggestionCoordinatorBridgeDelegate: AnyObject { + func completionSuggestionCoordinatorBridge(_ coordinator: CompletionSuggestionCoordinatorBridge, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) + func completionSuggestionCoordinatorBridgeDidRequestMentionForRoom(_ coordinator: CompletionSuggestionCoordinatorBridge, textTrigger: String?) + func completionSuggestionCoordinatorBridge(_ coordinator: CompletionSuggestionCoordinatorBridge, didRequestCommand command: String, textTrigger: String?) + func completionSuggestionCoordinatorBridge(_ coordinator: CompletionSuggestionCoordinatorBridge, didUpdateViewHeight height: CGFloat) +} + +@objcMembers +final class CompletionSuggestionCoordinatorBridge: NSObject { + private var _completionSuggestionCoordinator: Any? + fileprivate var completionSuggestionCoordinator: CompletionSuggestionCoordinator { + _completionSuggestionCoordinator as! CompletionSuggestionCoordinator + } + + weak var delegate: CompletionSuggestionCoordinatorBridgeDelegate? + + init(mediaManager: MXMediaManager, room: MXRoom, userID: String) { + let parameters = CompletionSuggestionCoordinatorParameters(mediaManager: mediaManager, room: room, userID: userID) + let completionSuggestionCoordinator = CompletionSuggestionCoordinator(parameters: parameters) + _completionSuggestionCoordinator = completionSuggestionCoordinator + + super.init() + + completionSuggestionCoordinator.delegate = self + } + + func processTextMessage(_ textMessage: String) { + completionSuggestionCoordinator.processTextMessage(textMessage) + } + + func processSuggestionPattern(_ suggestionPatternWrapper: SuggestionPatternWrapper) { + completionSuggestionCoordinator.processSuggestionPattern(suggestionPatternWrapper.suggestionPattern) + } + + func toPresentable() -> UIViewController? { + completionSuggestionCoordinator.toPresentable() + } + + func sharedContext() -> CompletionSuggestionViewModelContextWrapper { + completionSuggestionCoordinator.sharedContext() + } +} + +extension CompletionSuggestionCoordinatorBridge: CompletionSuggestionCoordinatorDelegate { + func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) { + delegate?.completionSuggestionCoordinatorBridge(self, didRequestMentionForMember: member, textTrigger: textTrigger) + } + + func completionSuggestionCoordinatorDidRequestMentionForRoom(_ coordinator: CompletionSuggestionCoordinator, textTrigger: String?) { + delegate?.completionSuggestionCoordinatorBridgeDidRequestMentionForRoom(self, textTrigger: textTrigger) + } + + func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didRequestCommand command: String, textTrigger: String?) { + delegate?.completionSuggestionCoordinatorBridge(self, didRequestCommand: command, textTrigger: textTrigger) + } + + func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didUpdateViewHeight height: CGFloat) { + delegate?.completionSuggestionCoordinatorBridge(self, didUpdateViewHeight: height) + } +} diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionService.swift similarity index 80% rename from RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionService.swift index 76d41e700..0353b63d4 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionService.swift @@ -28,7 +28,7 @@ struct CommandsProviderCommand { var name: String } -class UserSuggestionID: NSObject { +class CompletionSuggestionUserID: NSObject { /// A special case added for suggesting `@room` mentions. @objc static let room = "@room" } @@ -42,17 +42,17 @@ protocol CommandsProviderProtocol { func fetchCommands(_ commands: @escaping ([CommandsProviderCommand]) -> Void) } -struct UserSuggestionServiceItem: UserSuggestionItemProtocol { +struct CompletionSuggestionServiceUserItem: CompletionSuggestionUserItemProtocol { let userId: String let displayName: String? let avatarUrl: String? } -struct CommandSuggestionServiceItem: CommandSuggestionItemProtocol { +struct CompletionSuggestionServiceCommandItem: CompletionSuggestionCommandItemProtocol { let name: String } -class UserSuggestionService: UserSuggestionServiceProtocol { +class CompletionSuggestionService: CompletionSuggestionServiceProtocol { // MARK: - Properties // MARK: Private @@ -60,13 +60,13 @@ class UserSuggestionService: UserSuggestionServiceProtocol { private let roomMemberProvider: RoomMembersProviderProtocol private let commandProvider: CommandsProviderProtocol - private var suggestionItems: [SuggestionItem] = [] + private var suggestionItems: [CompletionSuggestionItem] = [] private let currentTextTriggerSubject = CurrentValueSubject(nil) private var cancellables = Set() // MARK: Public - var items = CurrentValueSubject<[SuggestionItem], Never>([]) + var items = CurrentValueSubject<[CompletionSuggestionItem], Never>([]) var currentTextTrigger: String? { currentTextTriggerSubject.value @@ -93,7 +93,7 @@ class UserSuggestionService: UserSuggestionServiceProtocol { } } - // MARK: - UserSuggestionServiceProtocol + // MARK: - CompletionSuggestionServiceProtocol func processTextMessage(_ textMessage: String?) { guard let textMessage = textMessage, @@ -145,14 +145,14 @@ class UserSuggestionService: UserSuggestionServiceProtocol { } self.suggestionItems = members.withRoom(self.roomMemberProvider.canMentionRoom).map { member in - SuggestionItem.user(value: UserSuggestionServiceItem(userId: member.userId, displayName: member.displayName, avatarUrl: member.avatarUrl)) + CompletionSuggestionItem.user(value: CompletionSuggestionServiceUserItem(userId: member.userId, displayName: member.displayName, avatarUrl: member.avatarUrl)) } self.items.send(self.suggestionItems.filter { item in - guard case let .user(userSuggestion) = item else { return false } + guard case let .user(completionSuggestionUserItem) = item else { return false } - let containedInUsername = userSuggestion.userId.lowercased().contains(partialName.lowercased()) - let containedInDisplayName = (userSuggestion.displayName ?? "").lowercased().contains(partialName.lowercased()) + let containedInUsername = completionSuggestionUserItem.userId.lowercased().contains(partialName.lowercased()) + let containedInDisplayName = (completionSuggestionUserItem.displayName ?? "").lowercased().contains(partialName.lowercased()) return (containedInUsername || containedInDisplayName) }) @@ -165,7 +165,7 @@ class UserSuggestionService: UserSuggestionServiceProtocol { guard let self else { return } self.suggestionItems = commands.map { command in - SuggestionItem.command(value: CommandSuggestionServiceItem(name: command.name)) + CompletionSuggestionItem.command(value: CompletionSuggestionServiceCommandItem(name: command.name)) } self.items.send(self.suggestionItems.filter { item in @@ -184,6 +184,6 @@ extension Array where Element == RoomMembersProviderMember { /// Returns the array with an additional member that represents an `@room` mention. func withRoom(_ canMentionRoom: Bool) -> Self { guard canMentionRoom else { return self } - return self + [RoomMembersProviderMember(userId: UserSuggestionID.room, displayName: "Everyone", avatarUrl: "")] + return self + [RoomMembersProviderMember(userId: CompletionSuggestionUserID.room, displayName: "Everyone", avatarUrl: "")] } } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionServiceProtocol.swift similarity index 71% rename from RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionServiceProtocol.swift index 4b5787cff..4586e1294 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionServiceProtocol.swift @@ -18,23 +18,23 @@ import Combine import Foundation import WysiwygComposer -protocol UserSuggestionItemProtocol: Avatarable { +protocol CompletionSuggestionUserItemProtocol: Avatarable { var userId: String { get } var displayName: String? { get } var avatarUrl: String? { get } } -protocol CommandSuggestionItemProtocol { +protocol CompletionSuggestionCommandItemProtocol { var name: String { get } } -enum SuggestionItem { - case command(value: CommandSuggestionItemProtocol) - case user(value: UserSuggestionItemProtocol) +enum CompletionSuggestionItem { + case command(value: CompletionSuggestionCommandItemProtocol) + case user(value: CompletionSuggestionUserItemProtocol) } -protocol UserSuggestionServiceProtocol { - var items: CurrentValueSubject<[SuggestionItem], Never> { get } +protocol CompletionSuggestionServiceProtocol { + var items: CurrentValueSubject<[CompletionSuggestionItem], Never> { get } var currentTextTrigger: String? { get } @@ -44,7 +44,7 @@ protocol UserSuggestionServiceProtocol { // MARK: Avatarable -extension UserSuggestionItemProtocol { +extension CompletionSuggestionUserItemProtocol { var mxContentUri: String? { avatarUrl } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Test/UI/CompletionSuggestionUITests.swift similarity index 79% rename from RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/Test/UI/CompletionSuggestionUITests.swift index f44744a9c..5ec9d4b9b 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Test/UI/CompletionSuggestionUITests.swift @@ -17,9 +17,9 @@ import RiotSwiftUI import XCTest -class UserSuggestionUITests: MockScreenTestCase { - func testUserSuggestionScreen() throws { - app.goToScreenWithIdentifier(MockUserSuggestionScreenState.multipleResults.title) +class CompletionSuggestionUITests: MockScreenTestCase { + func testCompletionSuggestionScreen() throws { + app.goToScreenWithIdentifier(MockCompletionSuggestionScreenState.multipleResults.title) let firstButton = app.buttons["displayNameText-userIdText"].firstMatch XCTAssert(firstButton.waitForExistence(timeout: 10)) diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Test/Unit/CompletionSuggestionServiceTests.swift similarity index 61% rename from RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/Test/Unit/CompletionSuggestionServiceTests.swift index 7ae0bfa39..636ba3355 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Test/Unit/CompletionSuggestionServiceTests.swift @@ -19,51 +19,53 @@ import XCTest @testable import RiotSwiftUI -class UserSuggestionServiceTests: XCTestCase { - var service: UserSuggestionService! +class CompletionSuggestionServiceTests: XCTestCase { + var service: CompletionSuggestionService! var canMentionRoom = false override func setUp() { - service = UserSuggestionService(roomMemberProvider: self, shouldDebounce: false) + service = CompletionSuggestionService(roomMemberProvider: self, + commandProvider: self, + shouldDebounce: false) canMentionRoom = false } func testAlice() { service.processTextMessage("@Al") - XCTAssertEqual(service.items.value.first?.displayName, "Alice") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice") service.processTextMessage("@al") - XCTAssertEqual(service.items.value.first?.displayName, "Alice") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice") service.processTextMessage("@ice") - XCTAssertEqual(service.items.value.first?.displayName, "Alice") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice") service.processTextMessage("@Alice") - XCTAssertEqual(service.items.value.first?.displayName, "Alice") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice") service.processTextMessage("@alice:matrix.org") - XCTAssertEqual(service.items.value.first?.displayName, "Alice") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice") } func testBob() { service.processTextMessage("@ob") - XCTAssertEqual(service.items.value.first?.displayName, "Bob") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Bob") service.processTextMessage("@ob:") - XCTAssertEqual(service.items.value.first?.displayName, "Bob") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Bob") service.processTextMessage("@b:matrix") - XCTAssertEqual(service.items.value.first?.displayName, "Bob") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Bob") } func testBoth() { service.processTextMessage("@:matrix") - XCTAssertEqual(service.items.value.first?.displayName, "Alice") - XCTAssertEqual(service.items.value.last?.displayName, "Bob") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice") + XCTAssertEqual(service.items.value.last?.asUser?.displayName, "Bob") service.processTextMessage("@.org") - XCTAssertEqual(service.items.value.first?.displayName, "Alice") - XCTAssertEqual(service.items.value.last?.displayName, "Bob") + XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice") + XCTAssertEqual(service.items.value.last?.asUser?.displayName, "Bob") } func testEmptyResult() { @@ -117,18 +119,18 @@ class UserSuggestionServiceTests: XCTestCase { } func testRoomWithPower() { - // Given a user without the power to mention a room. + // Given a user with the power to mention a room. canMentionRoom = true - // Given a user without the power to mention a room. + // Given a user with the power to mention a room. service.processTextMessage("@ro") // Then the completion for a room mention should be shown. - XCTAssertEqual(service.items.value.first?.userId, UserSuggestionID.room) + XCTAssertEqual(service.items.value.first?.asUser?.userId, CompletionSuggestionUserID.room) } } -extension UserSuggestionServiceTests: RoomMembersProviderProtocol { +extension CompletionSuggestionServiceTests: RoomMembersProviderProtocol { func fetchMembers(_ members: @escaping ([RoomMembersProviderMember]) -> Void) { let users = [("Alice", "@alice:matrix.org"), ("Bob", "@bob:matrix.org")] @@ -138,3 +140,23 @@ extension UserSuggestionServiceTests: RoomMembersProviderProtocol { }) } } + +extension CompletionSuggestionServiceTests: CommandsProviderProtocol { + func fetchCommands(_ commands: @escaping ([CommandsProviderCommand]) -> Void) { + let commandList = ["/ban", "/invite", "/join", "/me"] + + commands(commandList.map { command in + CommandsProviderCommand(name: command) + }) + } +} + +extension CompletionSuggestionItem { + var asUser: CompletionSuggestionUserItemProtocol? { + if case let .user(value) = self { return value } else { return nil } + } + + var asCommand: CompletionSuggestionCommandItemProtocol? { + if case let .command(value) = self { return value } else { return nil } + } +} diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionList.swift similarity index 92% rename from RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionList.swift index fe0c21761..02aef8a1f 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionList.swift @@ -16,7 +16,7 @@ import SwiftUI -struct UserSuggestionList: View { +struct CompletionSuggestionList: View { private enum Constants { static let topPadding: CGFloat = 8.0 static let listItemPadding: CGFloat = 4.0 @@ -43,7 +43,7 @@ struct UserSuggestionList: View { // MARK: Public - @ObservedObject var viewModel: UserSuggestionViewModel.Context + @ObservedObject var viewModel: CompletionSuggestionViewModel.Context var showBackgroundShadow: Bool = true var body: some View { @@ -51,7 +51,7 @@ struct UserSuggestionList: View { EmptyView() } else { ZStack { - UserSuggestionListItem(content: UserSuggestionViewStateItem.user( + CompletionSuggestionListItem(content: CompletionSuggestionViewStateItem.user( id: "Prototype", avatar: AvatarInput(mxContentUri: "", matrixItemId: "", @@ -79,7 +79,7 @@ struct UserSuggestionList: View { Button { viewModel.send(viewAction: .selectedItem(item)) } label: { - UserSuggestionListItem(content: item) + CompletionSuggestionListItem(content: item) .modifier(ListItemPaddingModifier(isFirst: viewModel.viewState.items.first?.id == item.id)) } } @@ -134,8 +134,8 @@ private struct BackgroundView: View { // MARK: - Previews -struct UserSuggestion_Previews: PreviewProvider { - static let stateRenderer = MockUserSuggestionScreenState.stateRenderer +struct CompletionSuggestion_Previews: PreviewProvider { + static let stateRenderer = MockCompletionSuggestionScreenState.stateRenderer static var previews: some View { stateRenderer.screenGroup() } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionListItem.swift similarity index 89% rename from RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionListItem.swift index 0175c2abe..c30ec5d89 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionListItem.swift @@ -16,7 +16,7 @@ import SwiftUI -struct UserSuggestionListItem: View { +struct CompletionSuggestionListItem: View { // MARK: - Properties // MARK: Private @@ -25,7 +25,7 @@ struct UserSuggestionListItem: View { // MARK: Public - let content: UserSuggestionViewStateItem + let content: CompletionSuggestionViewStateItem var body: some View { HStack { @@ -59,9 +59,9 @@ struct UserSuggestionListItem: View { // MARK: - Previews -struct UserSuggestionHeader_Previews: PreviewProvider { +struct CompletionSuggestionHeader_Previews: PreviewProvider { static var previews: some View { - UserSuggestionListItem(content: UserSuggestionViewStateItem.user( + CompletionSuggestionListItem(content: CompletionSuggestionViewStateItem.user( id: "@alice:matrix.org", avatar: MockAvatarInput.example, displayName: "Alice" diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionListWithInput.swift similarity index 75% rename from RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift rename to RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionListWithInput.swift index 176be8ec4..0b1dd8e8a 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/View/CompletionSuggestionListWithInput.swift @@ -16,24 +16,24 @@ import SwiftUI -struct UserSuggestionListWithInputViewModel { - let listViewModel: UserSuggestionViewModel +struct CompletionSuggestionListWithInputViewModel { + let listViewModel: CompletionSuggestionViewModel let callback: (String) -> Void } -struct UserSuggestionListWithInput: View { +struct CompletionSuggestionListWithInput: View { // MARK: - Properties // MARK: Private // MARK: Public - var viewModel: UserSuggestionListWithInputViewModel + var viewModel: CompletionSuggestionListWithInputViewModel @State private var inputText = "" var body: some View { VStack(spacing: 0.0) { - UserSuggestionList(viewModel: viewModel.listViewModel.context) + CompletionSuggestionList(viewModel: viewModel.listViewModel.context) TextField("Search for user", text: $inputText) .background(Color.white) .onChange(of: inputText, perform: viewModel.callback) @@ -48,8 +48,8 @@ struct UserSuggestionListWithInput: View { // MARK: - Previews -struct UserSuggestionListWithInput_Previews: PreviewProvider { - static let stateRenderer = MockUserSuggestionScreenState.stateRenderer +struct CompletionSuggestionListWithInput_Previews: PreviewProvider { + static let stateRenderer = MockCompletionSuggestionScreenState.stateRenderer static var previews: some View { stateRenderer.screenGroup() } diff --git a/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift b/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift index 8b5327b14..79322b78a 100644 --- a/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift +++ b/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift @@ -29,7 +29,7 @@ enum MockComposerScreenState: MockScreenState, CaseIterable { var screenView: ([Any], AnyView) { let viewModel: ComposerViewModel - let userSuggestionViewModel = MockUserSuggestionViewModel(initialViewState: UserSuggestionViewState(items: [])) + let completionSuggestionViewModel = MockCompletionSuggestionViewModel(initialViewState: CompletionSuggestionViewState(items: [])) let bindings = ComposerBindings(focused: false) switch self { @@ -67,7 +67,7 @@ enum MockComposerScreenState: MockScreenState, CaseIterable { Spacer() Composer(viewModel: viewModel.context, wysiwygViewModel: wysiwygviewModel, - userSuggestionSharedContext: userSuggestionViewModel.context, + completionSuggestionSharedContext: completionSuggestionViewModel.context, resizeAnimationDuration: 0.1, sendMessageAction: { _ in }, showSendMediaActions: { }) @@ -82,6 +82,4 @@ enum MockComposerScreenState: MockScreenState, CaseIterable { } } -private final class MockUserSuggestionViewModel: UserSuggestionViewModelType { - -} +private final class MockCompletionSuggestionViewModel: CompletionSuggestionViewModelType { } diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift index 6f7bab165..33d73ef4a 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift @@ -257,11 +257,11 @@ final class SuggestionPatternWrapper: NSObject { } } -final class UserSuggestionViewModelWrapper: NSObject { - let userSuggestionViewModel: UserSuggestionViewModel +final class CompletionSuggestionViewModelWrapper: NSObject { + let completionSuggestionViewModel: CompletionSuggestionViewModel - init(_ userSuggestionViewModel: UserSuggestionViewModel) { - self.userSuggestionViewModel = userSuggestionViewModel + init(_ completionSuggestionViewModel: CompletionSuggestionViewModel) { + self.completionSuggestionViewModel = completionSuggestionViewModel super.init() } } diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index e4317a275..a74b0bb4d 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -23,7 +23,7 @@ struct Composer: View { // MARK: Private @ObservedObject private var viewModel: ComposerViewModelType.Context @ObservedObject private var wysiwygViewModel: WysiwygComposerViewModel - private let userSuggestionSharedContext: UserSuggestionViewModelType.Context + private let completionSuggestionSharedContext: CompletionSuggestionViewModelType.Context private let resizeAnimationDuration: Double private let sendMessageAction: (WysiwygComposerContent) -> Void @@ -223,13 +223,13 @@ struct Composer: View { init( viewModel: ComposerViewModelType.Context, wysiwygViewModel: WysiwygComposerViewModel, - userSuggestionSharedContext: UserSuggestionViewModelType.Context, + completionSuggestionSharedContext: CompletionSuggestionViewModelType.Context, resizeAnimationDuration: Double, sendMessageAction: @escaping (WysiwygComposerContent) -> Void, showSendMediaActions: @escaping () -> Void) { self.viewModel = viewModel self.wysiwygViewModel = wysiwygViewModel - self.userSuggestionSharedContext = userSuggestionSharedContext + self.completionSuggestionSharedContext = completionSuggestionSharedContext self.resizeAnimationDuration = resizeAnimationDuration self.sendMessageAction = sendMessageAction self.showSendMediaActions = showSendMediaActions @@ -256,7 +256,7 @@ struct Composer: View { } } if wysiwygViewModel.maximised { - UserSuggestionList(viewModel: userSuggestionSharedContext, showBackgroundShadow: false) + CompletionSuggestionList(viewModel: completionSuggestionSharedContext, showBackgroundShadow: false) } } .frame(height: composerHeight) diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift deleted file mode 100644 index ba1bc75ca..000000000 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// 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 - -@objc -protocol UserSuggestionCoordinatorBridgeDelegate: AnyObject { - func userSuggestionCoordinatorBridge(_ coordinator: UserSuggestionCoordinatorBridge, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) - func userSuggestionCoordinatorBridgeDidRequestMentionForRoom(_ coordinator: UserSuggestionCoordinatorBridge, textTrigger: String?) - func userSuggestionCoordinatorBridge(_ coordinator: UserSuggestionCoordinatorBridge, didRequestCommand command: String, textTrigger: String?) - func userSuggestionCoordinatorBridge(_ coordinator: UserSuggestionCoordinatorBridge, didUpdateViewHeight height: CGFloat) -} - -@objcMembers -final class UserSuggestionCoordinatorBridge: NSObject { - private var _userSuggestionCoordinator: Any? - fileprivate var userSuggestionCoordinator: UserSuggestionCoordinator { - _userSuggestionCoordinator as! UserSuggestionCoordinator - } - - weak var delegate: UserSuggestionCoordinatorBridgeDelegate? - - init(mediaManager: MXMediaManager, room: MXRoom, userID: String) { - let parameters = UserSuggestionCoordinatorParameters(mediaManager: mediaManager, room: room, userID: userID) - let userSuggestionCoordinator = UserSuggestionCoordinator(parameters: parameters) - _userSuggestionCoordinator = userSuggestionCoordinator - - super.init() - - userSuggestionCoordinator.delegate = self - } - - func processTextMessage(_ textMessage: String) { - userSuggestionCoordinator.processTextMessage(textMessage) - } - - func processSuggestionPattern(_ suggestionPatternWrapper: SuggestionPatternWrapper) { - userSuggestionCoordinator.processSuggestionPattern(suggestionPatternWrapper.suggestionPattern) - } - - func toPresentable() -> UIViewController? { - userSuggestionCoordinator.toPresentable() - } - - func sharedContext() -> UserSuggestionViewModelContextWrapper { - userSuggestionCoordinator.sharedContext() - } -} - -extension UserSuggestionCoordinatorBridge: UserSuggestionCoordinatorDelegate { - func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) { - delegate?.userSuggestionCoordinatorBridge(self, didRequestMentionForMember: member, textTrigger: textTrigger) - } - - func userSuggestionCoordinatorDidRequestMentionForRoom(_ coordinator: UserSuggestionCoordinator, textTrigger: String?) { - delegate?.userSuggestionCoordinatorBridgeDidRequestMentionForRoom(self, textTrigger: textTrigger) - } - - func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestCommand command: String, textTrigger: String?) { - delegate?.userSuggestionCoordinatorBridge(self, didRequestCommand: command, textTrigger: textTrigger) - } - - func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didUpdateViewHeight height: CGFloat) { - delegate?.userSuggestionCoordinatorBridge(self, didUpdateViewHeight: height) - } -} diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift deleted file mode 100644 index 68d573bdf..000000000 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// 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 Combine -import SwiftUI - -typealias UserSuggestionViewModelType = StateStoreViewModel - -class UserSuggestionViewModel: UserSuggestionViewModelType, UserSuggestionViewModelProtocol { - // MARK: - Properties - - // MARK: Private - - private let userSuggestionService: UserSuggestionServiceProtocol - - // MARK: Public - - var sharedContext: UserSuggestionViewModelType.Context { - return self.context - } - - var completion: ((UserSuggestionViewModelResult) -> Void)? - - // MARK: - Setup - - init(userSuggestionService: UserSuggestionServiceProtocol) { - self.userSuggestionService = userSuggestionService - - let items = userSuggestionService.items.value.map { suggestionItem in - switch suggestionItem { - case .command(let commandSuggestionItem): - return UserSuggestionViewStateItem.command(name: commandSuggestionItem.name) - case .user(let userSuggestionItem): - return UserSuggestionViewStateItem.user(id: userSuggestionItem.userId, - avatar: userSuggestionItem, - displayName: userSuggestionItem.displayName) - } - } - - super.init(initialViewState: UserSuggestionViewState(items: items)) - - userSuggestionService.items.sink { [weak self] items in - self?.state.items = items.map { item in - switch item { - case .command(let commandSuggestionItem): - return UserSuggestionViewStateItem.command(name: commandSuggestionItem.name) - case .user(let userSuggestionItem): - return UserSuggestionViewStateItem.user(id: userSuggestionItem.userId, - avatar: userSuggestionItem, - displayName: userSuggestionItem.displayName) - } - } - }.store(in: &cancellables) - } - - // MARK: - Public - - override func process(viewAction: UserSuggestionViewAction) { - switch viewAction { - case .selectedItem(let item): - completion?(.selectedItemWithIdentifier(item.id)) - } - } -}