mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 15:22:39 +00:00
Rename UserSuggestion
module as CompletionSuggestion
This commit is contained in:
parent
3cdbc26aed
commit
6d981004ed
24 changed files with 353 additions and 339 deletions
|
@ -61,7 +61,7 @@ extern NSTimeInterval const kResizeComposerAnimationDuration;
|
||||||
// The preview header
|
// The preview header
|
||||||
@property (weak, nonatomic, nullable) IBOutlet UIView *previewHeaderContainer;
|
@property (weak, nonatomic, nullable) IBOutlet UIView *previewHeaderContainer;
|
||||||
@property (weak, nonatomic, nullable) IBOutlet NSLayoutConstraint *previewHeaderContainerHeightConstraint;
|
@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
|
// The jump to last unread banner
|
||||||
@property (weak, nonatomic, nullable) IBOutlet UIView *jumpToLastUnreadBannerContainer;
|
@property (weak, nonatomic, nullable) IBOutlet UIView *jumpToLastUnreadBannerContainer;
|
||||||
|
|
|
@ -97,7 +97,7 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
|
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
|
||||||
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
|
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
|
||||||
ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate,
|
ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate,
|
||||||
RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate, UserSuggestionCoordinatorBridgeDelegate, ThreadsCoordinatorBridgePresenterDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, MXThreadingServiceDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate, RoomInputToolbarViewDelegate, ComposerCreateActionListBridgePresenterDelegate>
|
RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate, CompletionSuggestionCoordinatorBridgeDelegate, ThreadsCoordinatorBridgePresenterDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, MXThreadingServiceDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate, RoomInputToolbarViewDelegate, ComposerCreateActionListBridgePresenterDelegate>
|
||||||
{
|
{
|
||||||
|
|
||||||
// The preview header
|
// The preview header
|
||||||
|
@ -223,8 +223,8 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
@property (nonatomic, strong) ShareManager *shareManager;
|
@property (nonatomic, strong) ShareManager *shareManager;
|
||||||
@property (nonatomic, strong) EventMenuBuilder *eventMenuBuilder;
|
@property (nonatomic, strong) EventMenuBuilder *eventMenuBuilder;
|
||||||
|
|
||||||
@property (nonatomic, strong) UserSuggestionCoordinatorBridge *userSuggestionCoordinator;
|
@property (nonatomic, strong) CompletionSuggestionCoordinatorBridge *completionSuggestionCoordinator;
|
||||||
@property (nonatomic, weak) IBOutlet UIView *userSuggestionContainerView;
|
@property (nonatomic, weak) IBOutlet UIView *completionSuggestionContainerView;
|
||||||
|
|
||||||
@property (nonatomic, readwrite) RoomDisplayConfiguration *displayConfiguration;
|
@property (nonatomic, readwrite) RoomDisplayConfiguration *displayConfiguration;
|
||||||
|
|
||||||
|
@ -416,7 +416,7 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
|
|
||||||
[self setupActions];
|
[self setupActions];
|
||||||
|
|
||||||
[self setupUserSuggestionViewIfNeeded];
|
[self setupCompletionSuggestionViewIfNeeded];
|
||||||
|
|
||||||
[self.topBannersStackView vc_removeAllSubviews];
|
[self.topBannersStackView vc_removeAllSubviews];
|
||||||
}
|
}
|
||||||
|
@ -1088,12 +1088,12 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
[VoiceMessageMediaServiceProvider.sharedProvider setCurrentRoomSummary:dataSource.room.summary];
|
[VoiceMessageMediaServiceProvider.sharedProvider setCurrentRoomSummary:dataSource.room.summary];
|
||||||
_voiceMessageController.roomId = dataSource.roomId;
|
_voiceMessageController.roomId = dataSource.roomId;
|
||||||
|
|
||||||
_userSuggestionCoordinator = [[UserSuggestionCoordinatorBridge alloc] initWithMediaManager:self.roomDataSource.mxSession.mediaManager
|
_completionSuggestionCoordinator = [[CompletionSuggestionCoordinatorBridge alloc] initWithMediaManager:self.roomDataSource.mxSession.mediaManager
|
||||||
room:dataSource.room
|
room:dataSource.room
|
||||||
userID:self.roomDataSource.mxSession.myUserId];
|
userID:self.roomDataSource.mxSession.myUserId];
|
||||||
_userSuggestionCoordinator.delegate = self;
|
_completionSuggestionCoordinator.delegate = self;
|
||||||
|
|
||||||
[self setupUserSuggestionViewIfNeeded];
|
[self setupCompletionSuggestionViewIfNeeded];
|
||||||
|
|
||||||
[self updateTopBanners];
|
[self updateTopBanners];
|
||||||
}
|
}
|
||||||
|
@ -2726,13 +2726,13 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setupUserSuggestionViewIfNeeded
|
- (void)setupCompletionSuggestionViewIfNeeded
|
||||||
{
|
{
|
||||||
if(!self.isViewLoaded) {
|
if(!self.isViewLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UIViewController *suggestionsViewController = self.userSuggestionCoordinator.toPresentable;
|
UIViewController *suggestionsViewController = self.completionSuggestionCoordinator.toPresentable;
|
||||||
|
|
||||||
if (!suggestionsViewController)
|
if (!suggestionsViewController)
|
||||||
{
|
{
|
||||||
|
@ -2742,12 +2742,12 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
[suggestionsViewController.view setTranslatesAutoresizingMaskIntoConstraints:NO];
|
[suggestionsViewController.view setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
[self addChildViewController:suggestionsViewController];
|
[self addChildViewController:suggestionsViewController];
|
||||||
[self.userSuggestionContainerView addSubview:suggestionsViewController.view];
|
[self.completionSuggestionContainerView addSubview:suggestionsViewController.view];
|
||||||
|
|
||||||
[NSLayoutConstraint activateConstraints:@[[suggestionsViewController.view.topAnchor constraintEqualToAnchor:self.userSuggestionContainerView.topAnchor],
|
[NSLayoutConstraint activateConstraints:@[[suggestionsViewController.view.topAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.topAnchor],
|
||||||
[suggestionsViewController.view.leadingAnchor constraintEqualToAnchor:self.userSuggestionContainerView.leadingAnchor],
|
[suggestionsViewController.view.leadingAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.leadingAnchor],
|
||||||
[suggestionsViewController.view.trailingAnchor constraintEqualToAnchor:self.userSuggestionContainerView.trailingAnchor],
|
[suggestionsViewController.view.trailingAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.trailingAnchor],
|
||||||
[suggestionsViewController.view.bottomAnchor constraintEqualToAnchor:self.userSuggestionContainerView.bottomAnchor],]];
|
[suggestionsViewController.view.bottomAnchor constraintEqualToAnchor:self.completionSuggestionContainerView.bottomAnchor],]];
|
||||||
|
|
||||||
[suggestionsViewController didMoveToParentViewController:self];
|
[suggestionsViewController didMoveToParentViewController:self];
|
||||||
}
|
}
|
||||||
|
@ -5147,17 +5147,17 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
|
|
||||||
- (void)roomInputToolbarViewDidChangeTextMessage:(RoomInputToolbarView *)toolbarView
|
- (void)roomInputToolbarViewDidChangeTextMessage:(RoomInputToolbarView *)toolbarView
|
||||||
{
|
{
|
||||||
[self.userSuggestionCoordinator processTextMessage:toolbarView.textMessage];
|
[self.completionSuggestionCoordinator processTextMessage:toolbarView.textMessage];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didDetectTextPattern:(SuggestionPatternWrapper *)suggestionPattern
|
- (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
|
- (MXMediaManager *)mediaManager
|
||||||
|
@ -8059,9 +8059,9 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
[[LegacyAppDelegate theDelegate] openSpaceWithId:spaceId];
|
[[LegacyAppDelegate theDelegate] openSpaceWithId:spaceId];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - UserSuggestionCoordinatorBridgeDelegate
|
#pragma mark - CompletionSuggestionCoordinatorBridgeDelegate
|
||||||
|
|
||||||
- (void)userSuggestionCoordinatorBridge:(UserSuggestionCoordinatorBridge *)coordinator
|
- (void)completionSuggestionCoordinatorBridge:(CompletionSuggestionCoordinatorBridge *)coordinator
|
||||||
didRequestMentionForMember:(MXRoomMember *)member
|
didRequestMentionForMember:(MXRoomMember *)member
|
||||||
textTrigger:(NSString *)textTrigger
|
textTrigger:(NSString *)textTrigger
|
||||||
{
|
{
|
||||||
|
@ -8069,16 +8069,16 @@ static CGSize kThreadListBarButtonItemImageSize;
|
||||||
[self mention:member];
|
[self mention:member];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)userSuggestionCoordinatorBridgeDidRequestMentionForRoom:(UserSuggestionCoordinatorBridge *)coordinator
|
- (void)completionSuggestionCoordinatorBridgeDidRequestMentionForRoom:(CompletionSuggestionCoordinatorBridge *)coordinator
|
||||||
textTrigger:(NSString *)textTrigger
|
textTrigger:(NSString *)textTrigger
|
||||||
{
|
{
|
||||||
[self removeTriggerTextFromComposer:textTrigger];
|
[self removeTriggerTextFromComposer:textTrigger];
|
||||||
[self.inputToolbarView pasteText:[UserSuggestionID.room stringByAppendingString:@" "]];
|
[self.inputToolbarView pasteText:[CompletionSuggestionUserID.room stringByAppendingString:@" "]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)userSuggestionCoordinatorBridge:(UserSuggestionCoordinatorBridge *)coordinator
|
- (void)completionSuggestionCoordinatorBridge:(CompletionSuggestionCoordinatorBridge *)coordinator
|
||||||
didRequestCommand:(NSString *)command
|
didRequestCommand:(NSString *)command
|
||||||
textTrigger:(NSString *)textTrigger
|
textTrigger:(NSString *)textTrigger
|
||||||
{
|
{
|
||||||
[self removeTriggerTextFromComposer:textTrigger];
|
[self removeTriggerTextFromComposer:textTrigger];
|
||||||
[self setCommand:command];
|
[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];
|
[self.view layoutIfNeeded];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
@ -13,6 +12,8 @@
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="bubblesTableView" destination="BGD-sd-SQR" id="OG4-Tw-Ovt"/>
|
<outlet property="bubblesTableView" destination="BGD-sd-SQR" id="OG4-Tw-Ovt"/>
|
||||||
<outlet property="bubblesTableViewBottomConstraint" destination="1SD-y2-oTg" id="n8D-hT-eqt"/>
|
<outlet property="bubblesTableViewBottomConstraint" destination="1SD-y2-oTg" id="n8D-hT-eqt"/>
|
||||||
|
<outlet property="completionSuggestionContainerHeightConstraint" destination="1Cd-cT-gOr" id="au5-3q-r54"/>
|
||||||
|
<outlet property="completionSuggestionContainerView" destination="oni-F4-X1U" id="0js-Ji-8Mm"/>
|
||||||
<outlet property="inputBackgroundView" destination="Xt7-83-dQh" id="xoG-eb-zFB"/>
|
<outlet property="inputBackgroundView" destination="Xt7-83-dQh" id="xoG-eb-zFB"/>
|
||||||
<outlet property="jumpToLastUnreadBanner" destination="S6r-bo-jxw" id="FSS-Be-E15"/>
|
<outlet property="jumpToLastUnreadBanner" destination="S6r-bo-jxw" id="FSS-Be-E15"/>
|
||||||
<outlet property="jumpToLastUnreadBannerContainer" destination="S6H-Az-RCM" id="YlI-fu-OpT"/>
|
<outlet property="jumpToLastUnreadBannerContainer" destination="S6H-Az-RCM" id="YlI-fu-OpT"/>
|
||||||
|
@ -32,8 +33,6 @@
|
||||||
<outlet property="scrollToBottomBadgeLabel" destination="QHs-rM-UU8" id="wk7-PQ-9Jm"/>
|
<outlet property="scrollToBottomBadgeLabel" destination="QHs-rM-UU8" id="wk7-PQ-9Jm"/>
|
||||||
<outlet property="scrollToBottomButton" destination="Ih9-EU-BOU" id="Wwg-gS-Sfp"/>
|
<outlet property="scrollToBottomButton" destination="Ih9-EU-BOU" id="Wwg-gS-Sfp"/>
|
||||||
<outlet property="topBannersStackView" destination="3z2-8P-wlg" id="uf5-gw-zWi"/>
|
<outlet property="topBannersStackView" destination="3z2-8P-wlg" id="uf5-gw-zWi"/>
|
||||||
<outlet property="userSuggestionContainerHeightConstraint" destination="1Cd-cT-gOr" id="au5-3q-r54"/>
|
|
||||||
<outlet property="userSuggestionContainerView" destination="oni-F4-X1U" id="0js-Ji-8Mm"/>
|
|
||||||
<outlet property="view" destination="iN0-l3-epB" id="ieV-u7-rXU"/>
|
<outlet property="view" destination="iN0-l3-epB" id="ieV-u7-rXU"/>
|
||||||
<outletCollection property="toolbarContainerConstraints" destination="T1Y-r9-bYV" id="wax-9P-KGn"/>
|
<outletCollection property="toolbarContainerConstraints" destination="T1Y-r9-bYV" id="wax-9P-KGn"/>
|
||||||
<outletCollection property="toolbarContainerConstraints" destination="pRw-S0-6WL" id="q4S-0g-sqQ"/>
|
<outletCollection property="toolbarContainerConstraints" destination="pRw-S0-6WL" id="q4S-0g-sqQ"/>
|
||||||
|
@ -48,20 +47,20 @@
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="3z2-8P-wlg" userLabel="Top Banners Stack View">
|
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="3z2-8P-wlg" userLabel="Top Banners Stack View">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="0.0"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="0.0"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" priority="250" id="Y9P-Ek-wjg"/>
|
<constraint firstAttribute="height" priority="250" id="Y9P-Ek-wjg"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
<tableView contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="BGD-sd-SQR">
|
<tableView contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="BGD-sd-SQR">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="626"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="606"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<userDefinedRuntimeAttributes>
|
<userDefinedRuntimeAttributes>
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="RoomVCBubblesTableView"/>
|
<userDefinedRuntimeAttribute type="string" keyPath="accessibilityIdentifier" value="RoomVCBubblesTableView"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableView>
|
</tableView>
|
||||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="54r-18-K1g">
|
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="54r-18-K1g">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="368"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="368"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<accessibility key="accessibilityConfiguration" identifier="RoomVCPreviewHeaderContainer"/>
|
<accessibility key="accessibilityConfiguration" identifier="RoomVCPreviewHeaderContainer"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
|
@ -69,7 +68,7 @@
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="fmF-ad-erE">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="fmF-ad-erE">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="0.0"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="0.0"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hB3-nR-MVR">
|
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hB3-nR-MVR">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="54"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="54"/>
|
||||||
|
@ -189,7 +188,7 @@
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gt1-EO-UVY">
|
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gt1-EO-UVY">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</view>
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
@ -237,11 +236,6 @@
|
||||||
<point key="canvasLocation" x="136.80000000000001" y="152.47376311844079"/>
|
<point key="canvasLocation" x="136.80000000000001" y="152.47376311844079"/>
|
||||||
</view>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
<designables>
|
|
||||||
<designable name="QHs-rM-UU8">
|
|
||||||
<size key="intrinsicContentSize" width="7.5" height="13.5"/>
|
|
||||||
</designable>
|
|
||||||
</designables>
|
|
||||||
<resources>
|
<resources>
|
||||||
<image name="new_close" width="16" height="16"/>
|
<image name="new_close" width="16" height="16"/>
|
||||||
<image name="room_scroll_up" width="24" height="24"/>
|
<image name="room_scroll_up" width="24" height="24"/>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
@class RoomInputToolbarView;
|
@class RoomInputToolbarView;
|
||||||
@class LinkActionWrapper;
|
@class LinkActionWrapper;
|
||||||
@class SuggestionPatternWrapper;
|
@class SuggestionPatternWrapper;
|
||||||
@class UserSuggestionViewModelContextWrapper;
|
@class CompletionSuggestionViewModelContextWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Destination of the message in the composer
|
Destination of the message in the composer
|
||||||
|
@ -84,7 +84,7 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode)
|
||||||
|
|
||||||
- (void)didDetectTextPattern: (SuggestionPatternWrapper *)suggestionPattern;
|
- (void)didDetectTextPattern: (SuggestionPatternWrapper *)suggestionPattern;
|
||||||
|
|
||||||
- (UserSuggestionViewModelContextWrapper *)userSuggestionContext;
|
- (CompletionSuggestionViewModelContextWrapper *)completionSuggestionContext;
|
||||||
|
|
||||||
- (MXMediaManager *)mediaManager;
|
- (MXMediaManager *)mediaManager;
|
||||||
|
|
||||||
|
|
|
@ -223,7 +223,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
|
||||||
let composer = Composer(
|
let composer = Composer(
|
||||||
viewModel: viewModel.context,
|
viewModel: viewModel.context,
|
||||||
wysiwygViewModel: wysiwygViewModel,
|
wysiwygViewModel: wysiwygViewModel,
|
||||||
userSuggestionSharedContext: toolbarViewDelegate.userSuggestionContext().context,
|
completionSuggestionSharedContext: toolbarViewDelegate.completionSuggestionContext().context,
|
||||||
resizeAnimationDuration: Double(kResizeComposerAnimationDuration),
|
resizeAnimationDuration: Double(kResizeComposerAnimationDuration),
|
||||||
sendMessageAction: { [weak self] content in
|
sendMessageAction: { [weak self] content in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
|
@ -51,7 +51,7 @@ enum MockAppScreens {
|
||||||
MockStaticLocationViewingScreenState.self,
|
MockStaticLocationViewingScreenState.self,
|
||||||
MockLocationSharingScreenState.self,
|
MockLocationSharingScreenState.self,
|
||||||
MockAnalyticsPromptScreenState.self,
|
MockAnalyticsPromptScreenState.self,
|
||||||
MockUserSuggestionScreenState.self,
|
MockCompletionSuggestionScreenState.self,
|
||||||
MockPollEditFormScreenState.self,
|
MockPollEditFormScreenState.self,
|
||||||
MockSpaceCreationEmailInvitesScreenState.self,
|
MockSpaceCreationEmailInvitesScreenState.self,
|
||||||
MockSpaceSettingsScreenState.self,
|
MockSpaceSettingsScreenState.self,
|
||||||
|
|
|
@ -16,15 +16,15 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum UserSuggestionViewAction {
|
enum CompletionSuggestionViewAction {
|
||||||
case selectedItem(UserSuggestionViewStateItem)
|
case selectedItem(CompletionSuggestionViewStateItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UserSuggestionViewModelResult {
|
enum CompletionSuggestionViewModelResult {
|
||||||
case selectedItemWithIdentifier(String)
|
case selectedItemWithIdentifier(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UserSuggestionViewStateItem: Identifiable {
|
enum CompletionSuggestionViewStateItem: Identifiable {
|
||||||
case command(name: String)
|
case command(name: String)
|
||||||
case user(id: String, avatar: AvatarInputProtocol?, displayName: String?)
|
case user(id: String, avatar: AvatarInputProtocol?, displayName: String?)
|
||||||
|
|
||||||
|
@ -38,6 +38,6 @@ enum UserSuggestionViewStateItem: Identifiable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserSuggestionViewState: BindableState {
|
struct CompletionSuggestionViewState: BindableState {
|
||||||
var items: [UserSuggestionViewStateItem]
|
var items: [CompletionSuggestionViewStateItem]
|
||||||
}
|
}
|
|
@ -17,32 +17,32 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum MockUserSuggestionScreenState: MockScreenState, CaseIterable {
|
enum MockCompletionSuggestionScreenState: MockScreenState, CaseIterable {
|
||||||
case multipleResults
|
case multipleResults
|
||||||
|
|
||||||
private static var members: [RoomMembersProviderMember]!
|
private static var members: [RoomMembersProviderMember]!
|
||||||
|
|
||||||
var screenType: Any.Type {
|
var screenType: Any.Type {
|
||||||
UserSuggestionList.self
|
CompletionSuggestionList.self
|
||||||
}
|
}
|
||||||
|
|
||||||
var screenView: ([Any], AnyView) {
|
var screenView: ([Any], AnyView) {
|
||||||
let service = UserSuggestionService(roomMemberProvider: self, commandProvider: self)
|
let service = CompletionSuggestionService(roomMemberProvider: self, commandProvider: self)
|
||||||
let listViewModel = UserSuggestionViewModel(userSuggestionService: service)
|
let listViewModel = CompletionSuggestionViewModel(completionSuggestionService: service)
|
||||||
|
|
||||||
let viewModel = UserSuggestionListWithInputViewModel(listViewModel: listViewModel) { textMessage in
|
let viewModel = CompletionSuggestionListWithInputViewModel(listViewModel: listViewModel) { textMessage in
|
||||||
service.processTextMessage(textMessage)
|
service.processTextMessage(textMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
[service, listViewModel],
|
[service, listViewModel],
|
||||||
AnyView(UserSuggestionListWithInput(viewModel: viewModel)
|
AnyView(CompletionSuggestionListWithInput(viewModel: viewModel)
|
||||||
.environmentObject(AvatarViewModel.withMockedServices()))
|
.environmentObject(AvatarViewModel.withMockedServices()))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MockUserSuggestionScreenState: RoomMembersProviderProtocol {
|
extension MockCompletionSuggestionScreenState: RoomMembersProviderProtocol {
|
||||||
var canMentionRoom: Bool { false }
|
var canMentionRoom: Bool { false }
|
||||||
|
|
||||||
func fetchMembers(_ members: ([RoomMembersProviderMember]) -> Void) {
|
func fetchMembers(_ members: ([RoomMembersProviderMember]) -> Void) {
|
||||||
|
@ -61,7 +61,7 @@ extension MockUserSuggestionScreenState: RoomMembersProviderProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MockUserSuggestionScreenState: CommandsProviderProtocol {
|
extension MockCompletionSuggestionScreenState: CommandsProviderProtocol {
|
||||||
func fetchCommands(_ commands: @escaping ([CommandsProviderCommand]) -> Void) {
|
func fetchCommands(_ commands: @escaping ([CommandsProviderCommand]) -> Void) {
|
||||||
commands([
|
commands([
|
||||||
CommandsProviderCommand(name: "/ban"),
|
CommandsProviderCommand(name: "/ban"),
|
|
@ -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<CompletionSuggestionViewState, CompletionSuggestionViewAction>
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,10 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol UserSuggestionViewModelProtocol {
|
protocol CompletionSuggestionViewModelProtocol {
|
||||||
/// Defines a shared context providing the ability to use a single `UserSuggestionViewModel` for multiple
|
/// Defines a shared context providing the ability to use a single `CompletionSuggestionViewModel` for multiple
|
||||||
/// `UserSuggestionList` e.g. the list component can then be displayed seemlessly in both `RoomViewController`
|
/// `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.
|
/// UIKit hosted context, and in Rich-Text-Editor's SwiftUI fullscreen mode, without need to reload the data.
|
||||||
var sharedContext: UserSuggestionViewModelType.Context { get }
|
var sharedContext: CompletionSuggestionViewModelType.Context { get }
|
||||||
var completion: ((UserSuggestionViewModelResult) -> Void)? { get set }
|
var completion: ((CompletionSuggestionViewModelResult) -> Void)? { get set }
|
||||||
}
|
}
|
|
@ -20,40 +20,40 @@ import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
import WysiwygComposer
|
import WysiwygComposer
|
||||||
|
|
||||||
protocol UserSuggestionCoordinatorDelegate: AnyObject {
|
protocol CompletionSuggestionCoordinatorDelegate: AnyObject {
|
||||||
func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?)
|
func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?)
|
||||||
func userSuggestionCoordinatorDidRequestMentionForRoom(_ coordinator: UserSuggestionCoordinator, textTrigger: String?)
|
func completionSuggestionCoordinatorDidRequestMentionForRoom(_ coordinator: CompletionSuggestionCoordinator, textTrigger: String?)
|
||||||
func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestCommand command: String, textTrigger: String?)
|
func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didRequestCommand command: String, textTrigger: String?)
|
||||||
func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didUpdateViewHeight height: CGFloat)
|
func completionSuggestionCoordinator(_ coordinator: CompletionSuggestionCoordinator, didUpdateViewHeight height: CGFloat)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserSuggestionCoordinatorParameters {
|
struct CompletionSuggestionCoordinatorParameters {
|
||||||
let mediaManager: MXMediaManager
|
let mediaManager: MXMediaManager
|
||||||
let room: MXRoom
|
let room: MXRoom
|
||||||
let userID: String
|
let userID: String
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper around `UserSuggestionViewModelType.Context` to pass it through obj-c.
|
/// Wrapper around `CompletionSuggestionViewModelType.Context` to pass it through obj-c.
|
||||||
final class UserSuggestionViewModelContextWrapper: NSObject {
|
final class CompletionSuggestionViewModelContextWrapper: NSObject {
|
||||||
let context: UserSuggestionViewModelType.Context
|
let context: CompletionSuggestionViewModelType.Context
|
||||||
|
|
||||||
init(context: UserSuggestionViewModelType.Context) {
|
init(context: CompletionSuggestionViewModelType.Context) {
|
||||||
self.context = context
|
self.context = context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class UserSuggestionCoordinator: Coordinator, Presentable {
|
final class CompletionSuggestionCoordinator: Coordinator, Presentable {
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private let parameters: UserSuggestionCoordinatorParameters
|
private let parameters: CompletionSuggestionCoordinatorParameters
|
||||||
|
|
||||||
private var userSuggestionHostingController: UIHostingController<AnyView>
|
private var completionSuggestionHostingController: UIHostingController<AnyView>
|
||||||
private var userSuggestionService: UserSuggestionServiceProtocol
|
private var completionSuggestionService: CompletionSuggestionServiceProtocol
|
||||||
private var userSuggestionViewModel: UserSuggestionViewModelProtocol
|
private var completionSuggestionViewModel: CompletionSuggestionViewModelProtocol
|
||||||
private var roomMemberProvider: UserSuggestionCoordinatorRoomMemberProvider
|
private var roomMemberProvider: CompletionSuggestionCoordinatorRoomMemberProvider
|
||||||
private var commandProvider: UserSuggestionCoordinatorCommandProvider
|
private var commandProvider: CompletionSuggestionCoordinatorCommandProvider
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@ -63,57 +63,57 @@ final class UserSuggestionCoordinator: Coordinator, Presentable {
|
||||||
var childCoordinators: [Coordinator] = []
|
var childCoordinators: [Coordinator] = []
|
||||||
var completion: (() -> Void)?
|
var completion: (() -> Void)?
|
||||||
|
|
||||||
weak var delegate: UserSuggestionCoordinatorDelegate?
|
weak var delegate: CompletionSuggestionCoordinatorDelegate?
|
||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
init(parameters: UserSuggestionCoordinatorParameters) {
|
init(parameters: CompletionSuggestionCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
roomMemberProvider = UserSuggestionCoordinatorRoomMemberProvider(room: parameters.room, userID: parameters.userID)
|
roomMemberProvider = CompletionSuggestionCoordinatorRoomMemberProvider(room: parameters.room, userID: parameters.userID)
|
||||||
commandProvider = UserSuggestionCoordinatorCommandProvider(room: parameters.room, userID: parameters.userID)
|
commandProvider = CompletionSuggestionCoordinatorCommandProvider(room: parameters.room, userID: parameters.userID)
|
||||||
userSuggestionService = UserSuggestionService(roomMemberProvider: roomMemberProvider, commandProvider: commandProvider)
|
completionSuggestionService = CompletionSuggestionService(roomMemberProvider: roomMemberProvider, commandProvider: commandProvider)
|
||||||
|
|
||||||
let viewModel = UserSuggestionViewModel(userSuggestionService: userSuggestionService)
|
let viewModel = CompletionSuggestionViewModel(completionSuggestionService: completionSuggestionService)
|
||||||
let view = UserSuggestionList(viewModel: viewModel.context)
|
let view = CompletionSuggestionList(viewModel: viewModel.context)
|
||||||
.environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager)))
|
.environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager)))
|
||||||
|
|
||||||
userSuggestionViewModel = viewModel
|
completionSuggestionViewModel = viewModel
|
||||||
userSuggestionHostingController = VectorHostingController(rootView: view)
|
completionSuggestionHostingController = VectorHostingController(rootView: view)
|
||||||
|
|
||||||
userSuggestionViewModel.completion = { [weak self] result in
|
completionSuggestionViewModel.completion = { [weak self] result in
|
||||||
guard let self = self else {
|
guard let self = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .selectedItemWithIdentifier(let identifier):
|
case .selectedItemWithIdentifier(let identifier):
|
||||||
if identifier == UserSuggestionID.room {
|
if identifier == CompletionSuggestionUserID.room {
|
||||||
self.delegate?.userSuggestionCoordinatorDidRequestMentionForRoom(self, textTrigger: self.userSuggestionService.currentTextTrigger)
|
self.delegate?.completionSuggestionCoordinatorDidRequestMentionForRoom(self, textTrigger: self.completionSuggestionService.currentTextTrigger)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let member = self.roomMemberProvider.roomMembers.filter({ $0.userId == identifier }).first {
|
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 {
|
} 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 }
|
guard let self = self else { return }
|
||||||
self.delegate?.userSuggestionCoordinator(self,
|
self.delegate?.completionSuggestionCoordinator(self,
|
||||||
didUpdateViewHeight: self.calculateViewHeight())
|
didUpdateViewHeight: self.calculateViewHeight())
|
||||||
}.store(in: &cancellables)
|
}.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processTextMessage(_ textMessage: String) {
|
func processTextMessage(_ textMessage: String) {
|
||||||
userSuggestionService.processTextMessage(textMessage)
|
completionSuggestionService.processTextMessage(textMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processSuggestionPattern(_ suggestionPattern: SuggestionPattern?) {
|
func processSuggestionPattern(_ suggestionPattern: SuggestionPattern?) {
|
||||||
userSuggestionService.processSuggestionPattern(suggestionPattern)
|
completionSuggestionService.processSuggestionPattern(suggestionPattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
@ -121,18 +121,18 @@ final class UserSuggestionCoordinator: Coordinator, Presentable {
|
||||||
func start() { }
|
func start() { }
|
||||||
|
|
||||||
func toPresentable() -> UIViewController {
|
func toPresentable() -> UIViewController {
|
||||||
userSuggestionHostingController
|
completionSuggestionHostingController
|
||||||
}
|
}
|
||||||
|
|
||||||
func sharedContext() -> UserSuggestionViewModelContextWrapper {
|
func sharedContext() -> CompletionSuggestionViewModelContextWrapper {
|
||||||
UserSuggestionViewModelContextWrapper(context: userSuggestionViewModel.sharedContext)
|
CompletionSuggestionViewModelContextWrapper(context: completionSuggestionViewModel.sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func calculateViewHeight() -> CGFloat {
|
private func calculateViewHeight() -> CGFloat {
|
||||||
let viewModel = UserSuggestionViewModel(userSuggestionService: userSuggestionService)
|
let viewModel = CompletionSuggestionViewModel(completionSuggestionService: completionSuggestionService)
|
||||||
let view = UserSuggestionList(viewModel: viewModel.context)
|
let view = CompletionSuggestionList(viewModel: viewModel.context)
|
||||||
.environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager)))
|
.environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager)))
|
||||||
|
|
||||||
let controller = VectorHostingController(rootView: view)
|
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 room: MXRoom
|
||||||
private let userID: String
|
private let userID: String
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ private class UserSuggestionCoordinatorRoomMemberProvider: RoomMembersProviderPr
|
||||||
self.roomMembers = joinedMembers
|
self.roomMembers = joinedMembers
|
||||||
members(self.roomMembersToProviderMembers(joinedMembers))
|
members(self.roomMembersToProviderMembers(joinedMembers))
|
||||||
} failure: { error in
|
} 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 room: MXRoom
|
||||||
private let userID: String
|
private let userID: String
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ struct CommandsProviderCommand {
|
||||||
var name: String
|
var name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserSuggestionID: NSObject {
|
class CompletionSuggestionUserID: NSObject {
|
||||||
/// A special case added for suggesting `@room` mentions.
|
/// A special case added for suggesting `@room` mentions.
|
||||||
@objc static let room = "@room"
|
@objc static let room = "@room"
|
||||||
}
|
}
|
||||||
|
@ -42,17 +42,17 @@ protocol CommandsProviderProtocol {
|
||||||
func fetchCommands(_ commands: @escaping ([CommandsProviderCommand]) -> Void)
|
func fetchCommands(_ commands: @escaping ([CommandsProviderCommand]) -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserSuggestionServiceItem: UserSuggestionItemProtocol {
|
struct CompletionSuggestionServiceUserItem: CompletionSuggestionUserItemProtocol {
|
||||||
let userId: String
|
let userId: String
|
||||||
let displayName: String?
|
let displayName: String?
|
||||||
let avatarUrl: String?
|
let avatarUrl: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CommandSuggestionServiceItem: CommandSuggestionItemProtocol {
|
struct CompletionSuggestionServiceCommandItem: CompletionSuggestionCommandItemProtocol {
|
||||||
let name: String
|
let name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserSuggestionService: UserSuggestionServiceProtocol {
|
class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
@ -60,13 +60,13 @@ class UserSuggestionService: UserSuggestionServiceProtocol {
|
||||||
private let roomMemberProvider: RoomMembersProviderProtocol
|
private let roomMemberProvider: RoomMembersProviderProtocol
|
||||||
private let commandProvider: CommandsProviderProtocol
|
private let commandProvider: CommandsProviderProtocol
|
||||||
|
|
||||||
private var suggestionItems: [SuggestionItem] = []
|
private var suggestionItems: [CompletionSuggestionItem] = []
|
||||||
private let currentTextTriggerSubject = CurrentValueSubject<String?, Never>(nil)
|
private let currentTextTriggerSubject = CurrentValueSubject<String?, Never>(nil)
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
var items = CurrentValueSubject<[SuggestionItem], Never>([])
|
var items = CurrentValueSubject<[CompletionSuggestionItem], Never>([])
|
||||||
|
|
||||||
var currentTextTrigger: String? {
|
var currentTextTrigger: String? {
|
||||||
currentTextTriggerSubject.value
|
currentTextTriggerSubject.value
|
||||||
|
@ -93,7 +93,7 @@ class UserSuggestionService: UserSuggestionServiceProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UserSuggestionServiceProtocol
|
// MARK: - CompletionSuggestionServiceProtocol
|
||||||
|
|
||||||
func processTextMessage(_ textMessage: String?) {
|
func processTextMessage(_ textMessage: String?) {
|
||||||
guard let textMessage = textMessage,
|
guard let textMessage = textMessage,
|
||||||
|
@ -145,14 +145,14 @@ class UserSuggestionService: UserSuggestionServiceProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.suggestionItems = members.withRoom(self.roomMemberProvider.canMentionRoom).map { member in
|
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
|
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 containedInUsername = completionSuggestionUserItem.userId.lowercased().contains(partialName.lowercased())
|
||||||
let containedInDisplayName = (userSuggestion.displayName ?? "").lowercased().contains(partialName.lowercased())
|
let containedInDisplayName = (completionSuggestionUserItem.displayName ?? "").lowercased().contains(partialName.lowercased())
|
||||||
|
|
||||||
return (containedInUsername || containedInDisplayName)
|
return (containedInUsername || containedInDisplayName)
|
||||||
})
|
})
|
||||||
|
@ -165,7 +165,7 @@ class UserSuggestionService: UserSuggestionServiceProtocol {
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
self.suggestionItems = commands.map { command in
|
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
|
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.
|
/// Returns the array with an additional member that represents an `@room` mention.
|
||||||
func withRoom(_ canMentionRoom: Bool) -> Self {
|
func withRoom(_ canMentionRoom: Bool) -> Self {
|
||||||
guard canMentionRoom else { return self }
|
guard canMentionRoom else { return self }
|
||||||
return self + [RoomMembersProviderMember(userId: UserSuggestionID.room, displayName: "Everyone", avatarUrl: "")]
|
return self + [RoomMembersProviderMember(userId: CompletionSuggestionUserID.room, displayName: "Everyone", avatarUrl: "")]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,23 +18,23 @@ import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import WysiwygComposer
|
import WysiwygComposer
|
||||||
|
|
||||||
protocol UserSuggestionItemProtocol: Avatarable {
|
protocol CompletionSuggestionUserItemProtocol: Avatarable {
|
||||||
var userId: String { get }
|
var userId: String { get }
|
||||||
var displayName: String? { get }
|
var displayName: String? { get }
|
||||||
var avatarUrl: String? { get }
|
var avatarUrl: String? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol CommandSuggestionItemProtocol {
|
protocol CompletionSuggestionCommandItemProtocol {
|
||||||
var name: String { get }
|
var name: String { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SuggestionItem {
|
enum CompletionSuggestionItem {
|
||||||
case command(value: CommandSuggestionItemProtocol)
|
case command(value: CompletionSuggestionCommandItemProtocol)
|
||||||
case user(value: UserSuggestionItemProtocol)
|
case user(value: CompletionSuggestionUserItemProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol UserSuggestionServiceProtocol {
|
protocol CompletionSuggestionServiceProtocol {
|
||||||
var items: CurrentValueSubject<[SuggestionItem], Never> { get }
|
var items: CurrentValueSubject<[CompletionSuggestionItem], Never> { get }
|
||||||
|
|
||||||
var currentTextTrigger: String? { get }
|
var currentTextTrigger: String? { get }
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ protocol UserSuggestionServiceProtocol {
|
||||||
|
|
||||||
// MARK: Avatarable
|
// MARK: Avatarable
|
||||||
|
|
||||||
extension UserSuggestionItemProtocol {
|
extension CompletionSuggestionUserItemProtocol {
|
||||||
var mxContentUri: String? {
|
var mxContentUri: String? {
|
||||||
avatarUrl
|
avatarUrl
|
||||||
}
|
}
|
|
@ -17,9 +17,9 @@
|
||||||
import RiotSwiftUI
|
import RiotSwiftUI
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class UserSuggestionUITests: MockScreenTestCase {
|
class CompletionSuggestionUITests: MockScreenTestCase {
|
||||||
func testUserSuggestionScreen() throws {
|
func testCompletionSuggestionScreen() throws {
|
||||||
app.goToScreenWithIdentifier(MockUserSuggestionScreenState.multipleResults.title)
|
app.goToScreenWithIdentifier(MockCompletionSuggestionScreenState.multipleResults.title)
|
||||||
|
|
||||||
let firstButton = app.buttons["displayNameText-userIdText"].firstMatch
|
let firstButton = app.buttons["displayNameText-userIdText"].firstMatch
|
||||||
XCTAssert(firstButton.waitForExistence(timeout: 10))
|
XCTAssert(firstButton.waitForExistence(timeout: 10))
|
|
@ -19,51 +19,53 @@ import XCTest
|
||||||
|
|
||||||
@testable import RiotSwiftUI
|
@testable import RiotSwiftUI
|
||||||
|
|
||||||
class UserSuggestionServiceTests: XCTestCase {
|
class CompletionSuggestionServiceTests: XCTestCase {
|
||||||
var service: UserSuggestionService!
|
var service: CompletionSuggestionService!
|
||||||
var canMentionRoom = false
|
var canMentionRoom = false
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
service = UserSuggestionService(roomMemberProvider: self, shouldDebounce: false)
|
service = CompletionSuggestionService(roomMemberProvider: self,
|
||||||
|
commandProvider: self,
|
||||||
|
shouldDebounce: false)
|
||||||
canMentionRoom = false
|
canMentionRoom = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAlice() {
|
func testAlice() {
|
||||||
service.processTextMessage("@Al")
|
service.processTextMessage("@Al")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Alice")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice")
|
||||||
|
|
||||||
service.processTextMessage("@al")
|
service.processTextMessage("@al")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Alice")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice")
|
||||||
|
|
||||||
service.processTextMessage("@ice")
|
service.processTextMessage("@ice")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Alice")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice")
|
||||||
|
|
||||||
service.processTextMessage("@Alice")
|
service.processTextMessage("@Alice")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Alice")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice")
|
||||||
|
|
||||||
service.processTextMessage("@alice:matrix.org")
|
service.processTextMessage("@alice:matrix.org")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Alice")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBob() {
|
func testBob() {
|
||||||
service.processTextMessage("@ob")
|
service.processTextMessage("@ob")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Bob")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Bob")
|
||||||
|
|
||||||
service.processTextMessage("@ob:")
|
service.processTextMessage("@ob:")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Bob")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Bob")
|
||||||
|
|
||||||
service.processTextMessage("@b:matrix")
|
service.processTextMessage("@b:matrix")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Bob")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Bob")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBoth() {
|
func testBoth() {
|
||||||
service.processTextMessage("@:matrix")
|
service.processTextMessage("@:matrix")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Alice")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice")
|
||||||
XCTAssertEqual(service.items.value.last?.displayName, "Bob")
|
XCTAssertEqual(service.items.value.last?.asUser?.displayName, "Bob")
|
||||||
|
|
||||||
service.processTextMessage("@.org")
|
service.processTextMessage("@.org")
|
||||||
XCTAssertEqual(service.items.value.first?.displayName, "Alice")
|
XCTAssertEqual(service.items.value.first?.asUser?.displayName, "Alice")
|
||||||
XCTAssertEqual(service.items.value.last?.displayName, "Bob")
|
XCTAssertEqual(service.items.value.last?.asUser?.displayName, "Bob")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEmptyResult() {
|
func testEmptyResult() {
|
||||||
|
@ -117,18 +119,18 @@ class UserSuggestionServiceTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomWithPower() {
|
func testRoomWithPower() {
|
||||||
// Given a user without the power to mention a room.
|
// Given a user with the power to mention a room.
|
||||||
canMentionRoom = true
|
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")
|
service.processTextMessage("@ro")
|
||||||
|
|
||||||
// Then the completion for a room mention should be shown.
|
// 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) {
|
func fetchMembers(_ members: @escaping ([RoomMembersProviderMember]) -> Void) {
|
||||||
let users = [("Alice", "@alice:matrix.org"),
|
let users = [("Alice", "@alice:matrix.org"),
|
||||||
("Bob", "@bob: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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct UserSuggestionList: View {
|
struct CompletionSuggestionList: View {
|
||||||
private enum Constants {
|
private enum Constants {
|
||||||
static let topPadding: CGFloat = 8.0
|
static let topPadding: CGFloat = 8.0
|
||||||
static let listItemPadding: CGFloat = 4.0
|
static let listItemPadding: CGFloat = 4.0
|
||||||
|
@ -43,7 +43,7 @@ struct UserSuggestionList: View {
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
@ObservedObject var viewModel: UserSuggestionViewModel.Context
|
@ObservedObject var viewModel: CompletionSuggestionViewModel.Context
|
||||||
var showBackgroundShadow: Bool = true
|
var showBackgroundShadow: Bool = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -51,7 +51,7 @@ struct UserSuggestionList: View {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
} else {
|
} else {
|
||||||
ZStack {
|
ZStack {
|
||||||
UserSuggestionListItem(content: UserSuggestionViewStateItem.user(
|
CompletionSuggestionListItem(content: CompletionSuggestionViewStateItem.user(
|
||||||
id: "Prototype",
|
id: "Prototype",
|
||||||
avatar: AvatarInput(mxContentUri: "",
|
avatar: AvatarInput(mxContentUri: "",
|
||||||
matrixItemId: "",
|
matrixItemId: "",
|
||||||
|
@ -79,7 +79,7 @@ struct UserSuggestionList: View {
|
||||||
Button {
|
Button {
|
||||||
viewModel.send(viewAction: .selectedItem(item))
|
viewModel.send(viewAction: .selectedItem(item))
|
||||||
} label: {
|
} label: {
|
||||||
UserSuggestionListItem(content: item)
|
CompletionSuggestionListItem(content: item)
|
||||||
.modifier(ListItemPaddingModifier(isFirst: viewModel.viewState.items.first?.id == item.id))
|
.modifier(ListItemPaddingModifier(isFirst: viewModel.viewState.items.first?.id == item.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,8 +134,8 @@ private struct BackgroundView<Content: View>: View {
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct UserSuggestion_Previews: PreviewProvider {
|
struct CompletionSuggestion_Previews: PreviewProvider {
|
||||||
static let stateRenderer = MockUserSuggestionScreenState.stateRenderer
|
static let stateRenderer = MockCompletionSuggestionScreenState.stateRenderer
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
stateRenderer.screenGroup()
|
stateRenderer.screenGroup()
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct UserSuggestionListItem: View {
|
struct CompletionSuggestionListItem: View {
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
@ -25,7 +25,7 @@ struct UserSuggestionListItem: View {
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
let content: UserSuggestionViewStateItem
|
let content: CompletionSuggestionViewStateItem
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
@ -59,9 +59,9 @@ struct UserSuggestionListItem: View {
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct UserSuggestionHeader_Previews: PreviewProvider {
|
struct CompletionSuggestionHeader_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
UserSuggestionListItem(content: UserSuggestionViewStateItem.user(
|
CompletionSuggestionListItem(content: CompletionSuggestionViewStateItem.user(
|
||||||
id: "@alice:matrix.org",
|
id: "@alice:matrix.org",
|
||||||
avatar: MockAvatarInput.example,
|
avatar: MockAvatarInput.example,
|
||||||
displayName: "Alice"
|
displayName: "Alice"
|
|
@ -16,24 +16,24 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct UserSuggestionListWithInputViewModel {
|
struct CompletionSuggestionListWithInputViewModel {
|
||||||
let listViewModel: UserSuggestionViewModel
|
let listViewModel: CompletionSuggestionViewModel
|
||||||
let callback: (String) -> Void
|
let callback: (String) -> Void
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserSuggestionListWithInput: View {
|
struct CompletionSuggestionListWithInput: View {
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
// MARK: Public
|
// MARK: Public
|
||||||
|
|
||||||
var viewModel: UserSuggestionListWithInputViewModel
|
var viewModel: CompletionSuggestionListWithInputViewModel
|
||||||
@State private var inputText = ""
|
@State private var inputText = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0.0) {
|
VStack(spacing: 0.0) {
|
||||||
UserSuggestionList(viewModel: viewModel.listViewModel.context)
|
CompletionSuggestionList(viewModel: viewModel.listViewModel.context)
|
||||||
TextField("Search for user", text: $inputText)
|
TextField("Search for user", text: $inputText)
|
||||||
.background(Color.white)
|
.background(Color.white)
|
||||||
.onChange(of: inputText, perform: viewModel.callback)
|
.onChange(of: inputText, perform: viewModel.callback)
|
||||||
|
@ -48,8 +48,8 @@ struct UserSuggestionListWithInput: View {
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct UserSuggestionListWithInput_Previews: PreviewProvider {
|
struct CompletionSuggestionListWithInput_Previews: PreviewProvider {
|
||||||
static let stateRenderer = MockUserSuggestionScreenState.stateRenderer
|
static let stateRenderer = MockCompletionSuggestionScreenState.stateRenderer
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
stateRenderer.screenGroup()
|
stateRenderer.screenGroup()
|
||||||
}
|
}
|
|
@ -29,7 +29,7 @@ enum MockComposerScreenState: MockScreenState, CaseIterable {
|
||||||
|
|
||||||
var screenView: ([Any], AnyView) {
|
var screenView: ([Any], AnyView) {
|
||||||
let viewModel: ComposerViewModel
|
let viewModel: ComposerViewModel
|
||||||
let userSuggestionViewModel = MockUserSuggestionViewModel(initialViewState: UserSuggestionViewState(items: []))
|
let completionSuggestionViewModel = MockCompletionSuggestionViewModel(initialViewState: CompletionSuggestionViewState(items: []))
|
||||||
let bindings = ComposerBindings(focused: false)
|
let bindings = ComposerBindings(focused: false)
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -67,7 +67,7 @@ enum MockComposerScreenState: MockScreenState, CaseIterable {
|
||||||
Spacer()
|
Spacer()
|
||||||
Composer(viewModel: viewModel.context,
|
Composer(viewModel: viewModel.context,
|
||||||
wysiwygViewModel: wysiwygviewModel,
|
wysiwygViewModel: wysiwygviewModel,
|
||||||
userSuggestionSharedContext: userSuggestionViewModel.context,
|
completionSuggestionSharedContext: completionSuggestionViewModel.context,
|
||||||
resizeAnimationDuration: 0.1,
|
resizeAnimationDuration: 0.1,
|
||||||
sendMessageAction: { _ in },
|
sendMessageAction: { _ in },
|
||||||
showSendMediaActions: { })
|
showSendMediaActions: { })
|
||||||
|
@ -82,6 +82,4 @@ enum MockComposerScreenState: MockScreenState, CaseIterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class MockUserSuggestionViewModel: UserSuggestionViewModelType {
|
private final class MockCompletionSuggestionViewModel: CompletionSuggestionViewModelType { }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -257,11 +257,11 @@ final class SuggestionPatternWrapper: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class UserSuggestionViewModelWrapper: NSObject {
|
final class CompletionSuggestionViewModelWrapper: NSObject {
|
||||||
let userSuggestionViewModel: UserSuggestionViewModel
|
let completionSuggestionViewModel: CompletionSuggestionViewModel
|
||||||
|
|
||||||
init(_ userSuggestionViewModel: UserSuggestionViewModel) {
|
init(_ completionSuggestionViewModel: CompletionSuggestionViewModel) {
|
||||||
self.userSuggestionViewModel = userSuggestionViewModel
|
self.completionSuggestionViewModel = completionSuggestionViewModel
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ struct Composer: View {
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
@ObservedObject private var viewModel: ComposerViewModelType.Context
|
@ObservedObject private var viewModel: ComposerViewModelType.Context
|
||||||
@ObservedObject private var wysiwygViewModel: WysiwygComposerViewModel
|
@ObservedObject private var wysiwygViewModel: WysiwygComposerViewModel
|
||||||
private let userSuggestionSharedContext: UserSuggestionViewModelType.Context
|
private let completionSuggestionSharedContext: CompletionSuggestionViewModelType.Context
|
||||||
private let resizeAnimationDuration: Double
|
private let resizeAnimationDuration: Double
|
||||||
|
|
||||||
private let sendMessageAction: (WysiwygComposerContent) -> Void
|
private let sendMessageAction: (WysiwygComposerContent) -> Void
|
||||||
|
@ -223,13 +223,13 @@ struct Composer: View {
|
||||||
init(
|
init(
|
||||||
viewModel: ComposerViewModelType.Context,
|
viewModel: ComposerViewModelType.Context,
|
||||||
wysiwygViewModel: WysiwygComposerViewModel,
|
wysiwygViewModel: WysiwygComposerViewModel,
|
||||||
userSuggestionSharedContext: UserSuggestionViewModelType.Context,
|
completionSuggestionSharedContext: CompletionSuggestionViewModelType.Context,
|
||||||
resizeAnimationDuration: Double,
|
resizeAnimationDuration: Double,
|
||||||
sendMessageAction: @escaping (WysiwygComposerContent) -> Void,
|
sendMessageAction: @escaping (WysiwygComposerContent) -> Void,
|
||||||
showSendMediaActions: @escaping () -> Void) {
|
showSendMediaActions: @escaping () -> Void) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
self.wysiwygViewModel = wysiwygViewModel
|
self.wysiwygViewModel = wysiwygViewModel
|
||||||
self.userSuggestionSharedContext = userSuggestionSharedContext
|
self.completionSuggestionSharedContext = completionSuggestionSharedContext
|
||||||
self.resizeAnimationDuration = resizeAnimationDuration
|
self.resizeAnimationDuration = resizeAnimationDuration
|
||||||
self.sendMessageAction = sendMessageAction
|
self.sendMessageAction = sendMessageAction
|
||||||
self.showSendMediaActions = showSendMediaActions
|
self.showSendMediaActions = showSendMediaActions
|
||||||
|
@ -256,7 +256,7 @@ struct Composer: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if wysiwygViewModel.maximised {
|
if wysiwygViewModel.maximised {
|
||||||
UserSuggestionList(viewModel: userSuggestionSharedContext, showBackgroundShadow: false)
|
CompletionSuggestionList(viewModel: completionSuggestionSharedContext, showBackgroundShadow: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: composerHeight)
|
.frame(height: composerHeight)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<UserSuggestionViewState, UserSuggestionViewAction>
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue