From b8c321b5bb9073af50522c237caf783a53021043 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Sun, 17 Jul 2022 12:44:31 +0100 Subject: [PATCH 01/80] Outgoing SAS User Verification Flow --- .../Scanning/KeyVerificationVerifyByScanningViewModel.swift | 2 +- .../User/Start/UserVerificationStartViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift index de4b9e189..d0d900290 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -173,7 +173,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca // Remove pending QR code transaction, as we are going to use SAS verification self.removePendingQRCodeTransaction() - if keyVerificationTransaction is MXOutgoingSASTransaction == false { + if keyVerificationTransaction is MXSASTransaction == false || keyVerificationTransaction.isIncoming { MXLog.debug("[KeyVerificationVerifyByScanningViewModel] SAS transaction should be outgoing") self.unregisterTransactionDidStateChangeNotification() self.update(viewState: .error(KeyVerificationVerifyByScanningViewModelError.unknown)) diff --git a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModel.swift b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModel.swift index c49576c64..77a18de7c 100644 --- a/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModel.swift +++ b/Riot/Modules/KeyVerification/User/Start/UserVerificationStartViewModel.swift @@ -122,7 +122,7 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { } @objc private func keyVerificationRequestDidChange(notification: Notification) { - guard let keyVerificationRequest = notification.object as? MXKeyVerificationByDMRequest else { + guard let keyVerificationRequest = notification.object as? MXKeyVerificationRequest else { return } From 9396271db4c29259303dcf2dea1777dbd1fda6d0 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 9 Aug 2022 14:20:37 +0100 Subject: [PATCH 02/80] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 11f0ce3d1..cd95bca59 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.8.25 -CURRENT_PROJECT_VERSION = 1.8.25 +MARKETING_VERSION = 1.8.26 +CURRENT_PROJECT_VERSION = 1.8.26 From 7dac5b907c34fb46eecfe69c4517642790d83dc9 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 5 Aug 2022 12:26:48 +0100 Subject: [PATCH 03/80] Remove all types related to Groups --- Riot/Categories/MXGroup+Riot.h | 36 - Riot/Categories/MXGroup+Riot.m | 50 - .../DataSources/GroupsDataSource.h | 24 - .../DataSources/GroupsDataSource.m | 126 -- .../Communities/GroupsViewController.h | 55 - .../Communities/GroupsViewController.m | 646 -------- .../Home/GroupHomeViewController.h | 70 - .../Home/GroupHomeViewController.m | 903 ----------- .../Home/GroupHomeViewController.xib | 285 ---- .../Members/GroupParticipantsViewController.h | 83 -- .../Members/GroupParticipantsViewController.m | 1324 ----------------- .../GroupParticipantsViewController.xib | 87 -- .../Rooms/GroupRoomsViewController.h | 70 - .../Rooms/GroupRoomsViewController.m | 698 --------- .../Rooms/GroupRoomsViewController.xib | 87 -- .../Rooms/Views/GroupRoomTableViewCell.h | 32 - .../Rooms/Views/GroupRoomTableViewCell.m | 96 -- .../Rooms/Views/GroupRoomTableViewCell.xib | 68 - .../TabDetail/GroupDetailsCoordinator.swift | 59 - .../GroupDetailsCoordinatorParameters.swift | 29 - .../GroupDetailsCoordinatorProtocol.swift | 28 - .../TabDetail/GroupDetailsViewController.h | 47 - .../TabDetail/GroupDetailsViewController.m | 181 --- .../TabDetail/GroupDetailsViewController.xib | 52 - .../Views/GroupInviteTableViewCell.h | 49 - .../Views/GroupInviteTableViewCell.m | 95 -- .../Views/GroupInviteTableViewCell.xib | 128 -- .../Communities/Views/GroupTableViewCell.h | 33 - .../Communities/Views/GroupTableViewCell.m | 91 -- .../Communities/Views/GroupTableViewCell.xib | 103 -- .../Controllers/MXKGroupListViewController.h | 118 -- .../Controllers/MXKGroupListViewController.m | 614 -------- .../MXKGroupListViewController.xib | 53 - .../MatrixKit/Models/Group/MXKGroupCellData.h | 24 - .../MatrixKit/Models/Group/MXKGroupCellData.m | 49 - .../Models/Group/MXKGroupCellDataStoring.h | 53 - .../Models/Group/MXKSessionGroupsDataSource.h | 94 -- .../Models/Group/MXKSessionGroupsDataSource.m | 611 -------- .../Views/Group/MXKGroupTableViewCell.h | 39 - .../Views/Group/MXKGroupTableViewCell.m | 92 -- .../Views/Group/MXKGroupTableViewCell.xib | 62 - .../Views/GroupTableViewCellWithSwitch.h | 26 - .../Views/GroupTableViewCellWithSwitch.m | 21 - .../Views/GroupTableViewCellWithSwitch.xib | 72 - 44 files changed, 7563 deletions(-) delete mode 100644 Riot/Categories/MXGroup+Riot.h delete mode 100644 Riot/Categories/MXGroup+Riot.m delete mode 100644 Riot/Modules/Communities/DataSources/GroupsDataSource.h delete mode 100644 Riot/Modules/Communities/DataSources/GroupsDataSource.m delete mode 100644 Riot/Modules/Communities/GroupsViewController.h delete mode 100644 Riot/Modules/Communities/GroupsViewController.m delete mode 100644 Riot/Modules/Communities/Home/GroupHomeViewController.h delete mode 100644 Riot/Modules/Communities/Home/GroupHomeViewController.m delete mode 100644 Riot/Modules/Communities/Home/GroupHomeViewController.xib delete mode 100644 Riot/Modules/Communities/Members/GroupParticipantsViewController.h delete mode 100644 Riot/Modules/Communities/Members/GroupParticipantsViewController.m delete mode 100644 Riot/Modules/Communities/Members/GroupParticipantsViewController.xib delete mode 100644 Riot/Modules/Communities/Rooms/GroupRoomsViewController.h delete mode 100644 Riot/Modules/Communities/Rooms/GroupRoomsViewController.m delete mode 100644 Riot/Modules/Communities/Rooms/GroupRoomsViewController.xib delete mode 100644 Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h delete mode 100644 Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.m delete mode 100644 Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.xib delete mode 100644 Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift delete mode 100644 Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorParameters.swift delete mode 100644 Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorProtocol.swift delete mode 100644 Riot/Modules/Communities/TabDetail/GroupDetailsViewController.h delete mode 100644 Riot/Modules/Communities/TabDetail/GroupDetailsViewController.m delete mode 100644 Riot/Modules/Communities/TabDetail/GroupDetailsViewController.xib delete mode 100644 Riot/Modules/Communities/Views/GroupInviteTableViewCell.h delete mode 100644 Riot/Modules/Communities/Views/GroupInviteTableViewCell.m delete mode 100644 Riot/Modules/Communities/Views/GroupInviteTableViewCell.xib delete mode 100644 Riot/Modules/Communities/Views/GroupTableViewCell.h delete mode 100644 Riot/Modules/Communities/Views/GroupTableViewCell.m delete mode 100644 Riot/Modules/Communities/Views/GroupTableViewCell.xib delete mode 100644 Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.h delete mode 100644 Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.m delete mode 100644 Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.xib delete mode 100644 Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.h delete mode 100644 Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.m delete mode 100644 Riot/Modules/MatrixKit/Models/Group/MXKGroupCellDataStoring.h delete mode 100644 Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.h delete mode 100644 Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.m delete mode 100644 Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.h delete mode 100644 Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.m delete mode 100644 Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.xib delete mode 100644 Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.h delete mode 100644 Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.m delete mode 100644 Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.xib diff --git a/Riot/Categories/MXGroup+Riot.h b/Riot/Categories/MXGroup+Riot.h deleted file mode 100644 index 76b1e76e5..000000000 --- a/Riot/Categories/MXGroup+Riot.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 -#import "MatrixKit.h" - -/** - Define a `MXGroup` category at Riot level. - */ -@interface MXGroup (Riot) - -/** - Set the group avatar in the dedicated MXKImageView. - The riot style implies to use in order : - 1 - the default avatar if there is one - 2 - the first letter of the group name. - - @param mxkImageView the destinated MXKImageView. - @param mxSession the matrix session - */ -- (void)setGroupAvatarImageIn:(MXKImageView*)mxkImageView matrixSession:(MXSession*)mxSession; - -@end diff --git a/Riot/Categories/MXGroup+Riot.m b/Riot/Categories/MXGroup+Riot.m deleted file mode 100644 index a68710c06..000000000 --- a/Riot/Categories/MXGroup+Riot.m +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2017 Vector Creations Ltd - Copyright 2018 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - 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 "MXGroup+Riot.h" - -#import "AvatarGenerator.h" - -@implementation MXGroup (Riot) - -- (void)setGroupAvatarImageIn:(MXKImageView*)mxkImageView matrixSession:(MXSession*)mxSession -{ - // Use the group display name to prepare the default avatar image. - NSString *avatarDisplayName = self.profile.name; - UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:self.groupId withDisplayName:avatarDisplayName]; - - if (self.profile.avatarUrl && mxSession) - { - mxkImageView.enableInMemoryCache = YES; - - [mxkImageView setImageURI:self.profile.avatarUrl - withType:nil - andImageOrientation:UIImageOrientationUp - toFitViewSize:mxkImageView.frame.size - withMethod:MXThumbnailingMethodCrop - previewImage:avatarImage - mediaManager:mxSession.mediaManager]; - } - else - { - mxkImageView.image = avatarImage; - } - - mxkImageView.contentMode = UIViewContentModeScaleAspectFill; -} - -@end diff --git a/Riot/Modules/Communities/DataSources/GroupsDataSource.h b/Riot/Modules/Communities/DataSources/GroupsDataSource.h deleted file mode 100644 index 836f6fdaa..000000000 --- a/Riot/Modules/Communities/DataSources/GroupsDataSource.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MatrixKit.h" - -/** - 'GroupsDataSource' class inherits from 'MXKSessionGroupsDataSource' to define the Riot groups source. - */ -@interface GroupsDataSource : MXKSessionGroupsDataSource - -@end diff --git a/Riot/Modules/Communities/DataSources/GroupsDataSource.m b/Riot/Modules/Communities/DataSources/GroupsDataSource.m deleted file mode 100644 index 69b0100f5..000000000 --- a/Riot/Modules/Communities/DataSources/GroupsDataSource.m +++ /dev/null @@ -1,126 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupsDataSource.h" -#import "GeneratedInterface-Swift.h" - -@interface GroupsDataSource() - -@property (nonatomic) NSInteger betaAnnounceSection; -@property (nonatomic) BOOL showBetaAnnounce; - -@end - -@implementation GroupsDataSource - -- (instancetype)initWithMatrixSession:(MXSession *)matrixSession -{ - self = [super initWithMatrixSession:matrixSession]; - if (self) - { - // TODO: Hide the banner for the moment. Wait for iterations on it. -// _showBetaAnnounce = !RiotSettings.shared.hideSpaceBetaAnnounce; - _showBetaAnnounce = NO; - } - return self; -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - NSInteger count = 0; - self.betaAnnounceSection = self.groupInvitesSection = self.joinedGroupsSection = -1; - - // Check whether all data sources are ready before rendering groups. - if (self.state == MXKDataSourceStateReady) - { - if (self.showBetaAnnounce) - { - self.betaAnnounceSection = count++; - } - if (groupsInviteCellDataArray.count) - { - self.groupInvitesSection = count++; - } - if (groupsCellDataArray.count) - { - self.joinedGroupsSection = count++; - } - } - - return count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == self.betaAnnounceSection) - { - BetaAnnounceCell *cell = [tableView dequeueReusableCellWithIdentifier:BetaAnnounceCell.reuseIdentifier forIndexPath:indexPath]; - [cell vc_hideSeparator]; - [cell updateWithTheme:ThemeService.shared.theme]; - cell.delegate = self; - return cell; - - } - - return [super tableView:tableView cellForRowAtIndexPath:indexPath]; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - if (section == self.betaAnnounceSection) - { - return 1; - } - - return [super tableView:tableView numberOfRowsInSection:section]; -} - -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - NSString* sectionTitle = nil; - - // Check whether there are more than 1 section. - if (self.groupInvitesSection != -1) - { - if (section == self.groupInvitesSection) - { - sectionTitle = [VectorL10n groupInviteSection]; - } - else if (section == self.joinedGroupsSection) - { - sectionTitle = [VectorL10n groupSection]; - } - } - - return sectionTitle; -} - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath -{ - // Enable edition only for the joined groups. - return (indexPath.section == self.joinedGroupsSection); -} - -#pragma mark - BetaAnnounceCellDelegate - -- (void)betaAnnounceCellDidTapCloseButton:(BetaAnnounceCell *)cell -{ - self.showBetaAnnounce = NO; - RiotSettings.shared.hideSpaceBetaAnnounce = YES; - [self.delegate dataSource:self didCellChange:nil]; -} - -@end diff --git a/Riot/Modules/Communities/GroupsViewController.h b/Riot/Modules/Communities/GroupsViewController.h deleted file mode 100644 index ef2a4c486..000000000 --- a/Riot/Modules/Communities/GroupsViewController.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MatrixKit.h" - -/** - The `GroupsViewController` screen is the view controller displayed when `Groups` tab is selected. - */ -@interface GroupsViewController : MXKGroupListViewController -{ -@protected - /** - The group identifier related to the cell which is in editing mode (if any). - */ - NSString *editedGroupId; - - /** - Current alert (if any). - */ - UIAlertController *currentAlert; - - /** - The image view of the (+) button. - */ - UIImageView* plusButtonImageView; -} - -/** - If YES, the table view will scroll at the top on the next data source refresh. - It comes back to NO after each refresh. - */ -@property (nonatomic) BOOL shouldScrollToTopOnRefresh; - -/** - Tell whether the search bar at the top of the groups table is enabled. YES by default. - */ -@property (nonatomic) BOOL enableSearchBar; - - -+ (instancetype)instantiate; - -@end diff --git a/Riot/Modules/Communities/GroupsViewController.m b/Riot/Modules/Communities/GroupsViewController.m deleted file mode 100644 index 3f954e814..000000000 --- a/Riot/Modules/Communities/GroupsViewController.m +++ /dev/null @@ -1,646 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupsViewController.h" - -#import "GroupTableViewCell.h" -#import "GroupInviteTableViewCell.h" - -#import "GeneratedInterface-Swift.h" - -@interface GroupsViewController () -{ - // Tell whether a groups refresh is pending (suspended during editing mode). - BOOL isRefreshPending; - - // Observe UIApplicationDidEnterBackgroundNotification to cancel editing mode when app leaves the foreground state. - __weak id UIApplicationDidEnterBackgroundNotificationObserver; - - // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - __weak id kAppDelegateDidTapStatusBarNotificationObserver; - - MXHTTPOperation *currentRequest; - - // The fake search bar displayed at the top of the recents table. We switch on the actual search bar (self.groupsSearchBar) - // when the user selects it. - UISearchBar *tableSearchBar; - - // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - __weak id kThemeServiceDidChangeThemeNotificationObserver; -} - -@property (nonatomic) AnalyticsScreenTracker *screenTracker; - -@end - -@implementation GroupsViewController - -+ (instancetype)instantiate -{ - UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; - GroupsViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"GroupsViewController"]; - return viewController; -} - -- (void)finalizeInit -{ - [super finalizeInit]; - - // Setup `MXKViewControllerHandling` properties - self.enableBarTintColorStatusChange = NO; - self.rageShakeManager = [RageShakeManager sharedManager]; - - // Enable the search bar in the recents table, and remove the search option from the navigation bar. - _enableSearchBar = YES; - self.enableBarButtonSearch = NO; - - // Create the fake search bar - tableSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 600, 44)]; - tableSearchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; - tableSearchBar.showsCancelButton = NO; - tableSearchBar.placeholder = [VectorL10n searchDefaultPlaceholder]; - tableSearchBar.delegate = self; - - // Set itself as delegate by default. - self.delegate = self; - - self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenMyGroups]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - self.view.accessibilityIdentifier = @"GroupsVCView"; - self.groupsTableView.accessibilityIdentifier = @"GroupsVCTableView"; - - //Register here the customized cell view class used to render groups - [self.groupsTableView registerNib:GroupTableViewCell.nib forCellReuseIdentifier:GroupTableViewCell.defaultReuseIdentifier]; - [self.groupsTableView registerNib:GroupInviteTableViewCell.nib forCellReuseIdentifier:GroupInviteTableViewCell.defaultReuseIdentifier]; - [self.groupsTableView registerNib:BetaAnnounceCell.nib forCellReuseIdentifier:BetaAnnounceCell.reuseIdentifier]; - - // Hide line separators of empty cells - self.groupsTableView.tableFooterView = [[UIView alloc] init]; - - // Enable self-sizing cells and section headers. - self.groupsTableView.rowHeight = UITableViewAutomaticDimension; - self.groupsTableView.estimatedRowHeight = 74; - self.groupsTableView.sectionHeaderHeight = UITableViewAutomaticDimension; - self.groupsTableView.estimatedSectionHeaderHeight = 30; - self.groupsTableView.estimatedSectionFooterHeight = 0; - - MXWeakify(self); - - // Observe UIApplicationDidEnterBackgroundNotification to refresh bubbles when app leaves the foreground state. - UIApplicationDidEnterBackgroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - MXStrongifyAndReturnIfNil(self); - - // Leave potential editing mode - [self cancelEditionMode:self->isRefreshPending]; - - }]; - - self.groupsSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone; - self.groupsSearchBar.placeholder = [VectorL10n searchDefaultPlaceholder]; - - // @TODO: Add programmatically the (+) button. -// plusButtonImageView = [self vc_addFABWithImage:[UIImage imageNamed:@"plus_floating_action"] -// target:self -// action:@selector(onPlusButtonPressed)]; - - // Observe user interface theme change. - kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - MXStrongifyAndReturnIfNil(self); - - [self userInterfaceThemeDidChange]; - - }]; - [self userInterfaceThemeDidChange]; -} - -- (void)userInterfaceThemeDidChange -{ - [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; - - self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; - - // Use the primary bg color for the recents table view in plain style. - self.groupsTableView.backgroundColor = ThemeService.shared.theme.backgroundColor; - topview.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; - - [ThemeService.shared.theme applyStyleOnSearchBar:tableSearchBar]; - [ThemeService.shared.theme applyStyleOnSearchBar:self.groupsSearchBar]; - - if (self.groupsTableView.dataSource) - { - // Force table refresh - [self cancelEditionMode:YES]; - } - - [self setNeedsStatusBarAppearanceUpdate]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle -{ - return ThemeService.shared.theme.statusBarStyle; -} - -- (void)destroy -{ - [super destroy]; - - if (currentRequest) - { - [currentRequest cancel]; - currentRequest = nil; - } - - if (currentAlert) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = nil; - } - - if (UIApplicationDidEnterBackgroundNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:UIApplicationDidEnterBackgroundNotificationObserver]; - UIApplicationDidEnterBackgroundNotificationObserver = nil; - } - - if (kThemeServiceDidChangeThemeNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; - kThemeServiceDidChangeThemeNotificationObserver = nil; - } -} - -- (void)setEditing:(BOOL)editing animated:(BOOL)animated -{ - [super setEditing:editing animated:animated]; - - self.groupsTableView.editing = editing; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - [self.screenTracker trackScreen]; - - // Deselect the current selected row, it will be restored on viewDidAppear (if any) - NSIndexPath *indexPath = [self.groupsTableView indexPathForSelectedRow]; - if (indexPath) - { - [self.groupsTableView deselectRowAtIndexPath:indexPath animated:NO]; - } - - MXWeakify(self); - - // Observe kAppDelegateDidTapStatusBarNotificationObserver. - kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - MXStrongifyAndReturnIfNil(self); - - [self scrollToTop:YES]; - - }]; - [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - // Leave potential editing mode - [self cancelEditionMode:NO]; - - if (kAppDelegateDidTapStatusBarNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; - kAppDelegateDidTapStatusBarNotificationObserver = nil; - } -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - // Release the current selected item (if any) except if the second view controller is still visible. - if (self.splitViewController.isCollapsed) - { - // Release the current selected group (if any). - [[AppDelegate theDelegate].masterTabBarController releaseSelectedItem]; - } - else - { - // In case of split view controller where the primary and secondary view controllers are displayed side-by-side onscreen, - // the selected group (if any) is highlighted. - [self refreshCurrentSelectedCell:YES]; - } -} - -#pragma mark - Override MXKGroupListViewController - -- (void)refreshGroupsTable -{ - // Refresh the tabBar icon badges - [[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges]; - - isRefreshPending = NO; - - if (editedGroupId) - { - // Check whether the user didn't leave the room - if ([self.dataSource cellIndexPathWithGroupId:editedGroupId]) - { - isRefreshPending = YES; - return; - } - else - { - // Cancel the editing mode, a new refresh will be triggered. - [self cancelEditionMode:YES]; - return; - } - } - - [self.groupsTableView reloadData]; - - // Check conditions to display the fake search bar into the table header - if (_enableSearchBar && self.groupsSearchBar.isHidden && self.groupsTableView.tableHeaderView == nil) - { - // Add the search bar by hiding it by default. - self.groupsTableView.tableHeaderView = tableSearchBar; - self.groupsTableView.contentOffset = CGPointMake(0, self.groupsTableView.contentOffset.y + tableSearchBar.frame.size.height); - } - - if (_shouldScrollToTopOnRefresh) - { - [self scrollToTop:NO]; - _shouldScrollToTopOnRefresh = NO; - } - - // In case of split view controller where the primary and secondary view controllers are displayed side-by-side on screen, - // the selected group (if any) is updated. - if (!self.splitViewController.isCollapsed) - { - [self refreshCurrentSelectedCell:NO]; - } -} - -- (void)hideSearchBar:(BOOL)hidden -{ - [super hideSearchBar:hidden]; - - if (!hidden) - { - // Remove the fake table header view if any - self.groupsTableView.tableHeaderView = nil; - self.groupsTableView.contentInset = UIEdgeInsetsZero; - } -} - -#pragma mark - - -- (void)refreshCurrentSelectedCell:(BOOL)forceVisible -{ - // Update here the index of the current selected cell (if any) - Useful in landscape mode with split view controller. - NSIndexPath *currentSelectedCellIndexPath = nil; - MasterTabBarController *masterTabBarController = [AppDelegate theDelegate].masterTabBarController; - if (masterTabBarController.selectedGroup) - { - // Look for the rank of this selected group in displayed groups - currentSelectedCellIndexPath = [self.dataSource cellIndexPathWithGroupId:masterTabBarController.selectedGroup.groupId]; - } - - if (currentSelectedCellIndexPath) - { - // Select the right row - [self.groupsTableView selectRowAtIndexPath:currentSelectedCellIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; - - if (forceVisible) - { - // Scroll table view to make the selected row appear at second position - NSInteger topCellIndexPathRow = currentSelectedCellIndexPath.row ? currentSelectedCellIndexPath.row - 1: currentSelectedCellIndexPath.row; - NSIndexPath* indexPath = [NSIndexPath indexPathForRow:topCellIndexPathRow inSection:currentSelectedCellIndexPath.section]; - [self.groupsTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO]; - } - } - else - { - NSIndexPath *indexPath = [self.groupsTableView indexPathForSelectedRow]; - if (indexPath) - { - [self.groupsTableView deselectRowAtIndexPath:indexPath animated:NO]; - } - } -} - -- (void)cancelEditionMode:(BOOL)forceRefresh -{ - if (self.groupsTableView.isEditing || self.isEditing) - { - // Leave editing mode first - isRefreshPending = forceRefresh; - [self setEditing:NO]; - } - else if (forceRefresh) - { - // Clean - editedGroupId = nil; - - [self refreshGroupsTable]; - } -} - -#pragma mark - MXKDataSourceDelegate - -- (Class)cellViewClassForCellData:(MXKCellData*)cellData -{ - id cellDataStoring = (id )cellData; - - if (cellDataStoring.group.membership != MXMembershipInvite) - { - return GroupTableViewCell.class; - } - else - { - return GroupInviteTableViewCell.class; - } -} - -- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData -{ - Class class = [self cellViewClassForCellData:cellData]; - - if ([class respondsToSelector:@selector(defaultReuseIdentifier)]) - { - return [class defaultReuseIdentifier]; - } - - return nil; -} - -- (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)actionIdentifier inCell:(id)cell userInfo:(NSDictionary *)userInfo -{ - // Handle here user actions on groups for Riot app - if ([actionIdentifier isEqualToString:kGroupInviteTableViewCellPreviewButtonPressed]) - { - // Retrieve the invited group - MXGroup *invitedGroup = userInfo[kGroupInviteTableViewCellRoomKey]; - - // Display the room preview - [[AppDelegate theDelegate].masterTabBarController selectGroup:invitedGroup inMatrixSession:self.mainSession]; - } - else if ([actionIdentifier isEqualToString:kGroupInviteTableViewCellDeclineButtonPressed]) - { - // Retrieve the invited group - MXGroup *invitedGroup = userInfo[kGroupInviteTableViewCellRoomKey]; - - NSIndexPath *indexPath = [self.dataSource cellIndexPathWithGroupId:invitedGroup.groupId]; - if (indexPath) - { - [self.dataSource leaveGroupAtIndexPath:indexPath]; - } - } - else - { - // Keep default implementation for other actions if any - if ([super respondsToSelector:@selector(cell:didRecognizeAction:userInfo:)]) - { - [super dataSource:dataSource didRecognizeAction:actionIdentifier inCell:cell userInfo:userInfo]; - } - } -} - -#pragma mark - UITableView delegate - -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; -{ - [super tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath]; - - cell.backgroundColor = ThemeService.shared.theme.backgroundColor; - - // Update the selected background view - if (ThemeService.shared.theme.selectedBackgroundColor) - { - cell.selectedBackgroundView = [[UIView alloc] init]; - cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; - } - else - { - if (tableView.style == UITableViewStylePlain) - { - cell.selectedBackgroundView = nil; - } - else - { - cell.selectedBackgroundView.backgroundColor = nil; - } - } -} - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - MXKTableViewHeaderFooterWithLabel *sectionHeader; - - if (tableView.numberOfSections > 1) - { - sectionHeader = [tableView dequeueReusableHeaderFooterViewWithIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier]; - sectionHeader.mxkContentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - sectionHeader.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - sectionHeader.mxkLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; - - NSString* title = [self.dataSource tableView:tableView titleForHeaderInSection:section]; - NSUInteger count = [self.dataSource tableView:tableView numberOfRowsInSection:section]; - if (count) - { - NSString *roomCount = [NSString stringWithFormat:@" %tu", count]; - NSMutableAttributedString *mutableSectionTitle = [[NSMutableAttributedString alloc] initWithString:title - attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.headerTextPrimaryColor}]; - [mutableSectionTitle appendAttributedString:[[NSMutableAttributedString alloc] initWithString:roomCount - attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.headerTextSecondaryColor}]]; - - sectionHeader.mxkLabel.attributedText = mutableSectionTitle; - } - else - { - sectionHeader.mxkLabel.text = title; - } - } - - return sectionHeader; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - UITableViewCell* cell = [self.groupsTableView cellForRowAtIndexPath:indexPath]; - - if ([cell isKindOfClass:[GroupInviteTableViewCell class]]) - { - // hide the selection - [tableView deselectRowAtIndexPath:indexPath animated:NO]; - } - else - { - [super tableView:tableView didSelectRowAtIndexPath:indexPath]; - } -} - -- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSMutableArray* actions; - - // add the swipe to delete only on joined group - if (indexPath.section == self.dataSource.joinedGroupsSection) - { - // Store the identifier of the room related to the edited cell. - id cellData = [self.dataSource cellDataAtIndex:indexPath]; - editedGroupId = cellData.group.groupId; - - actions = [[NSMutableArray alloc] init]; - - // Patch: Force the width of the button by adding whitespace characters into the title string. - UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ - - [self.dataSource leaveGroupAtIndexPath:indexPath]; - - }]; - - leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon_blue" backgroundColor:ThemeService.shared.theme.headerBackgroundColor patternSize:CGSizeMake(74, 74) resourceSize:CGSizeMake(24, 24)]; - [actions insertObject:leaveAction atIndex:0]; - } - - return actions; -} - -- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath -{ - [self cancelEditionMode:isRefreshPending]; -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - [super scrollViewDidScroll:scrollView]; - - if (scrollView == self.groupsTableView) - { - if (!self.groupsSearchBar.isHidden) - { - if (!self.groupsSearchBar.text.length && (scrollView.contentOffset.y + scrollView.adjustedContentInset.top > self.groupsSearchBar.frame.size.height)) - { - // Hide the search bar - [self hideSearchBar:YES]; - - // Refresh display - [self refreshGroupsTable]; - } - } - } -} - -#pragma mark - Room handling - -- (void)onPlusButtonPressed -{ - __weak typeof(self) weakSelf = self; - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - currentAlert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert popoverPresentationController].sourceView = plusButtonImageView; - [currentAlert popoverPresentationController].sourceRect = plusButtonImageView.bounds; - - [currentAlert mxk_setAccessibilityIdentifier:@"GroupsVCCreateRoomAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; -} - -#pragma mark - Table view scrolling - -- (void)scrollToTop:(BOOL)animated -{ - [self.groupsTableView setContentOffset:CGPointMake(-self.groupsTableView.adjustedContentInset.left, -self.groupsTableView.adjustedContentInset.top) animated:animated]; -} - -#pragma mark - MXKGroupListViewControllerDelegate - -- (void)groupListViewController:(MXKGroupListViewController *)groupListViewController didSelectGroup:(MXGroup *)group inMatrixSession:(MXSession *)mxSession -{ - // Open the room - [[AppDelegate theDelegate].masterTabBarController selectGroup:group inMatrixSession:mxSession]; -} - -#pragma mark - UISearchBarDelegate - -- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar -{ - if (searchBar == tableSearchBar) - { - [self hideSearchBar:NO]; - [self.groupsSearchBar becomeFirstResponder]; - return NO; - } - - return YES; - -} - -- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar -{ - dispatch_async(dispatch_get_main_queue(), ^{ - - [self.groupsSearchBar setShowsCancelButton:YES animated:NO]; - - }); -} - -- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar -{ - [self.groupsSearchBar setShowsCancelButton:NO animated:NO]; -} - -#pragma mark - MasterTabBarItemDisplayProtocol - -- (NSString *)masterTabBarItemTitle -{ - return [VectorL10n titleGroups]; -} - -@end diff --git a/Riot/Modules/Communities/Home/GroupHomeViewController.h b/Riot/Modules/Communities/Home/GroupHomeViewController.h deleted file mode 100644 index 510e1dfcc..000000000 --- a/Riot/Modules/Communities/Home/GroupHomeViewController.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MatrixKit.h" - -@interface GroupHomeViewController : MXKViewController - -@property (weak, nonatomic) IBOutlet UIView *mainHeaderContainer; -@property (weak, nonatomic) IBOutlet MXKImageView *groupAvatar; -@property (weak, nonatomic) IBOutlet UIView *groupAvatarMask; -@property (weak, nonatomic) IBOutlet UILabel *groupName; -@property (weak, nonatomic) IBOutlet UIView *groupNameMask; -@property (weak, nonatomic) IBOutlet UILabel *groupDescription; -@property (weak, nonatomic) IBOutlet UIView *countsContainer; -@property (weak, nonatomic) IBOutlet UIView *membersCountContainer; -@property (weak, nonatomic) IBOutlet UIView *roomsCountContainer; -@property (weak, nonatomic) IBOutlet UILabel *membersCountLabel; -@property (weak, nonatomic) IBOutlet UILabel *roomsCountLabel; - -@property (weak, nonatomic) IBOutlet UIView *inviteContainer; -@property (weak, nonatomic) IBOutlet UILabel *inviteLabel; -@property (weak, nonatomic) IBOutlet UIView *buttonsContainer; -@property (weak, nonatomic) IBOutlet UIButton *leftButton; -@property (weak, nonatomic) IBOutlet UIButton *rightButton; - -@property (weak, nonatomic) IBOutlet UIView *separatorView; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *separatorViewTopConstraint; - -@property (weak, nonatomic) IBOutlet UITextView *groupLongDescription; - -@property (strong, readonly, nonatomic) MXGroup *group; -@property (strong, readonly, nonatomic) MXSession *mxSession; - -/** - Returns the `UINib` object initialized for a `GroupHomeViewController`. - - @return The initialized `UINib` object or `nil` if there were errors during initialization - or the nib file could not be located. - */ -+ (UINib *)nib; - -/** - Creates and returns a new `GroupHomeViewController` object. - - @discussion This is the designated initializer for programmatic instantiation. - @return An initialized `GroupHomeViewController` object if successful, `nil` otherwise. - */ -+ (instancetype)groupHomeViewController; - -/** - Set the group for which the details are displayed. - Provide the related matrix session. - */ -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession; - -@end - diff --git a/Riot/Modules/Communities/Home/GroupHomeViewController.m b/Riot/Modules/Communities/Home/GroupHomeViewController.m deleted file mode 100644 index 1821eff53..000000000 --- a/Riot/Modules/Communities/Home/GroupHomeViewController.m +++ /dev/null @@ -1,903 +0,0 @@ -/* - Copyright 2017 Vector Creations Ltd - Copyright 2018 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - 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 "GroupHomeViewController.h" - -#import "GeneratedInterface-Swift.h" - -#import "ThemeService.h" -#import "Tools.h" - -#import "MXGroup+Riot.h" - -#import "DTCoreText.h" - -@interface GroupHomeViewController () -{ - MXHTTPOperation *currentRequest; - - /** - The current visibility of the status bar in this view controller. - */ - BOOL isStatusBarHidden; - - // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; - - // The options used to load long description html content. - NSDictionary *options; - NSString *groupLongDescriptionString; - - // The current pushed view controller - UIViewController *pushedViewController; -} - -@property (nonatomic, readonly) DTHTMLAttributedStringBuilderWillFlushCallback longDescriptionSanitizationCallback; - -@property (nonatomic) AnalyticsScreenTracker *screenTracker; - -@end - -@implementation GroupHomeViewController - -#pragma mark - Class methods - -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -+ (instancetype)groupHomeViewController -{ - return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -#pragma mark - - -- (void)finalizeInit -{ - [super finalizeInit]; - - // Setup `MXKViewControllerHandling` properties - self.enableBarTintColorStatusChange = NO; - self.rageShakeManager = [RageShakeManager sharedManager]; - - // Keep visible the status bar by default. - isStatusBarHidden = NO; - - // Set up sanitization for the long description - NSArray *allowedHTMLTags = @[ - @"font", // custom to matrix for IRC-style font coloring - @"del", // for markdown - @"body", // added internally by DTCoreText - @"h1", @"h2", @"h3", @"h4", @"h5", @"h6", @"blockquote", @"p", @"a", @"ul", @"ol", - @"nl", @"li", @"b", @"i", @"u", @"strong", @"em", @"strike", @"code", @"hr", @"br", @"div", - @"table", @"thead", @"caption", @"tbody", @"tr", @"th", @"td", @"pre", - @"img" - ]; - - MXWeakify(self); - _longDescriptionSanitizationCallback = ^(DTHTMLElement *element) { - MXStrongifyAndReturnIfNil(self); - [element sanitizeWith:allowedHTMLTags bodyFont:self->_groupLongDescription.font imageHandler:[self groupLongDescriptionImageHandler]]; - }; - - self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenGroup]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - [self.leftButton setTitle:[VectorL10n decline] forState:UIControlStateNormal]; - [self.leftButton setTitle:[VectorL10n decline] forState:UIControlStateHighlighted]; - [self.rightButton setTitle:[VectorL10n join] forState:UIControlStateNormal]; - [self.rightButton setTitle:[VectorL10n join] forState:UIControlStateHighlighted]; - - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; - [tap setNumberOfTouchesRequired:1]; - [tap setNumberOfTapsRequired:1]; - [tap setDelegate:self]; - [_groupNameMask addGestureRecognizer:tap]; - _groupNameMask.userInteractionEnabled = YES; - - // Add tap to show the group avatar in fullscreen - tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; - [tap setNumberOfTouchesRequired:1]; - [tap setNumberOfTapsRequired:1]; - [tap setDelegate:self]; - [_groupAvatarMask addGestureRecognizer:tap]; - _groupAvatarMask.userInteractionEnabled = YES; - - // Observe user interface theme change. - kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self userInterfaceThemeDidChange]; - - }]; - [self userInterfaceThemeDidChange]; -} - -- (void)userInterfaceThemeDidChange -{ - [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; - - self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; - - self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; - self.mainHeaderContainer.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - - _groupName.textColor = ThemeService.shared.theme.textPrimaryColor; - - _groupDescription.textColor = ThemeService.shared.theme.baseTextSecondaryColor; - _groupDescription.numberOfLines = 0; - - self.inviteLabel.textColor = ThemeService.shared.theme.baseTextSecondaryColor; - self.inviteLabel.numberOfLines = 0; - - self.separatorView.backgroundColor = ThemeService.shared.theme.lineBreakColor; - - [self.leftButton.layer setCornerRadius:5]; - self.leftButton.clipsToBounds = YES; - self.leftButton.backgroundColor = ThemeService.shared.theme.tintColor; - - [self.rightButton.layer setCornerRadius:5]; - self.rightButton.clipsToBounds = YES; - self.rightButton.backgroundColor = ThemeService.shared.theme.tintColor; - - if (_groupLongDescription) - { - _groupLongDescription.textColor = ThemeService.shared.theme.textSecondaryColor; - _groupLongDescription.tintColor = ThemeService.shared.theme.tintColor; - - // Update HTML loading options - NSUInteger bgColor = [MXKTools rgbValueWithColor:ThemeService.shared.theme.headerBackgroundColor]; - NSString *defaultCSS = [NSString stringWithFormat:@" \ - pre,code { \ - background-color: #%06lX; \ - display: inline; \ - font-family: monospace; \ - white-space: pre; \ - -coretext-fontname: Menlo-Regular; \ - font-size: small; \ - }", (unsigned long)bgColor]; - - // Apply the css style with some sanitisation. - options = @{ - DTUseiOS6Attributes: @(YES), // Enable it to be able to display the attributed string in a UITextView - DTDefaultFontFamily: _groupLongDescription.font.familyName, - DTDefaultFontName: _groupLongDescription.font.fontName, - DTDefaultFontSize: @(_groupLongDescription.font.pointSize), - DTDefaultTextColor: _groupLongDescription.textColor, - DTDefaultLinkDecoration: @(NO), - DTDefaultStyleSheet: [[DTCSSStylesheet alloc] initWithStyleBlock:defaultCSS], - DTWillFlushBlockCallBack: self.longDescriptionSanitizationCallback - }; - } - - [self setNeedsStatusBarAppearanceUpdate]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle -{ - return ThemeService.shared.theme.statusBarStyle; -} - -- (BOOL)prefersStatusBarHidden -{ - // Return the current status bar visibility. - return isStatusBarHidden; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - [self.screenTracker trackScreen]; - - // Release the potential pushed view controller - [self releasePushedViewController]; - - if (_group) - { - // Restore the listeners on the group update. - [self registerOnGroupChangeNotifications]; - - // Check whether the selected group is stored in the user's session, or if it is a group preview. - // Replace the displayed group instance with the one stored in the session (if any). - MXGroup *storedGroup = [_mxSession groupWithGroupId:_group.groupId]; - BOOL isPreview = (!storedGroup); - - // Force refresh - [self refreshDisplayWithGroup:(isPreview ? _group : storedGroup)]; - - // Prepare a block called on successful update in case of a group preview. - // Indeed the group update notifications are triggered by the matrix session only for the user's groups. - void (^success)(void) = ^void(void) - { - [self refreshDisplayWithGroup:self->_group]; - }; - - // Trigger a refresh on the group summary. - [self.mxSession updateGroupSummary:self->_group success:(isPreview ? success : nil) failure:^(NSError *error) { - - MXLogDebug(@"[GroupHomeViewController] viewWillAppear: group summary update failed %@", self->_group.groupId); - - }]; - // Trigger a refresh on the group members (ignore here the invited users). - [self.mxSession updateGroupUsers:self->_group success:(isPreview ? success : nil) failure:^(NSError *error) { - - MXLogDebug(@"[GroupHomeViewController] viewWillAppear: group members update failed %@", self->_group.groupId); - - }]; - // Trigger a refresh on the group rooms. - [self.mxSession updateGroupRooms:self->_group success:(isPreview ? success : nil) failure:^(NSError *error) { - - MXLogDebug(@"[GroupHomeViewController] viewWillAppear: group rooms update failed %@", self->_group.groupId); - - }]; - } -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - [self cancelRegistrationOnGroupChangeNotifications]; -} - -- (void)viewDidLayoutSubviews -{ - [super viewDidLayoutSubviews]; - - // Scroll to the top the long group description. - _groupLongDescription.contentOffset = CGPointZero; -} - -- (void)destroy -{ - // Release the potential pushed view controller - [self releasePushedViewController]; - - // Note: all observers are removed during super call. - [super destroy]; - - _group = nil; - _mxSession = nil; - - [currentRequest cancel]; - currentRequest = nil; - - if (kThemeServiceDidChangeThemeNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; - kThemeServiceDidChangeThemeNotificationObserver = nil; - } -} - -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession -{ - if (_mxSession != mxSession) - { - [self cancelRegistrationOnGroupChangeNotifications]; - _mxSession = mxSession; - - [self registerOnGroupChangeNotifications]; - } - - [self addMatrixSession:mxSession]; - - [self refreshDisplayWithGroup:group]; -} - -#pragma mark - - -- (void)pushViewController:(UIViewController*)viewController -{ - // Keep ref on pushed view controller - pushedViewController = viewController; - - // Check whether the view controller is displayed inside a segmented one. - if (self.parentViewController.navigationController) - { - // Hide back button title - [self.parentViewController vc_removeBackTitle]; - - [self.parentViewController.navigationController pushViewController:viewController animated:YES]; - } - else - { - // Hide back button title - [self vc_removeBackTitle]; - - [self.navigationController pushViewController:viewController animated:YES]; - } -} - -- (void)releasePushedViewController -{ - if (pushedViewController) - { - if ([pushedViewController isKindOfClass:[UINavigationController class]]) - { - UINavigationController *navigationController = (UINavigationController*)pushedViewController; - for (id subViewController in navigationController.viewControllers) - { - if ([subViewController respondsToSelector:@selector(destroy)]) - { - [subViewController destroy]; - } - } - } - else if ([pushedViewController respondsToSelector:@selector(destroy)]) - { - [(id)pushedViewController destroy]; - } - - pushedViewController = nil; - } -} - -- (void)registerOnGroupChangeNotifications -{ - if (_mxSession) - { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroupDetails:) name:kMXSessionDidUpdateGroupSummaryNotification object:_mxSession]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroupDetails:) name:kMXSessionDidUpdateGroupUsersNotification object:_mxSession]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroupDetails:) name:kMXSessionDidUpdateGroupRoomsNotification object:_mxSession]; - } -} - -- (void)cancelRegistrationOnGroupChangeNotifications -{ - // Remove any pending observers - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupSummaryNotification object:_mxSession]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupUsersNotification object:_mxSession]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupRoomsNotification object:_mxSession]; -} - -- (void)didUpdateGroupDetails:(NSNotification *)notif -{ - MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey]; - if (group && [group.groupId isEqualToString:_group.groupId]) - { - // Update the current displayed group instance with the one stored in the session - [self refreshDisplayWithGroup:group]; - } -} - -- (void)refreshDisplayWithGroup:(MXGroup*)group -{ - _group = group; - - // Check whether the view controller has been loaded - if (!self.isViewLoaded) - { - return; - } - - if (_group) - { - [_group setGroupAvatarImageIn:_groupAvatar matrixSession:self.mxSession]; - - _groupName.text = _group.summary.profile.name; - if (!_groupName.text.length) - { - _groupName.text = _group.groupId; - } - - _groupDescription.text = _group.summary.profile.shortDescription; - - if (_group.users.totalUserCountEstimate == 1) - { - _membersCountLabel.text = [VectorL10n groupHomeOneMemberFormat]; - _membersCountContainer.hidden = NO; - } - else if (_group.users.totalUserCountEstimate > 1) - { - _membersCountLabel.text = [VectorL10n groupHomeMultiMembersFormat:_group.users.totalUserCountEstimate]; - _membersCountContainer.hidden = NO; - } - else - { - _membersCountLabel.text = nil; - _membersCountContainer.hidden = YES; - } - - if (_group.rooms.totalRoomCountEstimate == 1) - { - _roomsCountLabel.text = [VectorL10n groupHomeOneRoomFormat]; - _roomsCountContainer.hidden = NO; - } - else if (_group.rooms.totalRoomCountEstimate > 1) - { - _roomsCountLabel.text = [VectorL10n groupHomeMultiRoomsFormat:_group.rooms.totalRoomCountEstimate]; - _roomsCountContainer.hidden = NO; - } - else - { - _roomsCountLabel.text = nil; - _roomsCountContainer.hidden = YES; - } - - _countsContainer.hidden = (_membersCountContainer.isHidden && _roomsCountContainer.isHidden); - - if (_group.membership == MXMembershipInvite) - { - self.inviteContainer.hidden = NO; - - if (_group.inviter) - { - NSString *inviter = _group.inviter; - - if ([MXTools isMatrixUserIdentifier:inviter]) - { - // Get the user that corresponds to this member - MXUser *user = [self.mxSession userWithUserId:inviter]; - if (user.displayname.length) - { - inviter = user.displayname; - } - } - - self.inviteLabel.text = [VectorL10n groupInvitationFormat:inviter]; - } - else - { - self.inviteLabel.text = nil; - } - - [self.inviteContainer layoutIfNeeded]; - - if (_separatorViewTopConstraint.constant != self.inviteContainer.frame.size.height) - { - _separatorViewTopConstraint.constant = self.inviteContainer.frame.size.height; - [self.view setNeedsLayout]; - } - } - else - { - self.inviteContainer.hidden = YES; - if (_separatorViewTopConstraint.constant != 0) - { - _separatorViewTopConstraint.constant = 0; - [self.view setNeedsLayout]; - } - } - - [self refreshGroupLongDescription]; - } - else - { - _groupAvatar.image = nil; - - _groupName.text = nil; - _groupDescription.text = nil; - - self.inviteLabel.text = nil; - _groupLongDescription.text = nil; - - self.inviteContainer.hidden = YES; - - _separatorViewTopConstraint.constant = 0; - - _membersCountLabel.text = nil; - _roomsCountLabel.text = nil; - _countsContainer.hidden = YES; - } - - // Round image view for thumbnail - _groupAvatar.layer.cornerRadius = _groupAvatar.frame.size.width / 2; - _groupAvatar.clipsToBounds = YES; - - _groupAvatar.defaultBackgroundColor = ThemeService.shared.theme.headerBackgroundColor; -} - -- (void)refreshGroupLongDescription -{ - if (_group.summary.profile.longDescription.length) - { - groupLongDescriptionString = _group.summary.profile.longDescription; - } - else - { - groupLongDescriptionString = nil; - } - - [self renderGroupLongDescription]; -} - -- (void)renderGroupLongDescription -{ - if (groupLongDescriptionString) - { - // Using DTCoreText, which renders static string, helps to avoid code injection attacks - // that could happen with the default HTML renderer of NSAttributedString which is a - // webview. - // The supplied options include a callback to sanitize html tags and load image data. - NSAttributedString *attributedString = [[NSAttributedString alloc] initWithHTMLData:[groupLongDescriptionString dataUsingEncoding:NSUTF8StringEncoding] options:options documentAttributes:NULL]; - - // Apply additional treatments - NSInteger mxIdsBitMask = (MXKTOOLS_USER_IDENTIFIER_BITWISE | MXKTOOLS_ROOM_IDENTIFIER_BITWISE | MXKTOOLS_ROOM_ALIAS_BITWISE | MXKTOOLS_EVENT_IDENTIFIER_BITWISE | MXKTOOLS_GROUP_IDENTIFIER_BITWISE); - - NSMutableAttributedString *mutableStr = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString]; - [MXKTools createLinksInMutableAttributedString:mutableStr forEnabledMatrixIds:mxIdsBitMask]; - [MXKTools removeDTCoreTextArtifacts:mutableStr]; - - // Finalize the attributed string by removing DTCoreText artifacts (Trim trailing newlines, replace DTImageTextAttachments...) - _groupLongDescription.attributedText = mutableStr; - _groupLongDescription.contentOffset = CGPointZero; - } - else - { - _groupLongDescription.text = nil; - } -} - -- (NSURL *(^)(NSString *sourceURL, CGFloat width, CGFloat height))groupLongDescriptionImageHandler -{ - MXWeakify(self); - return ^NSURL *(NSString *sourceURL, CGFloat width, CGFloat height) { - - MXStrongifyAndReturnValueIfNil(self, nil); - NSURL *localSourceURL; - - if (width != -1 && height != -1) - { - CGSize size = CGSizeMake(width, height); - // Build the cache path for the a thumbnail of this image. - NSString *cacheFilePath = [MXMediaManager thumbnailCachePathForMatrixContentURI:sourceURL - andType:nil - inFolder:kMXMediaManagerDefaultCacheFolder - toFitViewSize:size - withMethod:MXThumbnailingMethodScale]; - // Check whether the provided URL is a valid Matrix Content URI. - if (cacheFilePath) - { - // Download the thumbnail if it is not already stored in the cache. - if (![[NSFileManager defaultManager] fileExistsAtPath:cacheFilePath]) - { - MXWeakify(self); - [self.mxSession.mediaManager downloadThumbnailFromMatrixContentURI:sourceURL - withType:nil - inFolder:kMXMediaManagerDefaultCacheFolder - toFitViewSize:size - withMethod:MXThumbnailingMethodScale - success:^(NSString *outputFilePath) { - MXStrongifyAndReturnIfNil(self); - [self refreshGroupLongDescription]; - } - failure:nil]; - } - else - { - // Update the local url - localSourceURL = [NSURL fileURLWithPath:cacheFilePath]; - } - } - } - else - { - // Build the cache path for this image. - NSString* cacheFilePath = [MXMediaManager cachePathForMatrixContentURI:sourceURL - andType:nil - inFolder:kMXMediaManagerDefaultCacheFolder]; - - // Check whether the provided URL is a valid Matrix Content URI. - if (cacheFilePath) - { - // Download the image if it is not already stored in the cache. - if (![[NSFileManager defaultManager] fileExistsAtPath:cacheFilePath]) - { - MXWeakify(self); - [self.mxSession.mediaManager downloadMediaFromMatrixContentURI:sourceURL - withType:nil - inFolder:kMXMediaManagerDefaultCacheFolder - success:^(NSString *outputFilePath) { - MXStrongifyAndReturnIfNil(self); - [self refreshGroupLongDescription]; - } - failure:nil]; - } - else - { - // Update the local path - localSourceURL = [NSURL fileURLWithPath:cacheFilePath]; - } - } - } - return localSourceURL; - - }; -} - -- (void)didSelectRoomId:(NSString*)roomId -{ - // Check first if the user already joined this room. - if ([self.mxSession roomWithRoomId:roomId]) - { - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession]; - [roomDataSourceManager roomDataSourceForRoom:roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { - // Open this room - RoomViewController *roomViewController = [RoomViewController roomViewController]; - roomViewController.showMissedDiscussionsBadge = NO; - [roomViewController displayRoom:roomDataSource]; - [self pushViewController:roomViewController]; - }]; - } - else - { - // Prepare a preview - RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithRoomId:roomId andSession:self.mxSession]; - __weak typeof(self) weakSelf = self; - [self startActivityIndicator]; - - // Try to get more information about the room before opening its preview - [roomPreviewData peekInRoom:^(BOOL succeeded) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - - // Display the room preview - RoomViewController *roomViewController = [RoomViewController roomViewController]; - roomViewController.showMissedDiscussionsBadge = NO; - [roomViewController displayRoomPreview:roomPreviewData]; - [self pushViewController:roomViewController]; - } - - }]; - } -} - -#pragma mark - Action - -- (IBAction)onButtonPressed:(id)sender -{ - if (!currentRequest) - { - if (sender == self.rightButton) - { - // Accept the invite - __weak typeof(self) weakSelf = self; - [self startActivityIndicator]; - - currentRequest = [self.mxSession acceptGroupInvite:_group.groupId success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentRequest = nil; - [self stopActivityIndicator]; - - [self refreshDisplayWithGroup:[self->_mxSession groupWithGroupId:self->_group.groupId]]; - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[GroupDetailsViewController] join group (%@) failed", self->_group.groupId); - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentRequest = nil; - [self stopActivityIndicator]; - } - - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - else if (sender == self.leftButton) - { - // Decline the invite - __weak typeof(self) weakSelf = self; - [self startActivityIndicator]; - - currentRequest = [self.mxSession leaveGroup:_group.groupId success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentRequest = nil; - [self stopActivityIndicator]; - - [self withdrawViewControllerAnimated:YES completion:nil]; - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[GroupDetailsViewController] leave group (%@) failed", self->_group.groupId); - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentRequest = nil; - [self stopActivityIndicator]; - } - - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - } -} - -- (void)handleTapGesture:(UITapGestureRecognizer*)tapGestureRecognizer -{ - UIView *view = tapGestureRecognizer.view; - - if (view == _groupNameMask && _group.summary.profile.name) - { - if ([_groupName.text isEqualToString:_group.summary.profile.name]) - { - // Display group's matrix id - _groupName.text = _group.groupId; - } - else - { - // Restore display name - _groupName.text = _group.summary.profile.name; - } - } - else if (view == _groupAvatarMask) - { - // Show the avatar in full screen - __block MXKImageView * avatarFullScreenView = [[MXKImageView alloc] initWithFrame:CGRectZero]; - avatarFullScreenView.stretchable = YES; - - MXWeakify(self); - [avatarFullScreenView setRightButtonTitle:[VectorL10n ok] handler:^(MXKImageView* imageView, NSString* buttonTitle) { - - MXStrongifyAndReturnIfNil(self); - [avatarFullScreenView dismissSelection]; - [avatarFullScreenView removeFromSuperview]; - - avatarFullScreenView = nil; - - self->isStatusBarHidden = NO; - // Trigger status bar update - [self setNeedsStatusBarAppearanceUpdate]; - }]; - - [avatarFullScreenView setImageURI:_group.summary.profile.avatarUrl - withType:nil - andImageOrientation:UIImageOrientationUp - previewImage:self.groupAvatar.image - mediaManager:_mxSession.mediaManager]; - - [avatarFullScreenView showFullScreen]; - isStatusBarHidden = YES; - - // Trigger status bar update - [self setNeedsStatusBarAppearanceUpdate]; - } -} - -#pragma mark - UITextView delegate - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange -{ - BOOL shouldInteractWithURL = YES; - // Try to catch universal link supported by the app - - // When a link refers to a room alias/id, a user id or an event id, the non-ASCII characters (like '#' in room alias) has been escaped - // to be able to convert it into a legal URL string. - NSString *absoluteURLString = [URL.absoluteString stringByRemovingPercentEncoding]; - - // If the link can be open it by the app, let it do - if ([Tools isUniversalLink:URL]) - { - shouldInteractWithURL = NO; - - [[AppDelegate theDelegate] handleUniversalLinkURL:URL]; - } - // Open a detail screen about the clicked user - else if ([MXTools isMatrixUserIdentifier:absoluteURLString]) - { - shouldInteractWithURL = NO; - - NSString *userId = absoluteURLString; - MXKContact *contact; - // Use the contact detail VC for other users - MXUser *user = [self.mxSession userWithUserId:userId]; - if (user) - { - contact = [[MXKContact alloc] initMatrixContactWithDisplayName:((user.displayname.length > 0) ? user.displayname : user.userId) andMatrixID:user.userId]; - } - else - { - contact = [[MXKContact alloc] initMatrixContactWithDisplayName:userId andMatrixID:userId]; - } - - ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController instantiate]; - contactDetailsViewController.enableVoipCall = NO; - contactDetailsViewController.contact = contact; - - [self pushViewController:contactDetailsViewController]; - } - // Open the clicked room - else if ([MXTools isMatrixRoomIdentifier:absoluteURLString] || [MXTools isMatrixRoomAlias:absoluteURLString]) - { - shouldInteractWithURL = NO; - - NSString *roomIdOrAlias = absoluteURLString; - NSString *roomId; - - if ([roomIdOrAlias hasPrefix:@"#"]) - { - // Check whether the room alias can be translated locally into the room id. - MXRoom *room = [self.mxSession roomWithAlias:roomIdOrAlias]; - if (room) - { - roomId = room.roomId; - } - } - else - { - roomId = roomIdOrAlias; - } - - if (roomId) - { - [self didSelectRoomId:roomId]; - } - else - { - // The alias may be not part of user's rooms states - // Ask the HS to resolve the room alias into a room id and then retry - __weak typeof(self) weakSelf = self; - [self startActivityIndicator]; - - [self.mxSession.matrixRestClient resolveRoomAlias:roomIdOrAlias success:^(MXRoomAliasResolution *resolution) { - if (roomId && weakSelf) - { - typeof(self) self = weakSelf; - - [self stopActivityIndicator]; - [self didSelectRoomId:resolution.roomId]; - } - - } failure:^(NSError *error) { - MXLogDebug(@"[GroupHomeViewController] Error: The homeserver failed to resolve the room alias (%@)", roomIdOrAlias); - }]; - } - } - // Preview the clicked group - else if ([MXTools isMatrixGroupIdentifier:absoluteURLString]) - { - shouldInteractWithURL = NO; - - // Open the group or preview it - NSString *fragment = [NSString stringWithFormat:@"/group/%@", - [MXTools encodeURIComponent:absoluteURLString]]; - UniversalLink *link = [[UniversalLink alloc] initWithUrl:URL]; - [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromLink:link]; - } - - return shouldInteractWithURL; -} -#pragma clang diagnostic pop - -@end diff --git a/Riot/Modules/Communities/Home/GroupHomeViewController.xib b/Riot/Modules/Communities/Home/GroupHomeViewController.xib deleted file mode 100644 index 4b9a27b42..000000000 --- a/Riot/Modules/Communities/Home/GroupHomeViewController.xib +++ /dev/null @@ -1,285 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/Communities/Members/GroupParticipantsViewController.h b/Riot/Modules/Communities/Members/GroupParticipantsViewController.h deleted file mode 100644 index e5b4a9ba8..000000000 --- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "ContactsTableViewController.h" -#import "MatrixKit.h" - -@class Contact; - -/** - 'GroupParticipantsViewController' instance is used to list members of the group defined by the property 'mxGroup'. - When this property is nil, the view controller is empty. - */ -@interface GroupParticipantsViewController : MXKViewController -{ -@protected - /** - Section indexes - */ - NSInteger participantsSection; - NSInteger invitedSection; - - /** - The current list of joined members. - */ - NSMutableArray *actualParticipants; - - /** - The current list of invited members. - */ - NSMutableArray *invitedParticipants; -} - -@property (weak, nonatomic) IBOutlet UITableView *tableView; -@property (weak, nonatomic) IBOutlet UIView *searchBarHeader; -@property (weak, nonatomic) IBOutlet UISearchBar *searchBarView; -@property (weak, nonatomic) IBOutlet UIView *searchBarHeaderBorder; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *searchBarTopConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *tableViewBottomConstraint; - -/** - A matrix group (nil by default). - */ -@property (strong, readonly, nonatomic) MXGroup *group; -@property (strong, readonly, nonatomic) MXSession *mxSession; - -/** - Returns the `UINib` object initialized for a `GroupParticipantsViewController`. - - @return The initialized `UINib` object or `nil` if there were errors during initialization - or the nib file could not be located. - */ -+ (UINib *)nib; - -/** - Creates and returns a new `GroupParticipantsViewController` object. - - @discussion This is the designated initializer for programmatic instantiation. - @return An initialized `GroupParticipantsViewController` object if successful, `nil` otherwise. - */ -+ (instancetype)groupParticipantsViewController; - -/** - Set the group for which the details are displayed. - Provide the related matrix session. - */ -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession; - -@end - diff --git a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m deleted file mode 100644 index fc05a9d12..000000000 --- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m +++ /dev/null @@ -1,1324 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupParticipantsViewController.h" - -#import "GeneratedInterface-Swift.h" - -#import "Contact.h" -#import "ContactTableViewCell.h" - -#import "RageShakeManager.h" - -@interface GroupParticipantsViewController () -{ - // Search result - NSString *currentSearchText; - NSMutableArray *filteredActualParticipants; - NSMutableArray *filteredInvitedParticipants; - - // Mask view while processing a request - UIActivityIndicatorView *pendingMaskSpinnerView; - - // The current pushed view controller - UIViewController *pushedViewController; - - // Display a gradient view above the screen. - CAGradientLayer* tableViewMaskLayer; - - // Display a button to invite new member. - UIImageView* addParticipantButtonImageView; - NSLayoutConstraint *addParticipantButtonImageViewBottomConstraint; - - UIAlertController *currentAlert; - - // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; -} - -@end - -@implementation GroupParticipantsViewController - -#pragma mark - Class methods - -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -+ (instancetype)groupParticipantsViewController -{ - return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -#pragma mark - - -- (void)finalizeInit -{ - [super finalizeInit]; - - // Setup `MXKViewControllerHandling` properties - self.enableBarTintColorStatusChange = NO; - self.rageShakeManager = [RageShakeManager sharedManager]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - // Do any additional setup after loading the view, typically from a nib. - - // Check whether the view controller has been pushed via storyboard - if (!self.tableView) - { - // Instantiate view controller objects - [[[self class] nib] instantiateWithOwner:self options:nil]; - } - - // Adjust Top and Bottom constraints to take into account potential navBar and tabBar. - [NSLayoutConstraint deactivateConstraints:@[_searchBarTopConstraint, _tableViewBottomConstraint]]; - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated" - _searchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.searchBarHeader - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - - _tableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeBottom - multiplier:1.0f - constant:0.0f]; - #pragma clang diagnostic pop - - [NSLayoutConstraint activateConstraints:@[_searchBarTopConstraint, _tableViewBottomConstraint]]; - - _searchBarView.placeholder = [VectorL10n groupParticipantsFilterMembers]; - _searchBarView.returnKeyType = UIReturnKeyDone; - _searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone; - - // Search bar header is hidden when no group is provided - _searchBarHeader.hidden = (self.group == nil); - - // Enable self-sizing cells and section headers. - self.tableView.rowHeight = UITableViewAutomaticDimension; - self.tableView.estimatedRowHeight = 74; - self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension; - self.tableView.estimatedSectionHeaderHeight = 30; - - // Hide line separators of empty cells - self.tableView.tableFooterView = [[UIView alloc] init]; - - [self.tableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:@"ParticipantTableViewCellId"]; - [self.tableView registerNib:MXKTableViewHeaderFooterWithLabel.nib forHeaderFooterViewReuseIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier]; - - // @TODO: Add programmatically the button to add participant. - //[self addAddParticipantButton]; - - // Observe user interface theme change. - kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self userInterfaceThemeDidChange]; - - }]; - [self userInterfaceThemeDidChange]; -} - -- (void)userInterfaceThemeDidChange -{ - [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; - - self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; - - [self refreshSearchBarItemsColor:_searchBarView]; - - _searchBarHeaderBorder.backgroundColor = ThemeService.shared.theme.headerBorderColor; - - // Check the table view style to select its bg color. - self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); - self.view.backgroundColor = self.tableView.backgroundColor; - self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor; - - // Update the gradient view above the screen - CGFloat white = 1.0; - [ThemeService.shared.theme.backgroundColor getWhite:&white alpha:nil]; - CGColorRef opaqueWhiteColor = [UIColor colorWithWhite:white alpha:1.0].CGColor; - CGColorRef transparentWhiteColor = [UIColor colorWithWhite:white alpha:0].CGColor; - tableViewMaskLayer.colors = @[(__bridge id) transparentWhiteColor, (__bridge id) transparentWhiteColor, (__bridge id) opaqueWhiteColor]; - - if (self.tableView.dataSource) - { - [self.tableView reloadData]; - } - - [self setNeedsStatusBarAppearanceUpdate]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle -{ - return ThemeService.shared.theme.statusBarStyle; -} - -// This method is called when the viewcontroller is added or removed from a container view controller. -- (void)didMoveToParentViewController:(nullable UIViewController *)parent -{ - [super didMoveToParentViewController:parent]; -} - -- (void)destroy -{ - // Release the potential pushed view controller - [self releasePushedViewController]; - - if (kThemeServiceDidChangeThemeNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; - kThemeServiceDidChangeThemeNotificationObserver = nil; - } - - if (currentAlert) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = nil; - } - - _group = nil; - _mxSession = nil; - - filteredActualParticipants = nil; - filteredInvitedParticipants = nil; - - actualParticipants = nil; - invitedParticipants = nil; - - [self removePendingActionMask]; - - // Note: all observers are removed during super call. - [super destroy]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Release the potential pushed view controller - [self releasePushedViewController]; - - if (_group) - { - // Restore the listeners on the group update. - [self registerOnGroupChangeNotifications]; - - // Check whether the selected group is stored in the user's session, or if it is a group preview. - // Replace the displayed group instance with the one stored in the session (if any). - MXGroup *storedGroup = [_mxSession groupWithGroupId:_group.groupId]; - BOOL isPreview = (!storedGroup); - - // Force refresh - [self refreshDisplayWithGroup:(isPreview ? _group : storedGroup)]; - - // Prepare a block called on successful update in case of a group preview. - // Indeed the group update notifications are triggered by the matrix session only for the user's groups. - void (^success)(void) = ^void(void) - { - [self refreshDisplayWithGroup:self->_group]; - }; - - // Trigger a refresh on the group members and the invited users. - [self.mxSession updateGroupUsers:_group success:(isPreview ? success : nil) failure:^(NSError *error) { - - MXLogDebug(@"[GroupParticipantsViewController] viewWillAppear: group members update failed %@", self->_group.groupId); - - }]; - [self.mxSession updateGroupInvitedUsers:_group success:(isPreview ? success : nil) failure:^(NSError *error) { - - MXLogDebug(@"[GroupParticipantsViewController] viewWillAppear: invited users update failed %@", self->_group.groupId); - - }]; - } -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - [self cancelRegistrationOnGroupChangeNotifications]; - - if (currentAlert) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = nil; - } - - // cancel any pending search - [self searchBarCancelButtonClicked:_searchBarView]; -} - -- (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion -{ - // Check whether the current view controller is displayed inside a segmented view controller in order to withdraw the right item - if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) - { - [((SegmentedViewController*)self.parentViewController) withdrawViewControllerAnimated:animated completion:completion]; - } - else - { - [super withdrawViewControllerAnimated:animated completion:completion]; - } -} - -- (void)viewDidLayoutSubviews -{ - [super viewDidLayoutSubviews]; - - // Sanity check - if (tableViewMaskLayer) - { - CGRect currentBounds = tableViewMaskLayer.bounds; - CGRect newBounds = CGRectIntegral(self.view.frame); - - newBounds.size.height -= self.keyboardHeight; - - // Check if there is an update - if (!CGSizeEqualToSize(currentBounds.size, newBounds.size)) - { - newBounds.origin = CGPointZero; - - [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn - animations:^{ - - self->tableViewMaskLayer.bounds = newBounds; - - } - completion:^(BOOL finished){ - }]; - - } - - // Hide the addParticipants button on landscape when keyboard is visible - BOOL isLandscapeOriented = UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation); - addParticipantButtonImageView.hidden = tableViewMaskLayer.hidden = (isLandscapeOriented && self.keyboardHeight); - } -} - -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession -{ - // Cancel any pending search - [self searchBarCancelButtonClicked:_searchBarView]; - - if (_mxSession != mxSession) - { - [self cancelRegistrationOnGroupChangeNotifications]; - _mxSession = mxSession; - - [self registerOnGroupChangeNotifications]; - } - - [self addMatrixSession:mxSession]; - - [self refreshDisplayWithGroup:group]; -} - -#pragma mark - - -- (void)registerOnGroupChangeNotifications -{ - if (_mxSession) - { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroupUsers:) name:kMXSessionDidUpdateGroupUsersNotification object:_mxSession]; - } -} - -- (void)cancelRegistrationOnGroupChangeNotifications -{ - // Remove any pending observers - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupUsersNotification object:_mxSession]; -} - -- (void)didUpdateGroupUsers:(NSNotification *)notif -{ - MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey]; - if (group && [group.groupId isEqualToString:_group.groupId]) - { - // Update the current displayed group instance with the one stored in the session - [self refreshDisplayWithGroup:group]; - } -} - -- (void)refreshDisplayWithGroup:(MXGroup *)group -{ - _group = group; - - if (_group) - { - _searchBarHeader.hidden = NO; - } - else - { - // Search bar header is hidden when no group is provided - _searchBarHeader.hidden = YES; - } - - // Refresh the members list. - [self refreshParticipantsList]; -} - -- (void)startActivityIndicator -{ - // Check whether the current view controller is displayed inside a segmented view controller in order to run the right activity view - if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) - { - [((SegmentedViewController*)self.parentViewController) startActivityIndicator]; - - // Force stop the activity view of the view controller - [self.activityIndicator stopAnimating]; - } - else - { - [super startActivityIndicator]; - } -} - -- (void)stopActivityIndicator -{ - // Check whether the current view controller is displayed inside a segmented view controller in order to stop the right activity view - if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) - { - [((SegmentedViewController*)self.parentViewController) stopActivityIndicator]; - - // Force stop the activity view of the view controller - [self.activityIndicator stopAnimating]; - } - else - { - [super stopActivityIndicator]; - } -} - -- (void)setKeyboardHeight:(CGFloat)keyboardHeight -{ - super.keyboardHeight = keyboardHeight; - - // Update addParticipants button position with animation - [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn - animations:^{ - - self->addParticipantButtonImageViewBottomConstraint.constant = keyboardHeight + 9; - - // Force to render the view - [self.view layoutIfNeeded]; - - } - completion:^(BOOL finished){ - }]; -} - -#pragma mark - Internals - -- (void)refreshTableView -{ - [self.tableView reloadData]; -} - -- (void)addAddParticipantButton -{ - // Add blur mask programmatically - tableViewMaskLayer = [CAGradientLayer layer]; - - // Consider the grayscale components of the ThemeService.shared.theme.backgroundColor. - CGFloat white = 1.0; - [ThemeService.shared.theme.backgroundColor getWhite:&white alpha:nil]; - - CGColorRef opaqueWhiteColor = [UIColor colorWithWhite:white alpha:1.0].CGColor; - CGColorRef transparentWhiteColor = [UIColor colorWithWhite:white alpha:0].CGColor; - - tableViewMaskLayer.colors = @[(__bridge id) transparentWhiteColor, (__bridge id) transparentWhiteColor, (__bridge id) opaqueWhiteColor]; - - // display a gradient to the rencents bottom (20% of the bottom of the screen) - tableViewMaskLayer.locations = @[@0.0F, - @0.85F, - @1.0F]; - - tableViewMaskLayer.bounds = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); - tableViewMaskLayer.anchorPoint = CGPointZero; - - // CAConstraint is not supported on IOS. - // it seems only being supported on Mac OS. - // so viewDidLayoutSubviews will refresh the layout bounds. - [self.view.layer addSublayer:tableViewMaskLayer]; - - // Add + button - addParticipantButtonImageView = [[UIImageView alloc] init]; - [addParticipantButtonImageView setTranslatesAutoresizingMaskIntoConstraints:NO]; - [self.view addSubview:addParticipantButtonImageView]; - - addParticipantButtonImageView.backgroundColor = [UIColor clearColor]; - addParticipantButtonImageView.contentMode = UIViewContentModeCenter; - addParticipantButtonImageView.image = AssetImages.addGroupParticipant.image; - - CGFloat side = 78.0f; - NSLayoutConstraint* widthConstraint = [NSLayoutConstraint constraintWithItem:addParticipantButtonImageView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1 - constant:side]; - - NSLayoutConstraint* heightConstraint = [NSLayoutConstraint constraintWithItem:addParticipantButtonImageView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1 - constant:side]; - - NSLayoutConstraint* centerXConstraint = [NSLayoutConstraint constraintWithItem:addParticipantButtonImageView - attribute:NSLayoutAttributeCenterX - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterX - multiplier:1 - constant:0]; - - addParticipantButtonImageViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.view - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:addParticipantButtonImageView - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:self.keyboardHeight + 9]; - - // Available on iOS 8 and later - [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, centerXConstraint, addParticipantButtonImageViewBottomConstraint]]; - - addParticipantButtonImageView.userInteractionEnabled = YES; - - // Handle tap gesture - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onAddParticipantButtonPressed)]; - [tap setNumberOfTouchesRequired:1]; - [tap setNumberOfTapsRequired:1]; - [tap setDelegate:self]; - [addParticipantButtonImageView addGestureRecognizer:tap]; -} - -- (void)onAddParticipantButtonPressed -{ - // Push the contacts picker. - ContactsTableViewController *contactsPickerViewController = [ContactsTableViewController contactsTableViewController]; - - // Set delegate to handle action on member (start chat, mention) - contactsPickerViewController.contactsTableViewControllerDelegate = self; - - // Prepare its data source - ContactsDataSource *contactsDataSource = [[ContactsDataSource alloc] initWithMatrixSession:self.mxSession]; - contactsDataSource.areSectionsShrinkable = YES; - contactsDataSource.displaySearchInputInContactsList = YES; - contactsDataSource.forceMatrixIdInDisplayName = YES; - // Add a plus icon to the contact cell in the contacts picker, in order to make it more understandable for the end user. - contactsDataSource.contactCellAccessoryImage = [AssetImages.plusIcon.image vc_tintedImageUsingColor:ThemeService.shared.theme.textPrimaryColor]; - - // List all the participants matrix user id to ignore them during the contacts search. - for (Contact *contact in actualParticipants) - { - contactsDataSource.ignoredContactsByMatrixId[contact.mxGroupUser.userId] = contact; - } - for (Contact *contact in invitedParticipants) - { - contactsDataSource.ignoredContactsByMatrixId[contact.mxGroupUser.userId] = contact; - } - - [contactsPickerViewController showSearch:YES]; - contactsPickerViewController.searchBar.placeholder = [VectorL10n groupParticipantsInviteAnotherUser]; - - // Apply the search pattern if any - if (currentSearchText) - { - contactsPickerViewController.searchBar.text = currentSearchText; - [contactsDataSource searchWithPattern:currentSearchText forceReset:YES]; - } - - [contactsPickerViewController displayList:contactsDataSource]; - - [self pushViewController:contactsPickerViewController]; -} - -- (void)refreshParticipantsList -{ - if (_group) - { - actualParticipants = [[NSMutableArray alloc] initWithCapacity:_group.users.chunk.count]; - for (MXGroupUser *groupUser in _group.users.chunk) - { - Contact *contact = [[Contact alloc] initMatrixContactWithDisplayName:groupUser.displayname andMatrixID:groupUser.userId]; - contact.mxGroupUser = groupUser; - - [actualParticipants addObject:contact]; - } - - invitedParticipants = [[NSMutableArray alloc] initWithCapacity:_group.invitedUsers.chunk.count]; - for (MXGroupUser *groupUser in _group.invitedUsers.chunk) - { - Contact *contact = [[Contact alloc] initMatrixContactWithDisplayName:groupUser.displayname andMatrixID:groupUser.userId]; - contact.mxGroupUser = groupUser; - - [invitedParticipants addObject:contact]; - } - } - else - { - actualParticipants = nil; - invitedParticipants = nil; - } - - [self finalizeParticipantsList]; -} - -- (void)finalizeParticipantsList -{ - // Sort group participants by power and then alphabetically. - NSComparator comparator = ^NSComparisonResult(Contact *userA, Contact *userB) { - - if (userA.mxGroupUser.isPrivileged && userB.mxGroupUser.isPrivileged) - { - return [userA.mxGroupUser.displayname compare:userB.mxGroupUser.displayname options:NSCaseInsensitiveSearch]; - } - if (userA.mxGroupUser.isPrivileged) - { - return NSOrderedAscending; - } - if (userB.mxGroupUser.isPrivileged) - { - return NSOrderedDescending; - } - - return [userA.mxGroupUser.displayname compare:userB.mxGroupUser.displayname options:NSCaseInsensitiveSearch]; - }; - - // Sort each participants list in alphabetical order - [actualParticipants sortUsingComparator:comparator]; - [invitedParticipants sortUsingComparator:comparator]; - - // Reload search result if any - if (currentSearchText.length) - { - NSString *searchText = currentSearchText; - currentSearchText = nil; - - [self searchBar:_searchBarView textDidChange:searchText]; - } - else - { - [self refreshTableView]; - } -} - -- (void)addPendingActionMask -{ - // Remove potential existing mask - [self removePendingActionMask]; - - // Add a spinner above the tableview to avoid that the user tap on any other button - pendingMaskSpinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; - pendingMaskSpinnerView.backgroundColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:0.5]; - pendingMaskSpinnerView.frame = self.tableView.frame; - pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin; - - // append it - [self.tableView.superview addSubview:pendingMaskSpinnerView]; - - // animate it - [pendingMaskSpinnerView startAnimating]; - - // Show the spinner after a delay so that if it is removed in a short future, - // it is not displayed to the end user. - pendingMaskSpinnerView.alpha = 0; - [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - - self->pendingMaskSpinnerView.alpha = 1; - - } completion:^(BOOL finished) { - }]; -} - -- (void)removePendingActionMask -{ - if (pendingMaskSpinnerView) - { - [pendingMaskSpinnerView removeFromSuperview]; - pendingMaskSpinnerView = nil; - } -} - -- (void)pushViewController:(UIViewController*)viewController -{ - // Keep ref on pushed view controller - pushedViewController = viewController; - - // Check whether the view controller is displayed inside a segmented one. - if (self.parentViewController.navigationController) - { - // Hide back button title - [self.parentViewController vc_removeBackTitle]; - - [self.parentViewController.navigationController pushViewController:viewController animated:YES]; - } - else - { - // Hide back button title - [self vc_removeBackTitle]; - - [self.navigationController pushViewController:viewController animated:YES]; - } -} - -- (void)releasePushedViewController -{ - if (pushedViewController) - { - if ([pushedViewController isKindOfClass:[UINavigationController class]]) - { - UINavigationController *navigationController = (UINavigationController*)pushedViewController; - for (id subViewController in navigationController.viewControllers) - { - if ([subViewController respondsToSelector:@selector(destroy)]) - { - [subViewController destroy]; - } - } - } - else if ([pushedViewController respondsToSelector:@selector(destroy)]) - { - [(id)pushedViewController destroy]; - } - - pushedViewController = nil; - } -} - -#pragma mark - UITableView data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - NSInteger count = 0; - - participantsSection = invitedSection = -1; - - if (currentSearchText.length) - { - if (filteredActualParticipants.count) - { - participantsSection = count++; - } - - if (filteredInvitedParticipants.count) - { - invitedSection = count++; - } - } - else - { - if (actualParticipants.count) - { - participantsSection = count++; - } - - if (invitedParticipants.count) - { - invitedSection = count++; - } - } - - return count; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - NSInteger count = 0; - - if (section == participantsSection) - { - if (currentSearchText.length) - { - count = filteredActualParticipants.count; - } - else - { - count = actualParticipants.count; - } - } - else if (section == invitedSection) - { - if (currentSearchText.length) - { - count = filteredInvitedParticipants.count; - } - else - { - count = invitedParticipants.count; - } - } - - return count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - UITableViewCell *cell; - - if (indexPath.section == participantsSection || indexPath.section == invitedSection) - { - ContactTableViewCell* participantCell = [tableView dequeueReusableCellWithIdentifier:@"ParticipantTableViewCellId" forIndexPath:indexPath]; - participantCell.selectionStyle = UITableViewCellSelectionStyleNone; - - Contact *contact; - NSArray *participants; - - if (indexPath.section == participantsSection) - { - if (currentSearchText.length) - { - participants = filteredActualParticipants; - } - else - { - participants = actualParticipants; - } - } - else - { - if (currentSearchText.length) - { - participants = filteredInvitedParticipants; - } - else - { - participants = invitedParticipants; - } - } - - if (indexPath.row < participants.count) - { - contact = participants[indexPath.row]; - } - - if (contact) - { - [participantCell render:contact]; - - NSString *powerLevelText; - - // Update power level label - if (contact.mxGroupUser.isPrivileged) - { - powerLevelText = [VectorL10n roomMemberPowerLevelShortAdmin]; - } - - participantCell.powerLevelLabel.text = powerLevelText; - } - - cell = participantCell; - } - else - { - // Return a fake cell to prevent app from crashing. - cell = [[UITableViewCell alloc] init]; - } - - return cell; -} - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == participantsSection || indexPath.section == invitedSection) - { - return YES; - } - return NO; -} - -- (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath -{ - // iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath). -} - -#pragma mark - UITableView delegate - -- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return tableView.estimatedRowHeight; -} - -- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section -{ - if (section == invitedSection) - { - return tableView.estimatedSectionHeaderHeight; - } - - return 0; -} - -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - cell.backgroundColor = ThemeService.shared.theme.backgroundColor; - - // Update the selected background view - if (ThemeService.shared.theme.selectedBackgroundColor) - { - cell.selectedBackgroundView = [[UIView alloc] init]; - cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; - } - else - { - if (tableView.style == UITableViewStylePlain) - { - cell.selectedBackgroundView = nil; - } - else - { - cell.selectedBackgroundView.backgroundColor = nil; - } - } - - // Refresh here the estimated row height - tableView.estimatedRowHeight = cell.frame.size.height; -} - -- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section -{ - // Refresh here the estimated header height - tableView.estimatedSectionHeaderHeight = view.frame.size.height; -} - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - MXKTableViewHeaderFooterWithLabel *sectionHeader; - - if (section == invitedSection) - { - sectionHeader = [tableView dequeueReusableHeaderFooterViewWithIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier]; - sectionHeader.mxkContentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - sectionHeader.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - sectionHeader.mxkLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; - - sectionHeader.mxkLabel.text = [VectorL10n groupParticipantsInvitedSection]; - } - - return sectionHeader; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - Contact *contact; - NSArray *participants; - - if (indexPath.section == participantsSection) - { - if (currentSearchText.length) - { - participants = filteredActualParticipants; - } - else - { - participants = actualParticipants; - } - } - else if (indexPath.section == invitedSection) - { - if (currentSearchText.length) - { - participants = filteredInvitedParticipants; - } - else - { - participants = invitedParticipants; - } - } - - if (indexPath.row < participants.count) - { - contact = participants[indexPath.row]; - } - - if (contact) - { - ContactDetailsViewController *contactDetailsViewController = [ContactDetailsViewController instantiate]; - contactDetailsViewController.enableVoipCall = NO; - contactDetailsViewController.contact = contact; - - [self pushViewController:contactDetailsViewController]; - } - - [tableView deselectRowAtIndexPath:indexPath animated:YES]; -} - -- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSMutableArray* actions; - - // add the swipe to delete only on participants sections - if (indexPath.section == participantsSection || indexPath.section == invitedSection) - { - actions = [[NSMutableArray alloc] init]; - - // Patch: Force the width of the button by adding whitespace characters into the title string. - UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ - - [self onDeleteAt:indexPath]; - - }]; - - leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon_blue" backgroundColor:ThemeService.shared.theme.headerBackgroundColor patternSize:CGSizeMake(74, 74) resourceSize:CGSizeMake(24, 24)]; - [actions insertObject:leaveAction atIndex:0]; - } - - return actions; -} - -#pragma mark - ContactsTableViewControllerDelegate - -- (void)contactsTableViewController:(ContactsTableViewController *)contactsTableViewController didSelectContact:(MXKContact*)contact -{ - [self didSelectInvitableContact:contact]; -} - -#pragma mark - Actions - -- (void)onDeleteAt:(NSIndexPath*)path -{ - if (path.section == participantsSection || path.section == invitedSection) - { - __weak typeof(self) weakSelf = self; - - if (currentAlert) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = nil; - } - - NSMutableArray *participants; - Contact *contact; - - if (path.section == participantsSection) - { - if (currentSearchText.length) - { - participants = filteredActualParticipants; - } - else - { - participants = actualParticipants; - } - } - else - { - if (currentSearchText.length) - { - participants = filteredInvitedParticipants; - } - else - { - participants = invitedParticipants; - } - } - - if (path.row < participants.count) - { - contact = participants[path.row]; - } - - if (contact && [contact.mxGroupUser.userId isEqualToString:self.mxSession.myUser.userId]) - { - // Leave this group? - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n groupParticipantsLeavePromptTitle] - message:[VectorL10n groupParticipantsLeavePromptMsg] - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n leave] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - [self addPendingActionMask]; - [self.mxSession leaveGroup:self->_group.groupId success:^{ - - [self withdrawViewControllerAnimated:YES completion:nil]; - - } failure:^(NSError *error) { - - [self removePendingActionMask]; - MXLogDebug(@"[GroupParticipantsVC] Leave group %@ failed", self->_group.groupId); - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"GroupParticipantsVCLeaveAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - } - else if (contact) - { - NSString *memberUserId = contact.mxGroupUser.userId; - - // Kick ? - NSString *promptMsg = [VectorL10n groupParticipantsRemovePromptMsg:(contact.mxGroupUser.displayname.length ? contact.mxGroupUser.displayname : memberUserId)]; - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n groupParticipantsRemovePromptTitle] - message:promptMsg - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n remove] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - MXLogDebug(@"[GroupParticipantsVC] Kick %@ failed", memberUserId); - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:[NSError errorWithDomain:@"GroupDomain" code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n notSupportedYet]}]]; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"GroupParticipantsVCKickAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - } - } -} - -#pragma mark - - -- (void)didSelectInvitableContact:(MXKContact*)contact -{ - __weak typeof(self) weakSelf = self; - - if (currentAlert) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = nil; - } - - // Invite ? - NSString *promptMsg = [VectorL10n groupParticipantsInvitePromptMsg:contact.displayName]; - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n groupParticipantsInvitePromptTitle] - message:promptMsg - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n invite] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - NSArray *identifiers = contact.matrixIdentifiers; - NSString *participantId; - - if (identifiers.count) - { - participantId = identifiers.firstObject; - - MXLogDebug(@"[GroupParticipantsVC] Invite %@ failed", participantId); - [[AppDelegate theDelegate] showErrorAsAlert:[NSError errorWithDomain:@"GroupDomain" code:0 userInfo:@{NSLocalizedDescriptionKey:[VectorL10n notSupportedYet]}]]; - } - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"GroupParticipantsVCInviteAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; -} - -#pragma mark - UISearchBar delegate - -- (void)refreshSearchBarItemsColor:(UISearchBar *)searchBar -{ - // bar tint color - searchBar.barTintColor = searchBar.tintColor = ThemeService.shared.theme.tintColor; - searchBar.tintColor = ThemeService.shared.theme.tintColor; - - // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. - - // text color - UITextField *searchBarTextField = searchBar.vc_searchTextField; - searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor; - - // Magnifying glass icon. - UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView; - leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - leftImageView.tintColor = ThemeService.shared.theme.tintColor; - - // remove the gray background color - UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"]; - UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"]; - effectBackgroundTop.hidden = YES; - effectBackgroundBottom.hidden = YES; - - // place holder - searchBarTextField.textColor = ThemeService.shared.theme.placeholderTextColor; -} - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText -{ - // Update search results. - NSUInteger index; - MXKContact *contact; - - searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - if (!currentSearchText.length || [searchText hasPrefix:currentSearchText] == NO) - { - // Copy participants and invited participants - filteredActualParticipants = [NSMutableArray arrayWithArray:actualParticipants]; - filteredInvitedParticipants = [NSMutableArray arrayWithArray:invitedParticipants]; - } - - currentSearchText = searchText; - - // Filter group participants - if (currentSearchText.length) - { - for (index = 0; index < filteredActualParticipants.count;) - { - contact = filteredActualParticipants[index]; - if (![contact matchedWithPatterns:@[currentSearchText]]) - { - [filteredActualParticipants removeObjectAtIndex:index]; - } - else - { - index++; - } - } - - for (index = 0; index < filteredInvitedParticipants.count;) - { - contact = filteredInvitedParticipants[index]; - if (![contact matchedWithPatterns:@[currentSearchText]]) - { - [filteredInvitedParticipants removeObjectAtIndex:index]; - } - else - { - index++; - } - } - } - else - { - filteredActualParticipants = nil; - filteredInvitedParticipants = nil; - } - - // Refresh display - [self refreshTableView]; -} - -- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar -{ - searchBar.showsCancelButton = YES; - - return YES; -} - -- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar -{ - searchBar.showsCancelButton = NO; - - return YES; -} - -- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar -{ - // "Done" key has been pressed. - - // Dismiss keyboard - [_searchBarView resignFirstResponder]; -} - -- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar -{ - if (currentSearchText) - { - currentSearchText = nil; - filteredActualParticipants = nil; - filteredInvitedParticipants = nil; - - [self refreshTableView]; - } - - searchBar.text = nil; - // Leave search - [searchBar resignFirstResponder]; -} - -@end diff --git a/Riot/Modules/Communities/Members/GroupParticipantsViewController.xib b/Riot/Modules/Communities/Members/GroupParticipantsViewController.xib deleted file mode 100644 index d9ecc8be3..000000000 --- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.xib +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.h b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.h deleted file mode 100644 index fe0a504a7..000000000 --- a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MatrixKit.h" - -/** - 'GroupRoomsViewController' instance is used to list the rooms of the group defined by the property 'mxGroup'. - When this property is nil, the view controller is empty. - */ -@interface GroupRoomsViewController : MXKViewController -{ -@protected - - /** - The current list of the rooms. - */ - NSArray *groupRooms; -} - -@property (weak, nonatomic) IBOutlet UITableView *tableView; -@property (weak, nonatomic) IBOutlet UIView *searchBarHeader; -@property (weak, nonatomic) IBOutlet UISearchBar *searchBarView; -@property (weak, nonatomic) IBOutlet UIView *searchBarHeaderBorder; - -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *searchBarTopConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *tableViewBottomConstraint; - -/** - A matrix group (nil by default). - */ -@property (strong, readonly, nonatomic) MXGroup *group; -@property (strong, readonly, nonatomic) MXSession *mxSession; - -/** - Returns the `UINib` object initialized for a `GroupRoomsViewController`. - - @return The initialized `UINib` object or `nil` if there were errors during initialization - or the nib file could not be located. - */ -+ (UINib *)nib; - -/** - Creates and returns a new `GroupRoomsViewController` object. - - @discussion This is the designated initializer for programmatic instantiation. - @return An initialized `GroupRoomsViewController` object if successful, `nil` otherwise. - */ -+ (instancetype)groupRoomsViewController; - -/** - Set the group for which the rooms are listed. - Provide the related matrix session. - */ -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession; - -@end - diff --git a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m deleted file mode 100644 index 4fa0a6cb4..000000000 --- a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m +++ /dev/null @@ -1,698 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupRoomsViewController.h" - -#import "GeneratedInterface-Swift.h" - -#import "GroupRoomTableViewCell.h" - -#import "RageShakeManager.h" - -@interface GroupRoomsViewController () -{ - // Search result - NSString *currentSearchText; - NSMutableArray *filteredGroupRooms; - - // The current pushed view controller - UIViewController *pushedViewController; - - // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; -} - -@end - -@implementation GroupRoomsViewController - -#pragma mark - Class methods - -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -+ (instancetype)groupRoomsViewController -{ - return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -#pragma mark - - -- (void)finalizeInit -{ - [super finalizeInit]; - - // Setup `MXKViewControllerHandling` properties - self.enableBarTintColorStatusChange = NO; - self.rageShakeManager = [RageShakeManager sharedManager]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - // Do any additional setup after loading the view, typically from a nib. - - // Check whether the view controller has been pushed via storyboard - if (!self.tableView) - { - // Instantiate view controller objects - [[[self class] nib] instantiateWithOwner:self options:nil]; - } - - // Adjust Top and Bottom constraints to take into account potential navBar and tabBar. - [NSLayoutConstraint deactivateConstraints:@[_searchBarTopConstraint, _tableViewBottomConstraint]]; - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated" - _searchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.searchBarHeader - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - - _tableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeBottom - multiplier:1.0f - constant:0.0f]; - #pragma clang diagnostic pop - - [NSLayoutConstraint activateConstraints:@[_searchBarTopConstraint, _tableViewBottomConstraint]]; - - _searchBarView.placeholder = [VectorL10n groupRoomsFilterRooms]; - _searchBarView.returnKeyType = UIReturnKeyDone; - _searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone; - - // Search bar header is hidden when no group is provided - _searchBarHeader.hidden = (self.group == nil); - - // Enable self-sizing cells and section headers. - self.tableView.rowHeight = UITableViewAutomaticDimension; - self.tableView.estimatedRowHeight = 74; - self.tableView.sectionHeaderHeight = 0; - - // Hide line separators of empty cells - self.tableView.tableFooterView = [[UIView alloc] init]; - - [self.tableView registerClass:GroupRoomTableViewCell.class forCellReuseIdentifier:@"RoomTableViewCellId"]; - - // Observe user interface theme change. - kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self userInterfaceThemeDidChange]; - - }]; - [self userInterfaceThemeDidChange]; -} - -- (void)userInterfaceThemeDidChange -{ - [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; - - self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; - - [self refreshSearchBarItemsColor:_searchBarView]; - - _searchBarHeaderBorder.backgroundColor = ThemeService.shared.theme.headerBorderColor; - - // Check the table view style to select its bg color. - self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); - self.view.backgroundColor = self.tableView.backgroundColor; - self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor; - - if (self.tableView.dataSource) - { - [self.tableView reloadData]; - } - - [self setNeedsStatusBarAppearanceUpdate]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle -{ - return ThemeService.shared.theme.statusBarStyle; -} - -// This method is called when the viewcontroller is added or removed from a container view controller. -- (void)didMoveToParentViewController:(nullable UIViewController *)parent -{ - [super didMoveToParentViewController:parent]; -} - -- (void)destroy -{ - // Release the potential pushed view controller - [self releasePushedViewController]; - - if (kThemeServiceDidChangeThemeNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; - kThemeServiceDidChangeThemeNotificationObserver = nil; - } - - _group = nil; - _mxSession = nil; - - filteredGroupRooms = nil; - - groupRooms = nil; - - // Note: all observers are removed during super call. - [super destroy]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Release the potential pushed view controller - [self releasePushedViewController]; - - if (_group) - { - // Restore the listeners on the group update. - [self registerOnGroupChangeNotifications]; - - // Check whether the selected group is stored in the user's session, or if it is a group preview. - // Replace the displayed group instance with the one stored in the session (if any). - MXGroup *storedGroup = [_mxSession groupWithGroupId:_group.groupId]; - BOOL isPreview = (!storedGroup); - - // Force refresh - [self refreshDisplayWithGroup:(isPreview ? _group : storedGroup)]; - - // Prepare a block called on successful update in case of a group preview. - // Indeed the group update notifications are triggered by the matrix session only for the user's groups. - void (^success)(void) = ^void(void) - { - [self refreshDisplayWithGroup:self->_group]; - }; - - // Trigger a refresh on the group rooms. - [self.mxSession updateGroupRooms:_group success:(isPreview ? success : nil) failure:^(NSError *error) { - - MXLogDebug(@"[GroupRoomsViewController] viewWillAppear: group rooms update failed %@", self->_group.groupId); - - }]; - } -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - [self cancelRegistrationOnGroupChangeNotifications]; - - // cancel any pending search - [self searchBarCancelButtonClicked:_searchBarView]; -} - -- (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion -{ - // Check whether the current view controller is displayed inside a segmented view controller in order to withdraw the right item - if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) - { - [((SegmentedViewController*)self.parentViewController) withdrawViewControllerAnimated:animated completion:completion]; - } - else - { - [super withdrawViewControllerAnimated:animated completion:completion]; - } -} - -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession -{ - // Cancel any pending search - [self searchBarCancelButtonClicked:_searchBarView]; - - if (_mxSession != mxSession) - { - [self cancelRegistrationOnGroupChangeNotifications]; - _mxSession = mxSession; - - [self registerOnGroupChangeNotifications]; - } - - [self addMatrixSession:mxSession]; - - [self refreshDisplayWithGroup:group]; -} - -#pragma mark - - -- (void)registerOnGroupChangeNotifications -{ - if (_mxSession) - { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroupRooms:) name:kMXSessionDidUpdateGroupRoomsNotification object:_mxSession]; - } -} - -- (void)cancelRegistrationOnGroupChangeNotifications -{ - // Remove any pending observers - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupRoomsNotification object:_mxSession]; -} - -- (void)didUpdateGroupRooms:(NSNotification *)notif -{ - MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey]; - if (group && [group.groupId isEqualToString:_group.groupId]) - { - // Update the current displayed group instance with the one stored in the session. - [self refreshDisplayWithGroup:group]; - } -} - -- (void)refreshDisplayWithGroup:(MXGroup *)group -{ - _group = group; - - if (_group) - { - _searchBarHeader.hidden = NO; - groupRooms = _group.rooms.chunk; - } - else - { - // Search bar header is hidden when no group is provided - _searchBarHeader.hidden = YES; - groupRooms = nil; - } - - // Reload search result if any - if (currentSearchText.length) - { - NSString *searchText = currentSearchText; - currentSearchText = nil; - - [self searchBar:_searchBarView textDidChange:searchText]; - } - else - { - [self refreshTableView]; - } -} - -- (void)startActivityIndicator -{ - // Check whether the current view controller is displayed inside a segmented view controller in order to run the right activity view - if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) - { - [((SegmentedViewController*)self.parentViewController) startActivityIndicator]; - - // Force stop the activity view of the view controller - [self.activityIndicator stopAnimating]; - } - else - { - [super startActivityIndicator]; - } -} - -- (void)stopActivityIndicator -{ - // Check whether the current view controller is displayed inside a segmented view controller in order to stop the right activity view - if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) - { - [((SegmentedViewController*)self.parentViewController) stopActivityIndicator]; - - // Force stop the activity view of the view controller - [self.activityIndicator stopAnimating]; - } - else - { - [super stopActivityIndicator]; - } -} - -#pragma mark - Internals - -- (void)refreshTableView -{ - [self.tableView reloadData]; -} - -- (void)pushViewController:(UIViewController*)viewController -{ - // Keep ref on pushed view controller - pushedViewController = viewController; - - // Check whether the view controller is displayed inside a segmented one. - if (self.parentViewController.navigationController) - { - // Hide back button title - [self.parentViewController vc_removeBackTitle]; - - [self.parentViewController.navigationController pushViewController:viewController animated:YES]; - } - else - { - // Hide back button title - [self vc_removeBackTitle]; - - [self.navigationController pushViewController:viewController animated:YES]; - } -} - -- (void)releasePushedViewController -{ - if (pushedViewController) - { - if ([pushedViewController isKindOfClass:[UINavigationController class]]) - { - UINavigationController *navigationController = (UINavigationController*)pushedViewController; - for (id subViewController in navigationController.viewControllers) - { - if ([subViewController respondsToSelector:@selector(destroy)]) - { - [subViewController destroy]; - } - } - } - else if ([pushedViewController respondsToSelector:@selector(destroy)]) - { - [(id)pushedViewController destroy]; - } - - pushedViewController = nil; - } -} - -#pragma mark - UITableView data source - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - NSInteger count = 0; - - if (currentSearchText.length) - { - if (filteredGroupRooms.count) - { - count++; - } - } - else - { - if (groupRooms.count) - { - count++; - } - } - - return count; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - NSInteger count = 0; - - if (currentSearchText.length) - { - count = filteredGroupRooms.count; - } - else - { - count = groupRooms.count; - } - - return count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - GroupRoomTableViewCell* roomCell = [tableView dequeueReusableCellWithIdentifier:@"RoomTableViewCellId" forIndexPath:indexPath]; - roomCell.selectionStyle = UITableViewCellSelectionStyleNone; - - MXGroupRoom *room; - NSArray *rooms; - - if (currentSearchText.length) - { - rooms = filteredGroupRooms; - } - else - { - rooms = groupRooms; - } - - if (indexPath.row < rooms.count) - { - room = rooms[indexPath.row]; - } - - if (room) - { - [roomCell render:room withMatrixSession:self.mxSession]; - } - - return roomCell; -} - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath -{ - return NO; -} - -- (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath -{ - // iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath). -} - -#pragma mark - UITableView delegate - -- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return tableView.estimatedRowHeight; -} - -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - cell.backgroundColor = ThemeService.shared.theme.backgroundColor; - - // Update the selected background view - if (ThemeService.shared.theme.selectedBackgroundColor) - { - cell.selectedBackgroundView = [[UIView alloc] init]; - cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; - } - else - { - if (tableView.style == UITableViewStylePlain) - { - cell.selectedBackgroundView = nil; - } - else - { - cell.selectedBackgroundView.backgroundColor = nil; - } - } - - // Refresh here the estimated row height - tableView.estimatedRowHeight = cell.frame.size.height; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - MXGroupRoom *room; - NSArray *rooms; - - if (currentSearchText.length) - { - rooms = filteredGroupRooms; - } - else - { - rooms = groupRooms; - } - - if (indexPath.row < rooms.count) - { - room = rooms[indexPath.row]; - } - - if (room) - { - // Check first if the user already joined this room. - if ([self.mxSession roomWithRoomId:room.roomId]) - { - // Open this room - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession]; - [roomDataSourceManager roomDataSourceForRoom:room.roomId create:YES onComplete:^(MXKRoomDataSource *roomDataSource) { - - RoomViewController *roomViewController = [RoomViewController roomViewController]; - roomViewController.showMissedDiscussionsBadge = NO; - [roomViewController displayRoom:roomDataSource]; - [self pushViewController:roomViewController]; - }]; - } - else - { - // Prepare a preview - RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithRoomId:room.roomId andSession:self.mxSession]; - [self startActivityIndicator]; - - // Try to get more information about the room before opening its preview - [roomPreviewData peekInRoom:^(BOOL succeeded) { - - [self stopActivityIndicator]; - - // If no data is available for this room, we name it with the known information (if any). - if (!succeeded) - { - roomPreviewData.roomName = (room.name.length ? room.name : room.canonicalAlias); - } - - // Display the room preview - RoomViewController *roomViewController = [RoomViewController roomViewController]; - roomViewController.showMissedDiscussionsBadge = NO; - [roomViewController displayRoomPreview:roomPreviewData]; - [self pushViewController:roomViewController]; - }]; - } - } - - [tableView deselectRowAtIndexPath:indexPath animated:YES]; -} - - -#pragma mark - UISearchBar delegate - -- (void)refreshSearchBarItemsColor:(UISearchBar *)searchBar -{ - // bar tint color - searchBar.barTintColor = searchBar.tintColor = ThemeService.shared.theme.tintColor; - searchBar.tintColor = ThemeService.shared.theme.tintColor; - - // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. - - // text color - UITextField *searchBarTextField = searchBar.vc_searchTextField; - searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor; - - // Magnifying glass icon. - UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView; - leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - leftImageView.tintColor = ThemeService.shared.theme.tintColor; - - // remove the gray background color - UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"]; - UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"]; - effectBackgroundTop.hidden = YES; - effectBackgroundBottom.hidden = YES; - - // place holder - searchBarTextField.textColor = ThemeService.shared.theme.placeholderTextColor; -} - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText -{ - // Update search results. - NSUInteger index; - MXGroupRoom *groupRoom; - - searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - if (!currentSearchText.length || [searchText hasPrefix:currentSearchText] == NO) - { - // Copy participants and invited participants - filteredGroupRooms = [NSMutableArray arrayWithArray:groupRooms]; - } - - currentSearchText = searchText; - - // Filter group participants - if (currentSearchText.length) - { - for (index = 0; index < filteredGroupRooms.count;) - { - groupRoom = filteredGroupRooms[index]; - - NSString *displayName = groupRoom.name; - if (!displayName) - { - displayName = groupRoom.canonicalAlias; - } - if (!displayName) - { - displayName = groupRoom.roomId; - } - - if ([displayName rangeOfString:currentSearchText options:NSCaseInsensitiveSearch].location == NSNotFound) - { - [filteredGroupRooms removeObjectAtIndex:index]; - } - else - { - index++; - } - } - } - else - { - filteredGroupRooms = nil; - } - - // Refresh display - [self refreshTableView]; -} - -- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar -{ - searchBar.showsCancelButton = YES; - - return YES; -} - -- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar -{ - searchBar.showsCancelButton = NO; - - return YES; -} - -- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar -{ - // "Done" key has been pressed. - - // Dismiss keyboard - [_searchBarView resignFirstResponder]; -} - -- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar -{ - if (currentSearchText) - { - currentSearchText = nil; - filteredGroupRooms = nil; - - [self refreshTableView]; - } - - searchBar.text = nil; - // Leave search - [searchBar resignFirstResponder]; -} - -@end diff --git a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.xib b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.xib deleted file mode 100644 index 5e2d861bb..000000000 --- a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.xib +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h b/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h deleted file mode 100644 index e140b3585..000000000 --- a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MatrixKit.h" - -@interface GroupRoomTableViewCell : MXKTableViewCell - -@property (weak, nonatomic) IBOutlet UILabel *roomDisplayName; -@property (weak, nonatomic) IBOutlet UILabel *roomTopic; -@property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar; - -/** - Configure the cell in order to display a room of a group. - - @param groupRoom the room to render. - */ -- (void)render:(MXGroupRoom*)groupRoom withMatrixSession:(MXSession*)mxSession; - -@end diff --git a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.m b/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.m deleted file mode 100644 index 80d84a128..000000000 --- a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.m +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright 2017 Vector Creations Ltd - Copyright 2018 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - 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 "GroupRoomTableViewCell.h" - -#import "AvatarGenerator.h" - -#import "ThemeService.h" -#import "GeneratedInterface-Swift.h" - -@implementation GroupRoomTableViewCell - -- (void)awakeFromNib -{ - [super awakeFromNib]; - - // Round image view - [_roomAvatar.layer setCornerRadius:_roomAvatar.frame.size.width / 2]; - _roomAvatar.clipsToBounds = YES; -} - -- (void)customizeTableViewCellRendering -{ - [super customizeTableViewCellRendering]; - - self.roomDisplayName.textColor = ThemeService.shared.theme.textPrimaryColor; - self.roomTopic.textColor = ThemeService.shared.theme.textSecondaryColor; - - _roomAvatar.defaultBackgroundColor = [UIColor clearColor]; -} - -- (void)render:(MXGroupRoom *)groupRoom withMatrixSession:(MXSession*)mxSession -{ - // Set room display name - self.roomDisplayName.text = groupRoom.name; - if (!self.roomDisplayName.text) - { - self.roomDisplayName.text = groupRoom.canonicalAlias; - } - if (!self.roomDisplayName.text) - { - self.roomDisplayName.text = groupRoom.roomId; - } - - // Check whether this room has topic - if (groupRoom.topic) - { - _roomTopic.hidden = NO; - _roomTopic.text = [MXTools stripNewlineCharacters:groupRoom.topic]; - } - else - { - // Hide and fill the label with a fake description to harmonize the height of all the cells. - // This is a drawback of the self-sizing cell. - _roomTopic.hidden = YES; - _roomTopic.text = @"No topic"; - } - - // Set the avatar - UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:groupRoom.roomId withDisplayName:self.roomDisplayName.text]; - - if (groupRoom.avatarUrl) - { - _roomAvatar.enableInMemoryCache = YES; - - [_roomAvatar setImageURI:groupRoom.avatarUrl - withType:nil - andImageOrientation:UIImageOrientationUp - toFitViewSize:_roomAvatar.frame.size - withMethod:MXThumbnailingMethodCrop - previewImage:avatarImage - mediaManager:mxSession.mediaManager]; - } - else - { - _roomAvatar.image = avatarImage; - } - - _roomAvatar.contentMode = UIViewContentModeScaleAspectFill; -} - -@end diff --git a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.xib b/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.xib deleted file mode 100644 index 33dbfa4c6..000000000 --- a/Riot/Modules/Communities/Rooms/Views/GroupRoomTableViewCell.xib +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift b/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift deleted file mode 100644 index 976c188ef..000000000 --- a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinator.swift +++ /dev/null @@ -1,59 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh Communities GroupDetails -/* - Copyright 2021 New Vector Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import Foundation -import UIKit - -final class GroupDetailsCoordinator: GroupDetailsCoordinatorProtocol { - - // MARK: - Properties - - // MARK: Private - - private let parameters: GroupDetailsCoordinatorParameters - private let groupDetailsViewController: GroupDetailsViewController - - // MARK: Public - - // Must be used only internally - var childCoordinators: [Coordinator] = [] - - weak var delegate: GroupDetailsCoordinatorDelegate? - - // MARK: - Setup - - init(parameters: GroupDetailsCoordinatorParameters) { - self.parameters = parameters - let groupDetailsViewController: GroupDetailsViewController = GroupDetailsViewController.instantiate() - self.groupDetailsViewController = groupDetailsViewController - } - - deinit { - groupDetailsViewController.destroy() - } - - // MARK: - Public - - func start() { - self.groupDetailsViewController.setGroup(self.parameters.group, withMatrixSession: self.parameters.session) - } - - func toPresentable() -> UIViewController { - return self.groupDetailsViewController - } -} diff --git a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorParameters.swift b/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorParameters.swift deleted file mode 100644 index 8ff866d7a..000000000 --- a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorParameters.swift +++ /dev/null @@ -1,29 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh Communities GroupDetails -/* - 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 - -/// GroupDetailsCoordinator input parameters -struct GroupDetailsCoordinatorParameters { - - /// The Matrix session - let session: MXSession - - /// The group for which the details are displayed - let group: MXGroup -} diff --git a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorProtocol.swift b/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorProtocol.swift deleted file mode 100644 index fbb3f9ff8..000000000 --- a/Riot/Modules/Communities/TabDetail/GroupDetailsCoordinatorProtocol.swift +++ /dev/null @@ -1,28 +0,0 @@ -// File created from ScreenTemplate -// $ createScreen.sh Communities GroupDetails -/* - 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 - -protocol GroupDetailsCoordinatorDelegate: AnyObject { - func groupDetailsCoordinatorDidCancel(_ coordinator: GroupDetailsCoordinatorProtocol) -} - -/// `GroupDetailsCoordinatorProtocol` is a protocol describing a Coordinator that handle communities navigation flow. -protocol GroupDetailsCoordinatorProtocol: Coordinator, Presentable { - var delegate: GroupDetailsCoordinatorDelegate? { get } -} diff --git a/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.h b/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.h deleted file mode 100644 index faaab6b94..000000000 --- a/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "SegmentedViewController.h" - -@interface GroupDetailsViewController : SegmentedViewController - -@property (strong, readonly, nonatomic) MXGroup *group; -@property (strong, readonly, nonatomic) MXSession *mxSession; - -/** - Returns the `UINib` object initialized for a `GroupDetailsViewController`. - - @return The initialized `UINib` object or `nil` if there were errors during initialization - or the nib file could not be located. - */ -+ (UINib *)nib; - -/** - Creates and returns a new `GroupDetailsViewController` object. - - @discussion This is the designated initializer for programmatic instantiation. - @return An initialized `GroupDetailsViewController` object if successful, `nil` otherwise. - */ -+ (instancetype)instantiate; - -/** - Set the group for which the details are displayed. - Provide the related matrix session. - */ -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession; - -@end - diff --git a/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.m b/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.m deleted file mode 100644 index 339af8584..000000000 --- a/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.m +++ /dev/null @@ -1,181 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupDetailsViewController.h" - -#import "GroupHomeViewController.h" -#import "GroupParticipantsViewController.h" -#import "GroupRoomsViewController.h" - -#import "GeneratedInterface-Swift.h" - -@interface GroupDetailsViewController () -{ - GroupHomeViewController *groupHomeViewController; - GroupParticipantsViewController *groupParticipantsViewController; - GroupRoomsViewController *groupRoomsViewController; - - /** - mask view while processing a request - */ - UIActivityIndicatorView * pendingMaskSpinnerView; - - /** - Current alert (if any). - */ - UIAlertController *currentAlert; - - /** - The current visibility of the status bar in this view controller. - */ - BOOL isStatusBarHidden; -} -@end - -@implementation GroupDetailsViewController - -#pragma mark - Class methods - -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -+ (instancetype)instantiate -{ - return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -#pragma mark - - -- (void)finalizeInit -{ - [super finalizeInit]; - - // Setup `MXKViewControllerHandling` properties - self.enableBarTintColorStatusChange = NO; - self.rageShakeManager = [RageShakeManager sharedManager]; - - self.sectionHeaderTintColor = ThemeService.shared.theme.tintColor; - - // Keep visible the status bar by default. - isStatusBarHidden = NO; -} - -- (void)viewDidLoad -{ - NSMutableArray* viewControllers = [[NSMutableArray alloc] init]; - NSMutableArray* titles = [[NSMutableArray alloc] init]; - - // home tab - [titles addObject:[VectorL10n groupDetailsHome]]; - groupHomeViewController = [GroupHomeViewController groupHomeViewController]; - if (_group) - { - [groupHomeViewController setGroup:_group withMatrixSession:_mxSession]; - } - [viewControllers addObject:groupHomeViewController]; - - // People tab - [titles addObject:[VectorL10n groupDetailsPeople]]; - groupParticipantsViewController = [GroupParticipantsViewController groupParticipantsViewController]; - if (_group) - { - [groupParticipantsViewController setGroup:_group withMatrixSession:_mxSession]; - } - [viewControllers addObject:groupParticipantsViewController]; - - // Rooms tab - [titles addObject:[VectorL10n groupDetailsRooms]]; - groupRoomsViewController = [GroupRoomsViewController groupRoomsViewController]; - if (_group) - { - [groupRoomsViewController setGroup:_group withMatrixSession:_mxSession]; - } - [viewControllers addObject:groupRoomsViewController]; - - if (!self.title.length) - { - self.title = [VectorL10n groupDetailsTitle]; - } - - [self initWithTitles:titles viewControllers:viewControllers defaultSelected:0]; - - [super viewDidLoad]; - - // Display leftBarButtonItems or leftBarButtonItem to the right of the Back button - self.navigationItem.leftItemsSupplementBackButton = YES; -} - -- (UIStatusBarStyle)preferredStatusBarStyle -{ - return ThemeService.shared.theme.statusBarStyle; -} - -- (BOOL)prefersStatusBarHidden -{ - // Return the current status bar visibility. - return isStatusBarHidden; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; -} - -- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession -{ - _group = group; - _mxSession = mxSession; - - self.title = group.summary.profile.name.length ? group.summary.profile.name : group.groupId; - - [self addMatrixSession:mxSession]; - - if (groupHomeViewController) - { - [groupHomeViewController setGroup:group withMatrixSession:mxSession]; - } - if (groupParticipantsViewController) - { - [groupParticipantsViewController setGroup:group withMatrixSession:mxSession]; - } - if (groupRoomsViewController) - { - [groupRoomsViewController setGroup:group withMatrixSession:mxSession]; - } -} - -- (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion -{ - [super withdrawViewControllerAnimated:animated completion:completion]; - - // Fill the secondary navigation view controller of the split view controller if it is empty. - UINavigationController *secondaryNavigationController = [AppDelegate theDelegate].secondaryNavigationController; - if (secondaryNavigationController && !secondaryNavigationController.viewControllers.count) - { - [[AppDelegate theDelegate] restoreEmptyDetailsViewController]; - } -} - -@end diff --git a/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.xib b/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.xib deleted file mode 100644 index 189a88ca4..000000000 --- a/Riot/Modules/Communities/TabDetail/GroupDetailsViewController.xib +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.h b/Riot/Modules/Communities/Views/GroupInviteTableViewCell.h deleted file mode 100644 index b746d6ba1..000000000 --- a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupTableViewCell.h" - -/** - Action identifier used when the user pressed 'preview' button displayed on group invitation. - - The `userInfo` dictionary contains an `MXGroup` object under the `kGroupInviteTableViewCellRoomKey` key, representing the room of the invitation. - */ -extern NSString *const kGroupInviteTableViewCellPreviewButtonPressed; - -/** - Action identifier used when the user pressed 'decline' button displayed on group invitation. - - The `userInfo` dictionary contains an `MXGroup` object under the `kGroupInviteTableViewCellRoomKey` key, representing the room of the invitation. - */ -extern NSString *const kGroupInviteTableViewCellDeclineButtonPressed; - -/** - Notifications `userInfo` keys - */ -extern NSString *const kGroupInviteTableViewCellRoomKey; - -/** - `GroupInviteTableViewCell` instances display an invite to a group in the context of the groups list. - */ -@interface GroupInviteTableViewCell : GroupTableViewCell - -@property (weak, nonatomic) IBOutlet UIButton *leftButton; -@property (weak, nonatomic) IBOutlet UIButton *rightButton; - -@property (weak, nonatomic) IBOutlet UIView *noticeBadgeView; - - -@end diff --git a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.m b/Riot/Modules/Communities/Views/GroupInviteTableViewCell.m deleted file mode 100644 index 87b965266..000000000 --- a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.m +++ /dev/null @@ -1,95 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupInviteTableViewCell.h" - -#import "ThemeService.h" -#import "GeneratedInterface-Swift.h" - -#pragma mark - Constant definitions - -NSString *const kGroupInviteTableViewCellPreviewButtonPressed = @"kGroupInviteTableViewCellPreviewButtonPressed"; -NSString *const kGroupInviteTableViewCellDeclineButtonPressed = @"kGroupInviteTableViewCellDeclineButtonPressed"; - -NSString *const kGroupInviteTableViewCellRoomKey = @"kGroupInviteTableViewCellRoomKey"; - -@implementation GroupInviteTableViewCell - -#pragma mark - Class methods - -- (void)awakeFromNib -{ - [super awakeFromNib]; - - [self.leftButton.layer setCornerRadius:5]; - self.leftButton.clipsToBounds = YES; - [self.leftButton setTitle:[VectorL10n decline] forState:UIControlStateNormal]; - [self.leftButton setTitle:[VectorL10n decline] forState:UIControlStateHighlighted]; - [self.leftButton addTarget:self action:@selector(onDeclinePressed:) forControlEvents:UIControlEventTouchUpInside]; - - [self.rightButton.layer setCornerRadius:5]; - self.rightButton.clipsToBounds = YES; - [self.rightButton setTitle:[VectorL10n preview] forState:UIControlStateNormal]; - [self.rightButton setTitle:[VectorL10n preview] forState:UIControlStateHighlighted]; - [self.rightButton addTarget:self action:@selector(onPreviewPressed:) forControlEvents:UIControlEventTouchUpInside]; - - [self.noticeBadgeView.layer setCornerRadius:10]; - - self.selectionStyle = UITableViewCellSelectionStyleNone; -} - -- (void)customizeTableViewCellRendering -{ - [super customizeTableViewCellRendering]; - - self.leftButton.backgroundColor = ThemeService.shared.theme.tintColor; - self.rightButton.backgroundColor = ThemeService.shared.theme.tintColor; - - self.noticeBadgeView.backgroundColor = ThemeService.shared.theme.noticeColor; -} - -- (void)onDeclinePressed:(id)sender -{ - if (self.delegate) - { - MXGroup *group = groupCellData.group; - - if (group) - { - [self.delegate cell:self didRecognizeAction:kGroupInviteTableViewCellDeclineButtonPressed userInfo:@{kGroupInviteTableViewCellRoomKey:group}]; - } - } -} - -- (void)onPreviewPressed:(id)sender -{ - if (self.delegate) - { - MXGroup *group = groupCellData.group; - - if (group) - { - [self.delegate cell:self didRecognizeAction:kGroupInviteTableViewCellPreviewButtonPressed userInfo:@{kGroupInviteTableViewCellRoomKey:group}]; - } - } -} - -- (void)render:(MXKCellData *)cellData -{ - [super render:cellData]; -} - -@end diff --git a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.xib b/Riot/Modules/Communities/Views/GroupInviteTableViewCell.xib deleted file mode 100644 index 09629ff56..000000000 --- a/Riot/Modules/Communities/Views/GroupInviteTableViewCell.xib +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/Communities/Views/GroupTableViewCell.h b/Riot/Modules/Communities/Views/GroupTableViewCell.h deleted file mode 100644 index 61f8f6849..000000000 --- a/Riot/Modules/Communities/Views/GroupTableViewCell.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MatrixKit.h" - -/** - `GroupTableViewCell` instances display a group in the context of the groups list. - */ -@interface GroupTableViewCell : MXKGroupTableViewCell - -@property (weak, nonatomic) IBOutlet MXKImageView *groupAvatar; - -/** - The optional unread badge - */ -@property (weak, nonatomic) IBOutlet UILabel *missedNotifAndUnreadBadgeLabel; -@property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadBadgeBgView; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *missedNotifAndUnreadBadgeBgViewWidthConstraint; - -@end diff --git a/Riot/Modules/Communities/Views/GroupTableViewCell.m b/Riot/Modules/Communities/Views/GroupTableViewCell.m deleted file mode 100644 index 029a8cb0e..000000000 --- a/Riot/Modules/Communities/Views/GroupTableViewCell.m +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupTableViewCell.h" - -#import "ThemeService.h" -#import "GeneratedInterface-Swift.h" - -#import "MXGroup+Riot.h" - -@implementation GroupTableViewCell - -#pragma mark - Class methods - -- (void)awakeFromNib -{ - [super awakeFromNib]; - - if (self.missedNotifAndUnreadBadgeBgView) - { - // Initialize unread count badge - [_missedNotifAndUnreadBadgeBgView.layer setCornerRadius:10]; - _missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 0; - } -} - -- (void)customizeTableViewCellRendering -{ - [super customizeTableViewCellRendering]; - - self.groupName.textColor = ThemeService.shared.theme.textPrimaryColor; - self.groupDescription.textColor = ThemeService.shared.theme.textSecondaryColor; - self.memberCount.textColor = ThemeService.shared.theme.textSecondaryColor; - - if (self.missedNotifAndUnreadBadgeLabel) - { - self.missedNotifAndUnreadBadgeLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor; - } - - self.groupAvatar.defaultBackgroundColor = [UIColor clearColor]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - // Round image view - [_groupAvatar.layer setCornerRadius:_groupAvatar.frame.size.width / 2]; - _groupAvatar.clipsToBounds = YES; -} - -- (void)render:(MXKCellData *)cellData -{ - [super render:cellData]; - - if (self.missedNotifAndUnreadBadgeBgView) - { - // Hide by default missed notifications and unread widgets - self.missedNotifAndUnreadBadgeBgView.hidden = YES; - self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 0; - } - - if (groupCellData) - { - [groupCellData.group setGroupAvatarImageIn:self.groupAvatar matrixSession:groupCellData.groupsDataSource.mxSession]; - } -} - -// @TODO: Remove this method required by `MXKCellRendering` protocol. -// It is not used for the groups table view. -+ (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth -{ - // The height is fixed - // @TODO change this to support dynamic fonts - return 74; -} - -@end diff --git a/Riot/Modules/Communities/Views/GroupTableViewCell.xib b/Riot/Modules/Communities/Views/GroupTableViewCell.xib deleted file mode 100644 index 4443598b2..000000000 --- a/Riot/Modules/Communities/Views/GroupTableViewCell.xib +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.h deleted file mode 100644 index 020f78549..000000000 --- a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 - -#import "MXKViewController.h" -#import "MXKSessionGroupsDataSource.h" - -@class MXKGroupListViewController; - -/** - `MXKGroupListViewController` delegate. - */ -@protocol MXKGroupListViewControllerDelegate - -/** - Tells the delegate that the user selected a group. - - @param groupListViewController the `MXKGroupListViewController` instance. - @param group the selected group. - @param mxSession the matrix session in which the group is defined. - */ -- (void)groupListViewController:(MXKGroupListViewController *)groupListViewController didSelectGroup:(MXGroup*)group inMatrixSession:(MXSession*)mxSession; - -@end - - -/** - This view controller displays a group list. - */ -@interface MXKGroupListViewController : MXKViewController -{ -@protected - - /** - The fake top view displayed in case of vertical bounce. - */ - __weak UIView *topview; -} - -@property (weak, nonatomic) IBOutlet UISearchBar *groupsSearchBar; -@property (weak, nonatomic) IBOutlet UITableView *groupsTableView; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsSearchBarTopConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsSearchBarHeightConstraint; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsTableViewBottomConstraint; - -/** - The current data source associated to the view controller. - */ -@property (nonatomic, readonly) MXKSessionGroupsDataSource *dataSource; - -/** - The delegate for the view controller. - */ -@property (nonatomic, weak) id delegate; - -/** - Enable the search option by adding a navigation item in the navigation bar (YES by default). - Set NO this property to disable this option and hide the related bar button. - */ -@property (nonatomic) BOOL enableBarButtonSearch; - -#pragma mark - Class methods - -/** - Returns the `UINib` object initialized for a `MXKGroupListViewController`. - - @return The initialized `UINib` object or `nil` if there were errors during initialization - or the nib file could not be located. - - @discussion You may override this method to provide a customized nib. If you do, - you should also override `groupListViewController` to return your - view controller loaded from your custom nib. - */ -+ (UINib *)nib; - -/** - Creates and returns a new `MXKGroupListViewController` object. - - @discussion This is the designated initializer for programmatic instantiation. - @return An initialized `MXKGroupListViewController` object if successful, `nil` otherwise. - */ -+ (instancetype)groupListViewController; - -/** - Display the groups described in the provided data source. - - Note: The provided data source will replace the current data source if any. The caller - should dispose properly this data source if it is not used anymore. - - @param listDataSource the data source providing the groups list. - */ -- (void)displayList:(MXKSessionGroupsDataSource*)listDataSource; - -/** - Refresh the groups table display. - */ -- (void)refreshGroupsTable; - -/** - Hide/show the search bar at the top of the groups table view. - */ -- (void)hideSearchBar:(BOOL)hidden; - -@end diff --git a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.m deleted file mode 100644 index b23a55720..000000000 --- a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.m +++ /dev/null @@ -1,614 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MXKGroupListViewController.h" - -#import "MXKGroupTableViewCell.h" -#import "MXKTableViewHeaderFooterWithLabel.h" - -@interface MXKGroupListViewController () -{ - /** - The data source providing UITableViewCells - */ - MXKSessionGroupsDataSource *dataSource; - - /** - Search handling - */ - UIBarButtonItem *searchButton; - BOOL ignoreSearchRequest; - - /** - The reconnection animated view. - */ - UIView* reconnectingView; - - /** - The current table view header if any. - */ - UIView* tableViewHeaderView; - - /** - The latest server sync date - */ - NSDate* latestServerSync; - - /** - The restart the event connnection - */ - BOOL restartConnection; -} - -@end - -@implementation MXKGroupListViewController -@synthesize dataSource; - -#pragma mark - Class methods - -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass([MXKGroupListViewController class]) - bundle:[NSBundle bundleForClass:[MXKGroupListViewController class]]]; -} - -+ (instancetype)groupListViewController -{ - return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKGroupListViewController class]) - bundle:[NSBundle bundleForClass:[MXKGroupListViewController class]]]; -} - -#pragma mark - - -- (void)finalizeInit -{ - [super finalizeInit]; - - _enableBarButtonSearch = YES; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - // Check whether the view controller has been pushed via storyboard - if (!_groupsTableView) - { - // Instantiate view controller objects - [[[self class] nib] instantiateWithOwner:self options:nil]; - } - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated" - // Adjust search bar Top constraint to take into account potential navBar. - if (_groupsSearchBarTopConstraint) - { - [NSLayoutConstraint deactivateConstraints:@[_groupsSearchBarTopConstraint]]; - - _groupsSearchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.groupsSearchBar - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - - [NSLayoutConstraint activateConstraints:@[_groupsSearchBarTopConstraint]]; - } - - // Adjust table view Bottom constraint to take into account tabBar. - if (_groupsTableViewBottomConstraint) - { - [NSLayoutConstraint deactivateConstraints:@[_groupsTableViewBottomConstraint]]; - - _groupsTableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.groupsTableView - attribute:NSLayoutAttributeBottom - multiplier:1.0f - constant:0.0f]; - - [NSLayoutConstraint activateConstraints:@[_groupsTableViewBottomConstraint]]; - } - #pragma clang diagnostic pop - - // Hide search bar by default - [self hideSearchBar:YES]; - - // Apply search option in navigation bar - self.enableBarButtonSearch = _enableBarButtonSearch; - - // Add an accessory view to the search bar in order to retrieve keyboard view. - self.groupsSearchBar.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero]; - - // Finalize table view configuration - // Note: self-sizing cells and self-sizing section headers are enabled from the nib file. - self.groupsTableView.delegate = self; - self.groupsTableView.dataSource = dataSource; // Note: dataSource may be nil here - self.groupsTableView.estimatedSectionHeaderHeight = 30; // The value set in the nib seems not available for iOS version < 10. - - // Set up classes to use for the cells and the section headers. - [self.groupsTableView registerNib:MXKGroupTableViewCell.nib forCellReuseIdentifier:MXKGroupTableViewCell.defaultReuseIdentifier]; - [self.groupsTableView registerNib:MXKTableViewHeaderFooterWithLabel.nib forHeaderFooterViewReuseIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier]; - - // Add a top view which will be displayed in case of vertical bounce. - CGFloat height = self.groupsTableView.frame.size.height; - UIView *topview = [[UIView alloc] initWithFrame:CGRectMake(0,-height,self.groupsTableView.frame.size.width,height)]; - topview.autoresizingMask = UIViewAutoresizingFlexibleWidth; - topview.backgroundColor = [UIColor groupTableViewBackgroundColor]; - [self.groupsTableView addSubview:topview]; - self->topview = topview; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Restore search mechanism (if enabled) - ignoreSearchRequest = NO; - - // Observe the server sync - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncNotification) name:kMXSessionDidSyncNotification object:nil]; - - // Do a full reload - [self refreshGroupsTable]; - - // Refresh all groups summary - [self.dataSource refreshGroupsSummary:nil]; -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - // The user may still press search button whereas the view disappears - ignoreSearchRequest = YES; - - // Leave potential search session - if (!self.groupsSearchBar.isHidden) - { - [self searchBarCancelButtonClicked:self.groupsSearchBar]; - } - - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidSyncNotification object:nil]; - - [self removeReconnectingView]; -} - -- (void)dealloc -{ - self.groupsSearchBar.inputAccessoryView = nil; - - searchButton = nil; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - - // Dispose of any resources that can be recreated. -} - -#pragma mark - Override MXKViewController - -- (void)onKeyboardShowAnimationComplete -{ - // Report the keyboard view in order to track keyboard frame changes - self.keyboardView = _groupsSearchBar.inputAccessoryView.superview; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated" -- (void)setKeyboardHeight:(CGFloat)keyboardHeight -{ - // Deduce the bottom constraint for the table view (Don't forget the potential tabBar) - CGFloat tableViewBottomConst = keyboardHeight - self.bottomLayoutGuide.length; - // Check whether the keyboard is over the tabBar - if (tableViewBottomConst < 0) - { - tableViewBottomConst = 0; - } - - // Update constraints - _groupsTableViewBottomConstraint.constant = tableViewBottomConst; - - // Force layout immediately to take into account new constraint - [self.view layoutIfNeeded]; -} -#pragma clang diagnostic pop - -- (void)destroy -{ - self.groupsTableView.dataSource = nil; - self.groupsTableView.delegate = nil; - self.groupsTableView = nil; - - dataSource.delegate = nil; - dataSource = nil; - - _delegate = nil; - - [topview removeFromSuperview]; - topview = nil; - - [super destroy]; -} - -#pragma mark - - -- (void)setEnableBarButtonSearch:(BOOL)enableBarButtonSearch -{ - _enableBarButtonSearch = enableBarButtonSearch; - - if (enableBarButtonSearch) - { - if (!searchButton) - { - searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)]; - } - - // Add it in right bar items - NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems; - self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:searchButton] : @[searchButton]; - } - else - { - NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems]; - [rightBarButtonItems removeObject:searchButton]; - self.navigationItem.rightBarButtonItems = rightBarButtonItems; - } -} - -- (void)displayList:(MXKSessionGroupsDataSource *)listDataSource -{ - // Cancel registration on existing dataSource if any - if (dataSource) - { - dataSource.delegate = nil; - - // Remove associated matrix sessions - NSArray *mxSessions = self.mxSessions; - for (MXSession *mxSession in mxSessions) - { - [self removeMatrixSession:mxSession]; - } - } - - dataSource = listDataSource; - dataSource.delegate = self; - - // Report the matrix session at view controller level to update UI according to session state - [self addMatrixSession:listDataSource.mxSession]; - - if (self.groupsTableView) - { - // Set up table data source - self.groupsTableView.dataSource = dataSource; - } -} - -- (void)refreshGroupsTable -{ - // For now, do a simple full reload - [self.groupsTableView reloadData]; -} - -- (void)hideSearchBar:(BOOL)hidden -{ - self.groupsSearchBar.hidden = hidden; - self.groupsSearchBarHeightConstraint.constant = hidden ? 0 : 44; - [self.view setNeedsUpdateConstraints]; -} - -#pragma mark - Action - -- (IBAction)search:(id)sender -{ - // The user may have pressed search button whereas the view controller was disappearing - if (ignoreSearchRequest) - { - return; - } - - if (self.groupsSearchBar.isHidden) - { - // Check whether there are data in which search - if ([self.dataSource numberOfSectionsInTableView:self.groupsTableView]) - { - [self hideSearchBar:NO]; - - // Create search bar - [self.groupsSearchBar becomeFirstResponder]; - } - } - else - { - [self searchBarCancelButtonClicked: self.groupsSearchBar]; - } -} - -#pragma mark - MXKDataSourceDelegate - -- (Class)cellViewClassForCellData:(MXKCellData*)cellData -{ - // Return the default group table view cell - return MXKGroupTableViewCell.class; -} - -- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData -{ - // Return the default group table view cell - return MXKGroupTableViewCell.defaultReuseIdentifier; -} - -- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes -{ - // For now, do a simple full reload - [self refreshGroupsTable]; -} - -- (void)dataSource:(MXKDataSource *)dataSource didAddMatrixSession:(MXSession *)mxSession -{ - [self addMatrixSession:mxSession]; -} - -- (void)dataSource:(MXKDataSource *)dataSource didRemoveMatrixSession:(MXSession *)mxSession -{ - [self removeMatrixSession:mxSession]; -} - -#pragma mark - UITableView delegate - -- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return tableView.estimatedRowHeight; -} - -- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section -{ - if (tableView.numberOfSections > 1) - { - return tableView.estimatedSectionHeaderHeight; - } - - return 0; -} - -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - // Refresh here the estimated row height - tableView.estimatedRowHeight = cell.frame.size.height; -} - -- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section -{ - // Refresh here the estimated header height - tableView.estimatedSectionHeaderHeight = view.frame.size.height; -} - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - MXKTableViewHeaderFooterWithLabel *sectionHeader; - - if (tableView.numberOfSections > 1) - { - sectionHeader = [tableView dequeueReusableHeaderFooterViewWithIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier]; - - sectionHeader.mxkLabel.text = [self.dataSource tableView:tableView titleForHeaderInSection:section]; - } - - return sectionHeader; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (_delegate) - { - UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath]; - - if ([selectedCell conformsToProtocol:@protocol(MXKCellRendering)]) - { - id cell = (id)selectedCell; - - if ([cell respondsToSelector:@selector(renderedCellData)]) - { - MXKCellData *cellData = cell.renderedCellData; - if ([cellData conformsToProtocol:@protocol(MXKGroupCellDataStoring)]) - { - id groupCellData = (id)cellData; - [_delegate groupListViewController:self didSelectGroup:groupCellData.group inMatrixSession:self.mainSession]; - } - } - } - } - - // Hide the keyboard when user select a room - // do not hide the searchBar until the view controller disappear - // on tablets / iphone 6+, the user could expect to search again while looking at a room - [self.groupsSearchBar resignFirstResponder]; -} - -- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath -{ - // Release here resources, and restore reusable cells - if ([cell respondsToSelector:@selector(didEndDisplay)]) - { - [(id)cell didEndDisplay]; - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - // Detect vertical bounce at the top of the tableview to trigger reconnection. - if (scrollView == _groupsTableView) - { - [self detectPullToKick:scrollView]; - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - if (scrollView == _groupsTableView) - { - [self managePullToKick:scrollView]; - } -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - if (scrollView == _groupsTableView) - { - if (scrollView.contentOffset.y + scrollView.adjustedContentInset.top == 0) - { - [self managePullToKick:scrollView]; - } - } -} - -#pragma mark - UISearchBarDelegate - -- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText -{ - // Apply filter - if (searchText.length) - { - [self.dataSource searchWithPatterns:@[searchText]]; - } - else - { - [self.dataSource searchWithPatterns:nil]; - } -} - -- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar -{ - // "Done" key has been pressed - [searchBar resignFirstResponder]; -} - -- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar -{ - // Leave search - [searchBar resignFirstResponder]; - - [self hideSearchBar:YES]; - - self.groupsSearchBar.text = nil; - - // Refresh display - [self.dataSource searchWithPatterns:nil]; -} - -#pragma mark - resync management - -- (void)onSyncNotification -{ - latestServerSync = [NSDate date]; - - MXWeakify(self); - - // Refresh all groups summary - [self.dataSource refreshGroupsSummary:^{ - - MXStrongifyAndReturnIfNil(self); - - [self removeReconnectingView]; - }]; -} - -- (BOOL)canReconnect -{ - // avoid restarting connection if some data has been received within 1 second (1000 : latestServerSync is null) - NSTimeInterval interval = latestServerSync ? [[NSDate date] timeIntervalSinceDate:latestServerSync] : 1000; - return (interval > 1) && [self.mainSession reconnect]; -} - -- (void)addReconnectingView -{ - if (!reconnectingView) - { - UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; - spinner.transform = CGAffineTransformMakeScale(0.75f, 0.75f); - CGRect frame = spinner.frame; - frame.size.height = 80; // 80 * 0.75 = 60 - spinner.bounds = frame; - spinner.color = [UIColor darkGrayColor]; - spinner.hidesWhenStopped = NO; - spinner.backgroundColor = _groupsTableView.backgroundColor; - [spinner startAnimating]; - - // no need to manage constraints here, IOS defines them. - tableViewHeaderView = _groupsTableView.tableHeaderView; - _groupsTableView.tableHeaderView = reconnectingView = spinner; - } -} - -- (void)removeReconnectingView -{ - if (reconnectingView && !restartConnection) - { - _groupsTableView.tableHeaderView = tableViewHeaderView; - reconnectingView = nil; - } -} - -/** - Detect if the current connection must be restarted. - The spinner is displayed until the overscroll ends (and scrollViewDidEndDecelerating is called). - */ -- (void)detectPullToKick:(UIScrollView *)scrollView -{ - if (!reconnectingView) - { - // detect if the user scrolls over the tableview top - restartConnection = (scrollView.contentOffset.y + scrollView.adjustedContentInset.top < -128); - - if (restartConnection) - { - // wait that list decelerate to display / hide it - [self addReconnectingView]; - } - } -} - -/** - Restarts the current connection if it is required. - The 0.3s delay is added to avoid flickering if the connection does not require to be restarted. - */ -- (void)managePullToKick:(UIScrollView *)scrollView -{ - // the current connection must be restarted - if (restartConnection) - { - // display at least 0.3s the spinner to show to the user that something is pending - // else the UI is flickering - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - self->restartConnection = NO; - - if (![self canReconnect]) - { - // if the event stream has not been restarted - // hide the spinner - [self removeReconnectingView]; - } - // else wait that onSyncNotification is called. - }); - } -} - -@end diff --git a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.xib b/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.xib deleted file mode 100644 index fad906339..000000000 --- a/Riot/Modules/MatrixKit/Controllers/MXKGroupListViewController.xib +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.h b/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.h deleted file mode 100644 index 9ecd35b9c..000000000 --- a/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MXKGroupCellDataStoring.h" - -/** - `MXKGroupCellData` modelised the data for a `MXKGroupTableViewCell` cell. - */ -@interface MXKGroupCellData : MXKCellData - -@end diff --git a/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.m b/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.m deleted file mode 100644 index 9bb53db22..000000000 --- a/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellData.m +++ /dev/null @@ -1,49 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MXKGroupCellData.h" - -#import "MXKSessionGroupsDataSource.h" - -@implementation MXKGroupCellData -@synthesize group, groupsDataSource, groupDisplayname, sortingDisplayname; - -- (instancetype)initWithGroup:(MXGroup*)theGroup andGroupsDataSource:(MXKSessionGroupsDataSource*)theGroupsDataSource -{ - self = [self init]; - if (self) - { - groupsDataSource = theGroupsDataSource; - [self updateWithGroup:theGroup]; - } - return self; -} - -- (void)updateWithGroup:(MXGroup*)theGroup -{ - group = theGroup; - - groupDisplayname = sortingDisplayname = group.profile.name; - - if (!groupDisplayname.length) - { - groupDisplayname = group.groupId; - // Ignore the prefix '+' of the group id during sorting. - sortingDisplayname = [groupDisplayname substringFromIndex:1]; - } -} - -@end diff --git a/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellDataStoring.h b/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellDataStoring.h deleted file mode 100644 index 9ca2bd57b..000000000 --- a/Riot/Modules/MatrixKit/Models/Group/MXKGroupCellDataStoring.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 - -#import "MXKCellData.h" - -@class MXKSessionGroupsDataSource; - -/** - `MXKGroupCellDataStoring` defines a protocol a class must conform in order to store group cell data - managed by `MXKSessionGroupsDataSource`. - */ -@protocol MXKGroupCellDataStoring - -@property (nonatomic, weak, readonly) MXKSessionGroupsDataSource *groupsDataSource; - -@property (nonatomic, readonly) MXGroup *group; - -@property (nonatomic, readonly) NSString *groupDisplayname; -@property (nonatomic, readonly) NSString *sortingDisplayname; - -#pragma mark - Public methods -/** - Create a new `MXKCellData` object for a new group cell. - - @param group the `MXGroup` object that has data about the group. - @param groupsDataSource the `MXKSessionGroupsDataSource` object that will use this instance. - @return the newly created instance. - */ -- (instancetype)initWithGroup:(MXGroup*)group andGroupsDataSource:(MXKSessionGroupsDataSource*)groupsDataSource; - -/** - The `MXKSessionGroupsDataSource` object calls this method when the group data has been updated. - - @param group the updated group. - */ -- (void)updateWithGroup:(MXGroup*)group; - -@end diff --git a/Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.h b/Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.h deleted file mode 100644 index 7ee1ac3c0..000000000 --- a/Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MXKDataSource.h" -#import "MXKGroupCellData.h" - -/** - Identifier to use for cells that display a group. - */ -extern NSString *const kMXKGroupCellIdentifier; - -/** - 'MXKSessionGroupsDataSource' is a base class to handle the groups of a matrix session. - A 'MXKSessionGroupsDataSource' instance provides the data source for `MXKGroupListViewController`. - - A section is created to handle the invitations to a group, the first one if any. - */ -@interface MXKSessionGroupsDataSource : MXKDataSource -{ -@protected - - /** - The current list of the group invitations (sorted in the alphabetic order). - This list takes into account potential filter defined by`patternsList`. - */ - NSMutableArray *groupsInviteCellDataArray; - - /** - The current displayed list of the joined groups (sorted in the alphabetic order). - This list takes into account potential filter defined by`patternsList`. - */ - NSMutableArray *groupsCellDataArray; -} - -@property (nonatomic) NSInteger groupInvitesSection; -@property (nonatomic) NSInteger joinedGroupsSection; - -#pragma mark - Life cycle - -/** - Refresh all the groups summary. - The group data are not synced with the server, use this method to refresh them according to your needs. - - @param completion the block to execute when a request has been done for each group (whatever the result of the requests). - You may specify nil for this parameter. - */ -- (void)refreshGroupsSummary:(void (^)(void))completion; - -/** - Filter the current groups list according to the provided patterns. - When patterns are not empty, the search result is stored in `filteredGroupsCellDataArray`, - this array provides then data for the cells served by `MXKSessionGroupsDataSource`. - - @param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search. - */ -- (void)searchWithPatterns:(NSArray*)patternsList; - -/** - Get the data for the cell at the given index path. - - @param indexPath the index of the cell in the table - @return the cell data - */ -- (id)cellDataAtIndex:(NSIndexPath*)indexPath; - -/** - Get the index path of the cell related to the provided groupId. - - @param groupId the group identifier. - @return indexPath the index of the cell (nil if not found). - */ -- (NSIndexPath*)cellIndexPathWithGroupId:(NSString*)groupId; - -/** - Leave the group displayed at the provided path. - - @param indexPath the index of the group cell in the table - */ -- (void)leaveGroupAtIndexPath:(NSIndexPath *)indexPath; - -@end diff --git a/Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.m b/Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.m deleted file mode 100644 index d35ce9502..000000000 --- a/Riot/Modules/MatrixKit/Models/Group/MXKSessionGroupsDataSource.m +++ /dev/null @@ -1,611 +0,0 @@ -/* - Copyright 2017 Vector Creations Ltd - Copyright 2018 New Vector Ltd - Copyright 2019 The Matrix.org Foundation C.I.C - - 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 "MXKSessionGroupsDataSource.h" - -#import "NSBundle+MatrixKit.h" - -#import "MXKConstants.h" - -#import "MXKSwiftHeader.h" - -#pragma mark - Constant definitions -NSString *const kMXKGroupCellIdentifier = @"kMXKGroupCellIdentifier"; - - -@interface MXKSessionGroupsDataSource () -{ - /** - Internal array used to regulate change notifications. - Cell data changes are stored instantly in this array. - We wait at least for 500 ms between two notifications of the delegate. - */ - NSMutableArray *internalCellDataArray; - - /* - Timer to not notify the delegate on every changes. - */ - NSTimer *timer; - - /* - Tells whether some changes must be notified. - */ - BOOL isDataChangePending; - - /** - Store the current search patterns list. - */ - NSArray* searchPatternsList; -} - -@end - -@implementation MXKSessionGroupsDataSource - -- (instancetype)initWithMatrixSession:(MXSession *)matrixSession -{ - self = [super initWithMatrixSession:matrixSession]; - if (self) - { - internalCellDataArray = [NSMutableArray array]; - groupsCellDataArray = [NSMutableArray array]; - groupsInviteCellDataArray = [NSMutableArray array]; - - isDataChangePending = NO; - - // Set default data and view classes - [self registerCellDataClass:MXKGroupCellData.class forCellIdentifier:kMXKGroupCellIdentifier]; - } - return self; -} - -- (void)destroy -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - groupsCellDataArray = nil; - groupsInviteCellDataArray = nil; - internalCellDataArray = nil; - - searchPatternsList = nil; - - [timer invalidate]; - timer = nil; - - [super destroy]; -} - -- (void)didMXSessionStateChange -{ - if (MXSessionStateRunning <= self.mxSession.state) - { - // Check whether some data have been already load - if (0 == internalCellDataArray.count) - { - [self loadData]; - } - else if (self.mxSession.state == MXSessionStateRunning) - { - // Refresh the group data - [self refreshGroupsSummary:nil]; - } - } -} - -#pragma mark - - -- (void)refreshGroupsSummary:(void (^)(void))completion -{ - MXLogDebug(@"[MXKSessionGroupsDataSource] refreshGroupsSummary"); - - __block NSUInteger count = internalCellDataArray.count; - - if (count) - { - for (id groupData in internalCellDataArray) - { - // Force the matrix session to refresh the group summary. - [self.mxSession updateGroupSummary:groupData.group success:^{ - - if (completion && !(--count)) - { - // All the requests have been done. - completion (); - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[MXKSessionGroupsDataSource] refreshGroupsSummary: group summary update failed %@", groupData.group.groupId); - - if (completion && !(--count)) - { - // All the requests have been done. - completion (); - } - - }]; - } - } - else if (completion) - { - completion(); - } -} - -- (void)searchWithPatterns:(NSArray*)patternsList -{ - if (patternsList.count) - { - searchPatternsList = patternsList; - } - else - { - searchPatternsList = nil; - } - - [self onCellDataChange]; -} - -- (id)cellDataAtIndex:(NSIndexPath*)indexPath -{ - id groupData; - - if (indexPath.section == _groupInvitesSection) - { - if (indexPath.row < groupsInviteCellDataArray.count) - { - groupData = groupsInviteCellDataArray[indexPath.row]; - } - } - else if (indexPath.section == _joinedGroupsSection) - { - if (indexPath.row < groupsCellDataArray.count) - { - groupData = groupsCellDataArray[indexPath.row]; - } - } - - return groupData; -} - -- (NSIndexPath*)cellIndexPathWithGroupId:(NSString*)groupId -{ - // Look for the cell - if (_groupInvitesSection != -1) - { - for (NSInteger index = 0; index < groupsInviteCellDataArray.count; index ++) - { - id groupData = groupsInviteCellDataArray[index]; - if ([groupId isEqualToString:groupData.group.groupId]) - { - // Got it - return [NSIndexPath indexPathForRow:index inSection:_groupInvitesSection]; - } - } - } - - if (_joinedGroupsSection != -1) - { - for (NSInteger index = 0; index < groupsCellDataArray.count; index ++) - { - id groupData = groupsCellDataArray[index]; - if ([groupId isEqualToString:groupData.group.groupId]) - { - // Got it - return [NSIndexPath indexPathForRow:index inSection:_joinedGroupsSection]; - } - } - } - - return nil; -} - -#pragma mark - Groups processing - -- (void)loadData -{ - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewGroupInviteNotification object:self.mxSession]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidJoinGroupNotification object:self.mxSession]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveGroupNotification object:self.mxSession]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupSummaryNotification object:self.mxSession]; - - // Reset the table - [internalCellDataArray removeAllObjects]; - - // Retrieve the MXKCellData class to manage the data - Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier]; - NSAssert([class conformsToProtocol:@protocol(MXKGroupCellDataStoring)], @"MXKSessionGroupsDataSource only manages MXKCellData that conforms to MXKGroupCellDataStoring protocol"); - - // Listen to MXSession groups changes - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNewGroupInvite:) name:kMXSessionNewGroupInviteNotification object:self.mxSession]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didJoinGroup:) name:kMXSessionDidJoinGroupNotification object:self.mxSession]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didLeaveGroup:) name:kMXSessionDidLeaveGroupNotification object:self.mxSession]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroup:) name:kMXSessionDidUpdateGroupSummaryNotification object:self.mxSession]; - - NSDate *startDate = [NSDate date]; - - NSArray *groups = self.mxSession.groups; - for (MXGroup *group in groups) - { - id cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self]; - if (cellData) - { - [internalCellDataArray addObject:cellData]; - - // Force the matrix session to refresh the group summary. - [self.mxSession updateGroupSummary:group success:nil failure:^(NSError *error) { - MXLogDebug(@"[MXKSessionGroupsDataSource] loadData: group summary update failed %@", group.groupId); - }]; - } - } - - MXLogDebug(@"[MXKSessionGroupsDataSource] Loaded %tu groups in %.3fms", groups.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000); - - [self sortCellData]; - [self onCellDataChange]; -} - -- (void)didUpdateGroup:(NSNotification *)notif -{ - MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey]; - if (group) - { - id groupData = [self cellDataWithGroupId:group.groupId]; - if (groupData) - { - [groupData updateWithGroup:group]; - } - else - { - MXLogDebug(@"[MXKSessionGroupsDataSource] didUpdateGroup: Cannot find the changed group for %@ (%@). It is probably not managed by this group data source", group.groupId, group); - return; - } - } - - [self sortCellData]; - [self onCellDataChange]; -} - -- (void)onNewGroupInvite:(NSNotification *)notif -{ - MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey]; - if (group) - { - // Add the group if there is not yet a cell for it - id groupData = [self cellDataWithGroupId:group.groupId]; - if (nil == groupData) - { - MXLogDebug(@"MXKSessionGroupsDataSource] Add new group invite: %@", group.groupId); - - // Retrieve the MXKCellData class to manage the data - Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier]; - - id cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self]; - if (cellData) - { - [internalCellDataArray addObject:cellData]; - - [self sortCellData]; - [self onCellDataChange]; - } - } - } -} - -- (void)didJoinGroup:(NSNotification *)notif -{ - MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey]; - if (group) - { - id groupData = [self cellDataWithGroupId:group.groupId]; - if (groupData) - { - MXLogDebug(@"MXKSessionGroupsDataSource] Update joined room: %@", group.groupId); - [groupData updateWithGroup:group]; - } - else - { - MXLogDebug(@"MXKSessionGroupsDataSource] Add new joined invite: %@", group.groupId); - - // Retrieve the MXKCellData class to manage the data - Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier]; - - id cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self]; - if (cellData) - { - [internalCellDataArray addObject:cellData]; - } - } - - [self sortCellData]; - [self onCellDataChange]; - } -} - -- (void)didLeaveGroup:(NSNotification *)notif -{ - NSString *groupId = notif.userInfo[kMXSessionNotificationGroupIdKey]; - if (groupId) - { - [self removeGroup:groupId]; - } -} - -- (void)removeGroup:(NSString*)groupId -{ - id groupData = [self cellDataWithGroupId:groupId]; - if (groupData) - { - MXLogDebug(@"MXKSessionGroupsDataSource] Remove left group: %@", groupId); - - [internalCellDataArray removeObject:groupData]; - - [self sortCellData]; - [self onCellDataChange]; - } -} - -- (void)onCellDataChange -{ - isDataChangePending = NO; - - // Check no notification was done recently. - // Note: do not wait in case of search - if (timer == nil || searchPatternsList) - { - [timer invalidate]; - timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(checkPendingUpdate:) userInfo:nil repeats:NO]; - - // Prepare cell data array, and notify the delegate. - [self prepareCellDataAndNotifyChanges]; - } - else - { - isDataChangePending = YES; - } -} - -- (IBAction)checkPendingUpdate:(id)sender -{ - [timer invalidate]; - timer = nil; - - if (isDataChangePending) - { - [self onCellDataChange]; - } -} - -- (void)sortCellData -{ - // Order alphabetically the groups - [internalCellDataArray sortUsingComparator:^NSComparisonResult(id cellData1, id cellData2) - { - if (cellData1.sortingDisplayname.length && cellData2.sortingDisplayname.length) - { - return [cellData1.sortingDisplayname compare:cellData2.sortingDisplayname options:NSCaseInsensitiveSearch]; - } - else if (cellData1.sortingDisplayname.length) - { - return NSOrderedAscending; - } - else if (cellData2.sortingDisplayname.length) - { - return NSOrderedDescending; - } - return NSOrderedSame; - }]; -} - -- (void)prepareCellDataAndNotifyChanges -{ - // Prepare the cell data arrays by considering the potential filter. - [groupsInviteCellDataArray removeAllObjects]; - [groupsCellDataArray removeAllObjects]; - for (id groupData in internalCellDataArray) - { - BOOL isKept = !searchPatternsList; - - for (NSString* pattern in searchPatternsList) - { - if (groupData.groupDisplayname && [groupData.groupDisplayname rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound) - { - isKept = YES; - break; - } - } - - if (isKept) - { - if (groupData.group.membership == MXMembershipInvite) - { - [groupsInviteCellDataArray addObject:groupData]; - } - else - { - [groupsCellDataArray addObject:groupData]; - } - } - } - - // Update here data source state - if (state != MXKDataSourceStateReady) - { - state = MXKDataSourceStateReady; - if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)]) - { - [self.delegate dataSource:self didStateChange:state]; - } - } - - // And inform the delegate about the update - [self.delegate dataSource:self didCellChange:nil]; -} - -// Find the cell data that stores information about the given group id -- (id)cellDataWithGroupId:(NSString*)groupId -{ - id theGroupData; - for (id groupData in internalCellDataArray) - { - if ([groupData.group.groupId isEqualToString:groupId]) - { - theGroupData = groupData; - break; - } - } - return theGroupData; -} - -#pragma mark - UITableViewDataSource - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - NSInteger count = 0; - _groupInvitesSection = _joinedGroupsSection = -1; - - // Check whether all data sources are ready before rendering groups. - if (self.state == MXKDataSourceStateReady) - { - if (groupsInviteCellDataArray.count) - { - _groupInvitesSection = count++; - } - if (groupsCellDataArray.count) - { - _joinedGroupsSection = count++; - } - } - return count; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - if (section == _groupInvitesSection) - { - return groupsInviteCellDataArray.count; - } - else if (section == _joinedGroupsSection) - { - return groupsCellDataArray.count; - } - - return 0; -} - -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - NSString* sectionTitle = nil; - - if (section == _groupInvitesSection) - { - sectionTitle = [VectorL10n groupInviteSection]; - } - else if (section == _joinedGroupsSection) - { - sectionTitle = [VectorL10n groupSection]; - } - - return sectionTitle; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - id groupData; - - if (indexPath.section == _groupInvitesSection) - { - if (indexPath.row < groupsInviteCellDataArray.count) - { - groupData = groupsInviteCellDataArray[indexPath.row]; - } - } - else if (indexPath.section == _joinedGroupsSection) - { - if (indexPath.row < groupsCellDataArray.count) - { - groupData = groupsCellDataArray[indexPath.row]; - } - } - - if (groupData) - { - NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:groupData]; - if (cellIdentifier) - { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath]; - - // Make sure we listen to user actions on the cell - cell.delegate = self; - - // Make the bubble display the data - [cell render:groupData]; - - return cell; - } - } - - // Return a fake cell to prevent app from crashing. - return [[UITableViewCell alloc] init]; -} - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath -{ - // Return NO if you do not want the specified item to be editable. - return YES; -} - -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (editingStyle == UITableViewCellEditingStyleDelete) - { - [self leaveGroupAtIndexPath:indexPath]; - } -} - -- (void)leaveGroupAtIndexPath:(NSIndexPath *)indexPath -{ - id cellData = [self cellDataAtIndex:indexPath]; - - if (cellData.group) - { - __weak typeof(self) weakSelf = self; - - [self.mxSession leaveGroup:cellData.group.groupId success:^{ - - if (weakSelf) - { - // Refresh the table content - typeof(self) self = weakSelf; - [self removeGroup:cellData.group.groupId]; - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[MXKSessionGroupsDataSource] Failed to leave group (%@)", cellData.group.groupId); - - // Notify MatrixKit user - NSString *myUserId = self.mxSession.myUser.userId; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - - }]; - } -} - - -@end diff --git a/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.h b/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.h deleted file mode 100644 index 874023ebb..000000000 --- a/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MXKTableViewCell.h" - -#import "MXKCellRendering.h" - -#import "MXKGroupCellDataStoring.h" - -/** - `MXKGroupTableViewCell` instances display a group. - */ -@interface MXKGroupTableViewCell : MXKTableViewCell -{ -@protected - /** - The current cell data displayed by the table view cell - */ - id groupCellData; -} - -@property (weak, nonatomic) IBOutlet UILabel *groupName; -@property (weak, nonatomic) IBOutlet UILabel *groupDescription; -@property (weak, nonatomic) IBOutlet UILabel *memberCount; - -@end diff --git a/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.m b/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.m deleted file mode 100644 index 641f2c4ac..000000000 --- a/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.m +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "MXKGroupTableViewCell.h" - -#import "NSBundle+MatrixKit.h" - -#import "MXKSwiftHeader.h" - -@implementation MXKGroupTableViewCell -@synthesize delegate; - -#pragma mark - Class methods - -- (void)render:(MXKCellData *)cellData -{ - groupCellData = (id)cellData; - if (groupCellData) - { - // Render the current group values. - _groupName.text = groupCellData.groupDisplayname; - _groupDescription.text = groupCellData.group.profile.shortDescription; - - if (_groupDescription.text.length) - { - _groupDescription.hidden = NO; - } - else - { - // Hide and fill the label with a fake description to harmonize the height of all the cells. - // This is a drawback of the self-sizing cell. - _groupDescription.hidden = YES; - _groupDescription.text = @"No description"; - } - - if (_memberCount) - { - if (groupCellData.group.summary.usersSection.totalUserCountEstimate > 1) - { - _memberCount.text = [VectorL10n numMembersOther:@(groupCellData.group.summary.usersSection.totalUserCountEstimate).stringValue]; - } - else if (groupCellData.group.summary.usersSection.totalUserCountEstimate == 1) - { - _memberCount.text = [VectorL10n numMembersOne:@(1).stringValue]; - } - else - { - _memberCount.text = nil; - } - } - } - else - { - _groupName.text = nil; - _groupDescription.text = nil; - _memberCount.text = nil; - } -} - -- (MXKCellData*)renderedCellData -{ - return groupCellData; -} - -+ (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth -{ - // The height is fixed - //@TODO: change this to handle dynamic font - return 70; -} - -- (void)prepareForReuse -{ - [super prepareForReuse]; - - groupCellData = nil; -} - -@end diff --git a/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.xib b/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.xib deleted file mode 100644 index cf6efef01..000000000 --- a/Riot/Modules/MatrixKit/Views/Group/MXKGroupTableViewCell.xib +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.h b/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.h deleted file mode 100644 index d67440bf0..000000000 --- a/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupTableViewCell.h" - -/** - `GroupTableViewCellWithSwitch` instances display a group with a toggle button. - */ -@interface GroupTableViewCellWithSwitch : GroupTableViewCell - -@property (strong, nonatomic) IBOutlet UISwitch *toggleButton; - -@end diff --git a/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.m b/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.m deleted file mode 100644 index e29804a37..000000000 --- a/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.m +++ /dev/null @@ -1,21 +0,0 @@ -/* - Copyright 2017 Vector Creations 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 "GroupTableViewCellWithSwitch.h" - -@implementation GroupTableViewCellWithSwitch - -@end diff --git a/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.xib b/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.xib deleted file mode 100644 index cb141c781..000000000 --- a/Riot/Modules/Settings/Views/GroupTableViewCellWithSwitch.xib +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 85db4e22a07924242d93b4a948717e119531305d Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 5 Aug 2022 12:32:22 +0100 Subject: [PATCH 04/80] Fix build errors. --- Config/BuildSettings.swift | 1 - Riot/Assets/Base.lproj/Main.storyboard | 27 +--- Riot/Managers/Settings/RiotSettings.swift | 3 - Riot/Modules/Application/LegacyAppDelegate.m | 65 +--------- Riot/Modules/MatrixKit/MatrixKit.h | 5 - .../Modules/Settings/SettingsViewController.m | 116 +----------------- Riot/Modules/TabBar/MasterTabBarController.h | 23 +--- Riot/Modules/TabBar/MasterTabBarController.m | 40 ------ Riot/Modules/TabBar/TabBarCoordinator.swift | 36 ------ Riot/SupportingFiles/Riot-Bridging-Header.h | 1 - 10 files changed, 7 insertions(+), 310 deletions(-) diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 0e6209163..8e09edb10 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -265,7 +265,6 @@ final class BuildSettings: NSObject { static let homeScreenShowFavouritesTab: Bool = true static let homeScreenShowPeopleTab: Bool = true static let homeScreenShowRoomsTab: Bool = true - static let homeScreenShowCommunitiesTab: Bool = true // MARK: - General Settings Screen diff --git a/Riot/Assets/Base.lproj/Main.storyboard b/Riot/Assets/Base.lproj/Main.storyboard index c38d8d929..68fcecb67 100644 --- a/Riot/Assets/Base.lproj/Main.storyboard +++ b/Riot/Assets/Base.lproj/Main.storyboard @@ -171,7 +171,7 @@ - + @@ -257,7 +257,6 @@ - @@ -371,7 +370,7 @@ - + @@ -403,25 +402,6 @@ - - - - - - - - - - - - - - - - - - - @@ -506,7 +486,7 @@ - + @@ -532,7 +512,6 @@ - diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 548fad9c7..a0c53efe5 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -287,9 +287,6 @@ final class RiotSettings: NSObject { @UserDefault(key: "homeScreenShowRoomsTab", defaultValue: BuildSettings.homeScreenShowRoomsTab, storage: defaults) var homeScreenShowRoomsTab - @UserDefault(key: "homeScreenShowCommunitiesTab", defaultValue: BuildSettings.homeScreenShowCommunitiesTab, storage: defaults) - var homeScreenShowCommunitiesTab - // MARK: General Settings @UserDefault(key: "settingsScreenShowChangePassword", defaultValue: BuildSettings.settingsScreenShowChangePassword, storage: defaults) diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 90038c046..16abba457 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -1319,7 +1319,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni NSString *roomIdOrAlias; NSString *eventId; NSString *userId; - NSString *groupId; // Check permalink to room or event if ([pathParams[0] isEqualToString:@"room"] && pathParams.count >= 2) @@ -1330,11 +1329,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Is it a link to an event of a room? eventId = (pathParams.count >= 3) ? pathParams[2] : nil; } - else if ([pathParams[0] isEqualToString:@"group"] && pathParams.count >= 2) - { - // The link is the form of "/group/[groupId]" - groupId = pathParams[1]; - } else if (([pathParams[0] hasPrefix:@"#"] || [pathParams[0] hasPrefix:@"!"]) && pathParams.count >= 1) { // The link is the form of "/#/[roomIdOrAlias]" or "/#/[roomIdOrAlias]/[eventId]" @@ -1641,44 +1635,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni continueUserActivity = YES; } - else if (groupId) - { - // @FIXME: In case of multi-account, ask the user which one to use - MXKAccount* account = accountManager.activeAccounts.firstObject; - if (account) - { - MXGroup *group = [account.mxSession groupWithGroupId:groupId]; - - if (!group) - { - // Create a group instance to display its preview - group = [[MXGroup alloc] initWithGroupId:groupId]; - } - - // Display the group details - [self showGroup:group withMatrixSession:account.mxSession presentationParamters:presentationParameters]; - - continueUserActivity = YES; - } - else - { - // There is no account. The app will display the AuthenticationVC. - // Wait for a successful login - MXLogDebug(@"[AppDelegate] Universal link: The user is not logged in. Wait for a successful login"); - universalLinkFragmentPending = fragment; - - // Register an observer in order to handle new account - universalLinkWaitingObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidAddAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - // Check that 'fragment' has not been cancelled - if ([self->universalLinkFragmentPending isEqualToString:fragment]) - { - MXLogDebug(@"[AppDelegate] Universal link: The user is now logged in. Retry the link"); - [self handleUniversalLinkWithParameters:universalLinkParameters]; - } - }]; - } - } else { // Unknown command: Do nothing except coming back to the main screen @@ -3074,26 +3030,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni } } -#pragma mark - Matrix Groups handling - -- (void)showGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession presentationParamters:(ScreenPresentationParameters*)presentationParameters -{ - void(^showGroup)(void) = ^{ - // Select group to display its details (dispatch this action in order to let TabBarController end its refresh) - [self.masterTabBarController selectGroup:group inMatrixSession:mxSession presentationParameters:presentationParameters]; - }; - - if (presentationParameters.restoreInitialDisplay) - { - [self restoreInitialDisplay:^{ - showGroup(); - }]; - } - else - { - showGroup(); - } -} +#pragma mark - VoIP - (void)promptForStunServerFallback { diff --git a/Riot/Modules/MatrixKit/MatrixKit.h b/Riot/Modules/MatrixKit/MatrixKit.h index d9cf57cf0..2bb02223b 100644 --- a/Riot/Modules/MatrixKit/MatrixKit.h +++ b/Riot/Modules/MatrixKit/MatrixKit.h @@ -146,9 +146,4 @@ #import "MXKCountryPickerViewController.h" #import "MXKLanguagePickerViewController.h" -#import "MXKGroupCellData.h" -#import "MXKSessionGroupsDataSource.h" -#import "MXKGroupListViewController.h" -#import "MXKGroupTableViewCell.h" - #import "MXKSlashCommands.h" diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 93d20bad4..fd6df1095 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -36,9 +36,6 @@ #import "ThemeService.h" #import "TableViewCellWithPhoneNumberTextField.h" -#import "GroupsDataSource.h" -#import "GroupTableViewCellWithSwitch.h" - #import "GBDeviceInfo_iOS.h" #import "MediaPickerViewController.h" @@ -69,7 +66,6 @@ typedef NS_ENUM(NSUInteger, SECTION_TAG) SECTION_TAG_ADVANCED, SECTION_TAG_ABOUT, SECTION_TAG_LABS, - SECTION_TAG_FLAIR, SECTION_TAG_DEACTIVATE_ACCOUNT }; @@ -188,7 +184,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(void); #pragma mark - SettingsViewController -@interface SettingsViewController () CountryPickerViewController *newPhoneNumberCountryPicker; NBPhoneNumber *newPhoneNumber; - // Flair: the groups data source - GroupsDataSource *groupsDataSource; - // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. __weak id kAppDelegateDidTapStatusBarNotificationObserver; @@ -600,19 +593,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } } - if ([groupsDataSource numberOfSectionsInTableView:self.tableView] && groupsDataSource.joinedGroupsSection != -1) - { - NSInteger count = [groupsDataSource tableView:self.tableView - numberOfRowsInSection:groupsDataSource.joinedGroupsSection]; - Section *sectionFlair = [Section sectionWithTag:SECTION_TAG_FLAIR]; - for (NSInteger index = 0; index < count; index++) - { - [sectionFlair addRowWithTag:index]; - } - sectionFlair.headerTitle = [VectorL10n settingsFlair]; - [tmpSections addObject:sectionFlair]; - } - if (BuildSettings.settingsScreenAllowDeactivatingAccount) { Section *sectionDeactivate = [Section sectionWithTag:SECTION_TAG_DEACTIVATE_ACCOUNT]; @@ -637,7 +617,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate> [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; [self.tableView registerClass:MXKTableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; [self.tableView registerClass:TableViewCellWithPhoneNumberTextField.class forCellReuseIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier]]; - [self.tableView registerClass:GroupTableViewCellWithSwitch.class forCellReuseIdentifier:[GroupTableViewCellWithSwitch defaultReuseIdentifier]]; [self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]]; [self.tableView registerNib:SectionFooterView.nib forHeaderFooterViewReuseIdentifier:[SectionFooterView defaultReuseIdentifier]]; @@ -694,10 +673,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } [self setupDiscoverySection]; - - groupsDataSource = [[GroupsDataSource alloc] initWithMatrixSession:self.mainSession]; - [groupsDataSource finalizeInitialization]; - groupsDataSource.delegate = self; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(onSave:)]; self.navigationItem.rightBarButtonItem.accessibilityIdentifier=@"SettingsVCNavBarSaveButton"; @@ -753,13 +728,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate> - (void)destroy { - if (groupsDataSource) - { - groupsDataSource.delegate = nil; - [groupsDataSource destroy]; - groupsDataSource = nil; - } - // Release the potential pushed view controller [self releasePushedViewController]; @@ -2556,32 +2524,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate> cell = [self buildLiveLocationSharingCellForTableView:tableView atIndexPath:indexPath]; } } - else if (section == SECTION_TAG_FLAIR) - { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:groupsDataSource.joinedGroupsSection]; - cell = [groupsDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; - - if ([cell isKindOfClass:GroupTableViewCellWithSwitch.class]) - { - GroupTableViewCellWithSwitch* groupWithSwitchCell = (GroupTableViewCellWithSwitch*)cell; - id groupCellData = [groupsDataSource cellDataAtIndex:indexPath]; - - // Display the groupId in the description label, except if the group has no name - if (![groupWithSwitchCell.groupName.text isEqualToString:groupCellData.group.groupId]) - { - groupWithSwitchCell.groupDescription.hidden = NO; - groupWithSwitchCell.groupDescription.text = groupCellData.group.groupId; - } - - // Update the toogle button - groupWithSwitchCell.toggleButton.on = groupCellData.group.summary.user.isPublicised; - groupWithSwitchCell.toggleButton.enabled = YES; - groupWithSwitchCell.toggleButton.tag = row; - - [groupWithSwitchCell.toggleButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [groupWithSwitchCell.toggleButton addTarget:self action:@selector(toggleCommunityFlair:) forControlEvents:UIControlEventTouchUpInside]; - } - } else if (section == SECTION_TAG_SECURITY) { switch (row) @@ -3326,43 +3268,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate> RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome = sender.on; } -- (void)toggleCommunityFlair:(UISwitch *)sender -{ - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:sender.tag inSection:groupsDataSource.joinedGroupsSection]; - id groupCellData = [groupsDataSource cellDataAtIndex:indexPath]; - MXGroup *group = groupCellData.group; - - if (group) - { - [self startActivityIndicator]; - - __weak typeof(self) weakSelf = self; - - [self.mainSession updateGroupPublicity:group isPublicised:sender.isOn success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - } - - } failure:^(NSError *error) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - - // Come back to previous state button - [sender setOn:!sender.isOn animated:YES]; - - // Notify user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - } - }]; - } -} - - (void)toggleUseOnlyLatestUserAvatarAndName:(UISwitch *)sender { RiotSettings.shared.roomScreenUseOnlyLatestUserAvatarAndName = sender.isOn; @@ -4170,25 +4075,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } } -#pragma mark - MXKDataSourceDelegate - -- (Class)cellViewClassForCellData:(MXKCellData*)cellData -{ - // Return the class used to display a group with a toogle button - return GroupTableViewCellWithSwitch.class; -} - -- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData -{ - return GroupTableViewCellWithSwitch.defaultReuseIdentifier; -} - -- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes -{ - // Group data has been updated. Do a simple full reload - [self refreshSettings]; -} - #pragma mark - DeactivateAccountViewControllerDelegate - (void)deactivateAccountViewControllerDidDeactivateWithSuccess:(DeactivateAccountViewController *)deactivateAccountViewController diff --git a/Riot/Modules/TabBar/MasterTabBarController.h b/Riot/Modules/TabBar/MasterTabBarController.h index c6b732f2e..e948bb301 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.h +++ b/Riot/Modules/TabBar/MasterTabBarController.h @@ -22,21 +22,18 @@ #import "FavouritesViewController.h" #import "PeopleViewController.h" #import "RoomsViewController.h" -#import "GroupsViewController.h" #define TABBAR_HOME_INDEX 0 #define TABBAR_FAVOURITES_INDEX 1 #define TABBAR_PEOPLE_INDEX 2 #define TABBAR_ROOMS_INDEX 3 -#define TABBAR_GROUPS_INDEX 4 -#define TABBAR_COUNT 5 +#define TABBAR_COUNT 4 typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { MasterTabBarIndexHome = TABBAR_HOME_INDEX, MasterTabBarIndexFavourites = TABBAR_FAVOURITES_INDEX, MasterTabBarIndexPeople = TABBAR_PEOPLE_INDEX, - MasterTabBarIndexRooms = TABBAR_ROOMS_INDEX, - MasterTabBarIndexGroups = TABBAR_GROUPS_INDEX + MasterTabBarIndexRooms = TABBAR_ROOMS_INDEX }; @protocol MasterTabBarControllerDelegate; @@ -88,16 +85,6 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { - (void)selectContact:(MXKContact*)contact withPresentationParameters:(ScreenPresentationParameters*)presentationParameters; -/** - Open a GroupDetailsViewController to display the information of the provided group. - - @param group Selected community. - @param matrixSession the matrix session in which the group should be available. - */ -- (void)selectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession; - -- (void)selectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession presentationParameters:(ScreenPresentationParameters*)presentationParameters; - /** Release the current selected item (if any). */ @@ -147,7 +134,6 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { @property (nonatomic, readonly) FavouritesViewController *favouritesViewController; @property (nonatomic, readonly) PeopleViewController *peopleViewController; @property (nonatomic, readonly) RoomsViewController *roomsViewController; -@property (nonatomic, readonly) GroupsViewController *groupsViewController; // References on the currently selected room @@ -159,10 +145,6 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { // References on the currently selected contact @property (nonatomic, readonly) MXKContact *selectedContact; -// References on the currently selected group -@property (nonatomic, readonly) MXGroup *selectedGroup; -@property (nonatomic, readonly) MXSession *selectedGroupSession; - // YES while the onboarding flow is displayed @property (nonatomic, readonly) BOOL isOnboardingInProgress; @@ -183,6 +165,5 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { - (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectRoomWithParameters:(RoomNavigationParameters*)roomNavigationParameters completion:(void (^)(void))completion; - (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectRoomPreviewWithParameters:(RoomPreviewNavigationParameters*)roomPreviewNavigationParameters completion:(void (^)(void))completion; - (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectContact:(MXKContact*)contact withPresentationParameters:(ScreenPresentationParameters*)presentationParameters; -- (void)masterTabBarController:(MasterTabBarController *)masterTabBarController didSelectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession presentationParameters:(ScreenPresentationParameters*)presentationParameters; @end diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index cb1546a02..6f8b338fc 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -18,7 +18,6 @@ #import "MasterTabBarController.h" #import "RecentsDataSource.h" -#import "GroupsDataSource.h" #import "MXRoom+Riot.h" @@ -46,9 +45,6 @@ // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; - // The groups data source - GroupsDataSource *groupsDataSource; - // Custom title view of the navigation bar MainTitleView *titleView; @@ -92,11 +88,6 @@ return (RoomsViewController*)[self viewControllerForClass:RoomsViewController.class]; } -- (GroupsViewController *)groupsViewController -{ - return (GroupsViewController*)[self viewControllerForClass:GroupsViewController.class]; -} - #pragma mark - Life cycle - (void)viewDidLoad @@ -357,11 +348,6 @@ } [recentsDataSource setDelegate:recentsDataSourceDelegate andRecentsDataSourceMode:recentsDataSourceMode]; - // Init the recents data source - groupsDataSource = [[GroupsDataSource alloc] initWithMatrixSession:mainSession]; - [groupsDataSource finalizeInitialization]; - [self.groupsViewController displayList:groupsDataSource]; - // Check whether there are others sessions NSArray* mxSessions = self.mxSessions; if (mxSessions.count > 1) @@ -418,8 +404,6 @@ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMatrixSessionStateDidChange:) name:kMXSessionStateDidChangeNotification object:nil]; } [mxSessionArray addObject:mxSession]; - - // @TODO: handle multi sessions for groups } - (void)removeMatrixSession:(MXSession *)mxSession @@ -448,8 +432,6 @@ } [mxSessionArray removeObject:mxSession]; - - // @TODO: handle multi sessions for groups } - (void)onMatrixSessionStateDidChange:(NSNotification *)notif @@ -566,25 +548,6 @@ [self refreshSelectedControllerSelectedCellIfNeeded]; } -- (void)selectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession -{ - ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:YES stackAboveVisibleViews:NO]; - - [self selectGroup:group inMatrixSession:matrixSession presentationParameters:presentationParameters]; -} - -- (void)selectGroup:(MXGroup*)group inMatrixSession:(MXSession*)matrixSession presentationParameters:(ScreenPresentationParameters*)presentationParameters -{ - [self releaseSelectedItem]; - - _selectedGroup = group; - _selectedGroupSession = matrixSession; - - [self.masterTabBarDelegate masterTabBarController:self didSelectGroup:group inMatrixSession:matrixSession presentationParameters:presentationParameters]; - - [self refreshSelectedControllerSelectedCellIfNeeded]; -} - - (void)releaseSelectedItem { _selectedRoomId = nil; @@ -593,9 +556,6 @@ _selectedRoomPreviewData = nil; _selectedContact = nil; - - _selectedGroup = nil; - _selectedGroupSession = nil; } - (NSUInteger)missedDiscussionsCount diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 615540040..221986e14 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -332,13 +332,6 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { return roomsViewController } - private func createGroupsViewController() -> GroupsViewController { - let groupsViewController: GroupsViewController = GroupsViewController.instantiate() - groupsViewController.tabBarItem.tag = Int(TABBAR_GROUPS_INDEX) - groupsViewController.accessibilityLabel = VectorL10n.titleGroups - return groupsViewController - } - private func createUnifiedSearchController() -> UnifiedSearchViewController { let viewController: UnifiedSearchViewController = UnifiedSearchViewController.instantiate() @@ -392,11 +385,6 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { let roomsViewController = self.createRoomsViewController() viewControllers.append(roomsViewController) } - - if RiotSettings.shared.homeScreenShowCommunitiesTab && !(self.currentMatrixSession?.groups().isEmpty ?? false) && showCommunities { - let groupsViewController = self.createGroupsViewController() - viewControllers.append(groupsViewController) - } } tabBarController.updateViewControllers(viewControllers) @@ -449,18 +437,6 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } } - // FIXME: Should be displayed from a tab. - private func showGroupDetails(with group: MXGroup, for matrixSession: MXSession, presentationParameters: ScreenPresentationParameters) { - let coordinatorParameters = GroupDetailsCoordinatorParameters(session: matrixSession, group: group) - let coordinator = GroupDetailsCoordinator(parameters: coordinatorParameters) - coordinator.start() - self.add(childCoordinator: coordinator) - - self.showSplitViewDetails(with: coordinator, stackedOnSplitViewDetail: presentationParameters.stackAboveVisibleViews) { [weak self] in - self?.remove(childCoordinator: coordinator) - } - } - private func showRoom(withId roomId: String, eventId: String? = nil) { guard let matrixSession = self.parameters.userSessionsService.mainUserSession?.matrixSession else { @@ -690,10 +666,6 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } self.addMatrixSessionToMasterTabBarController(userSession.matrixSession) - - if let matrixSession = self.currentMatrixSession, matrixSession.groups().isEmpty { - self.masterTabBarController.removeTab(at: .groups) - } } @objc private func userSessionsServiceWillRemoveUserSession(_ notification: Notification) { @@ -721,10 +693,6 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } @objc private func sessionDidSync(_ notification: Notification) { - if self.currentMatrixSession?.groups().isEmpty ?? true { - self.masterTabBarController.removeTab(at: .groups) - } - if let session = notification.object as? MXSession { showCoachMessageIfNeeded(with: session) } @@ -968,10 +936,6 @@ extension TabBarCoordinator: MasterTabBarControllerDelegate { self.showRoom(with: roomId, eventId: eventId, matrixSession: matrixSession, completion: completion) } - func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelect group: MXGroup!, inMatrixSession matrixSession: MXSession!, presentationParameters: ScreenPresentationParameters!) { - self.showGroupDetails(with: group, for: matrixSession, presentationParameters: presentationParameters) - } - func masterTabBarController(_ masterTabBarController: MasterTabBarController!, needsSideMenuIconWithNotification displayNotification: Bool) { guard BuildSettings.enableSideMenu else { return diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index f3c41594e..7eacc6f91 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -41,7 +41,6 @@ #import "Tools.h" #import "RoomViewController.h" #import "ContactDetailsViewController.h" -#import "GroupDetailsViewController.h" #import "RoomInputToolbarView.h" #import "NSArray+Element.h" #import "ShareItemSender.h" From 57f92307f217deeed09606f3b730ea94836eea37 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 5 Aug 2022 15:09:56 +0100 Subject: [PATCH 05/80] Remove flairs and related groups. --- Config/BuildSettings.swift | 1 - Riot/Managers/Settings/RiotSettings.swift | 3 - Riot/Modules/Application/LegacyAppDelegate.m | 7 - .../Modules/MatrixKit/Models/MXKAppSettings.m | 1 - .../Models/Room/MXKRoomBubbleCellData.m | 71 +--- .../Room/MXKRoomBubbleCellDataStoring.h | 10 - .../MatrixKit/Models/Room/MXKRoomDataSource.h | 16 - .../MatrixKit/Models/Room/MXKRoomDataSource.m | 127 ------- .../Utils/EventFormatter/MXKEventFormatter.h | 1 - .../Utils/EventFormatter/MXKEventFormatter.m | 21 -- Riot/Modules/MatrixKit/Utils/MXKTools.h | 1 - Riot/Modules/MatrixKit/Utils/MXKTools.m | 8 - .../Modules/Room/DataSources/RoomDataSource.m | 1 - ...ditHistoryCoordinatorBridgePresenter.swift | 1 - Riot/Modules/Room/RoomViewController.m | 10 - .../Settings/RoomSettingsViewController.m | 335 ------------------ .../Common/MXKRoomBubbleTableViewCell.h | 5 - .../Common/MXKRoomBubbleTableViewCell.m | 97 +---- .../Sticker/RoomSelectedStickerBubbleCell.m | 10 +- changelog.d/6523.api | 1 + changelog.d/6523.change | 1 + 21 files changed, 5 insertions(+), 723 deletions(-) create mode 100644 changelog.d/6523.api create mode 100644 changelog.d/6523.change diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 8e09edb10..02f47cf22 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -347,7 +347,6 @@ final class BuildSettings: NSObject { static let roomSettingsScreenAllowChangingAccessSettings: Bool = true static let roomSettingsScreenAllowChangingHistorySettings: Bool = true static let roomSettingsScreenShowAddressSettings: Bool = true - static let roomSettingsScreenShowFlairSettings: Bool = true static let roomSettingsScreenShowAdvancedSettings: Bool = true static let roomSettingsScreenAdvancedShowEncryptToVerifiedOption: Bool = true diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index a0c53efe5..f1cc69ddb 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -339,9 +339,6 @@ final class RiotSettings: NSObject { @UserDefault(key: "roomSettingsScreenShowAddressSettings", defaultValue: BuildSettings.roomSettingsScreenShowAddressSettings, storage: defaults) var roomSettingsScreenShowAddressSettings - @UserDefault(key: "roomSettingsScreenShowFlairSettings", defaultValue: BuildSettings.roomSettingsScreenShowFlairSettings, storage: defaults) - var roomSettingsScreenShowFlairSettings - @UserDefault(key: "roomSettingsScreenShowAdvancedSettings", defaultValue: BuildSettings.roomSettingsScreenShowAdvancedSettings, storage: defaults) var roomSettingsScreenShowAdvancedSettings diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 16abba457..94cdd96c6 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -620,13 +620,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [MXSDKOptions.sharedInstance.profiler resume]; - // Force each session to refresh here their publicised groups by user dictionary. - // When these publicised groups are retrieved for a user, they are cached and reused until the app is backgrounded and enters in the foreground again - for (MXSession *session in mxSessionArray) - { - [session markOutdatedPublicisedGroupsByUserData]; - } - _isAppForeground = YES; } diff --git a/Riot/Modules/MatrixKit/Models/MXKAppSettings.m b/Riot/Modules/MatrixKit/Models/MXKAppSettings.m index 8d8e44b12..40cf2ebd3 100644 --- a/Riot/Modules/MatrixKit/Models/MXKAppSettings.m +++ b/Riot/Modules/MatrixKit/Models/MXKAppSettings.m @@ -169,7 +169,6 @@ static NSString *const kMXAppGroupID = @"group.org.matrix"; kMXEventTypeStringRoomMessageFeedback, kMXEventTypeStringRoomRedaction, kMXEventTypeStringRoomThirdPartyInvite, - kMXEventTypeStringRoomRelatedGroups, kMXEventTypeStringReaction, kMXEventTypeStringCallInvite, kMXEventTypeStringCallAnswer, diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m index b1e904b60..e42b9e6d2 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m @@ -29,7 +29,7 @@ #import "GeneratedInterface-Swift.h" @implementation MXKRoomBubbleCellData -@synthesize senderId, targetId, roomId, senderDisplayName, senderAvatarUrl, senderAvatarPlaceholder, targetDisplayName, targetAvatarUrl, targetAvatarPlaceholder, isEncryptedRoom, isPaginationFirstBubble, shouldHideSenderInformation, date, isIncoming, isAttachmentWithThumbnail, isAttachmentWithIcon, attachment, senderFlair; +@synthesize senderId, targetId, roomId, senderDisplayName, senderAvatarUrl, senderAvatarPlaceholder, targetDisplayName, targetAvatarUrl, targetAvatarPlaceholder, isEncryptedRoom, isPaginationFirstBubble, shouldHideSenderInformation, date, isIncoming, isAttachmentWithThumbnail, isAttachmentWithIcon, attachment; @synthesize textMessage, attributedTextMessage, attributedTextMessageWithoutPositioningSpace; @synthesize shouldHideSenderName, isTyping, showBubbleDateTime, showBubbleReceipts, useCustomDateTimeLabel, useCustomReceipts, useCustomUnsentButton, hasNoDisplay; @synthesize tag; @@ -122,9 +122,6 @@ - (void)dealloc { - // Reset any observer on publicised groups by user. - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; - roomDataSource = nil; bubbleComponents = nil; } @@ -450,58 +447,6 @@ - (void)setShouldHideSenderInformation:(BOOL)inShouldHideSenderInformation { shouldHideSenderInformation = inShouldHideSenderInformation; - - if (!shouldHideSenderInformation) - { - // Refresh the flair - [self refreshSenderFlair]; - } -} - -- (void)refreshSenderFlair -{ - // Reset by default any observer on publicised groups by user. - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; - - // Check first whether the room enabled the flair for some groups - NSArray *roomRelatedGroups = roomDataSource.roomState.relatedGroups; - if (roomRelatedGroups.count && senderId) - { - NSArray *senderPublicisedGroups; - - senderPublicisedGroups = [self.mxSession publicisedGroupsForUser:senderId]; - - if (senderPublicisedGroups.count) - { - // Cross the 2 arrays to keep only the common group ids - NSMutableArray *flair = [NSMutableArray arrayWithCapacity:roomRelatedGroups.count]; - - for (NSString *groupId in roomRelatedGroups) - { - if ([senderPublicisedGroups indexOfObject:groupId] != NSNotFound) - { - MXGroup *group = [roomDataSource groupWithGroupId:groupId]; - [flair addObject:group]; - } - } - - if (flair.count) - { - self.senderFlair = flair; - } - else - { - self.senderFlair = nil; - } - } - else - { - self.senderFlair = nil; - } - - // Observe any change on publicised groups for the message sender - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionUpdatePublicisedGroupsForUsers:) name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; - } } - (BOOL)hasThreadRoot @@ -1042,18 +987,4 @@ } } -- (void)didMXSessionUpdatePublicisedGroupsForUsers:(NSNotification *)notif -{ - // Retrieved the list of the concerned users - NSArray *userIds = notif.userInfo[kMXSessionNotificationUserIdsArrayKey]; - if (userIds.count && self.senderId) - { - // Check whether the current sender is concerned. - if ([userIds indexOfObject:self.senderId] != NSNotFound) - { - [self refreshSenderFlair]; - } - } -} - @end diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h index c9a3fb98f..ad845ec4e 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h @@ -91,11 +91,6 @@ */ @property (nonatomic) UIImage *targetAvatarPlaceholder; -/** - The current sender flair (list of the publicised groups in the sender profile which matches the room flair settings) - */ -@property (nonatomic) NSArray *senderFlair; - /** Tell whether the room is encrypted. */ @@ -304,11 +299,6 @@ Update the event because its sent state changed or it is has been redacted. foregroundColor:(UIColor*)foregroundColor andFont:(UIFont*)patternFont; -/** - Refresh the sender flair information - */ -- (void)refreshSenderFlair; - /** Indicate that the current text message layout is no longer valid and should be recomputed before presentation in a bubble cell. This could be due to the content changing, or the diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h index 1caf1315c..a5c542880 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h @@ -100,11 +100,6 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey; The queue of events that need to be processed in order to compute their display. */ NSMutableArray *eventsToProcess; - - /** - The dictionary of the related groups that the current user did not join. - */ - NSMutableDictionary *externalRelatedGroups; } /** @@ -777,17 +772,6 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey; */ - (void)collapseRoomBubble:(id)bubbleData collapsed:(BOOL)collapsed; -#pragma mark - Groups - -/** - Get a MXGroup instance for a group. - This method is used by the bubble to retrieve a related groups of the room. - - @param groupId The identifier to the group. - @return the MXGroup instance. - */ -- (MXGroup *)groupWithGroupId:(NSString*)groupId; - #pragma mark - Reactions /** diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m index dca87ded5..5872eee2d 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m @@ -87,11 +87,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { The listener to receipts events in the room. */ id receiptsListener; - - /** - The listener to the related groups state events in the room. - */ - id relatedGroupsListener; /** The listener to reactions changed in the room. @@ -318,8 +313,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { eventsToProcess = [NSMutableArray array]; eventIdToBubbleMap = [NSMutableDictionary dictionary]; - externalRelatedGroups = [NSMutableDictionary dictionary]; - _filterMessagesWithURL = NO; emoteMessageSlashCommandPrefix = [NSString stringWithFormat:@"%@ ", kMXKSlashCmdEmote]; @@ -471,8 +464,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { - (void)resetNotifying:(BOOL)notify { - [externalRelatedGroups removeAllObjects]; - if (roomDidFlushDataNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:roomDidFlushDataNotificationObserver]; @@ -515,9 +506,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { [_timeline removeListener:receiptsListener]; receiptsListener = nil; - - [_timeline removeListener:relatedGroupsListener]; - relatedGroupsListener = nil; } if (_secondaryRoom && secondaryLiveEventsListener) @@ -601,8 +589,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { [self unregisterScanManagerNotifications]; [self unregisterReactionsChangeListener]; [self unregisterEventEditsListener]; - - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXEventDidChangeSentStateNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXEventDidDecryptNotification object:nil]; @@ -638,8 +624,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { [_timeline destroy]; [_secondaryTimeline destroy]; - externalRelatedGroups = nil; - [super destroy]; } @@ -824,52 +808,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { [self setState:MXKDataSourceStateFailed]; } } - - if (_room && MXSessionStateRunning == self.mxSession.state) - { - // Flair handling: observe the update in the publicised groups by users when the flair is enabled in the room. - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; - [self.room state:^(MXRoomState *roomState) { - if (roomState.relatedGroups.count) - { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionUpdatePublicisedGroupsForUsers:) name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession]; - - // Get a fresh profile for all the related groups. Trigger a table refresh when all requests are done. - __block NSUInteger count = roomState.relatedGroups.count; - for (NSString *groupId in roomState.relatedGroups) - { - MXGroup *group = [self.mxSession groupWithGroupId:groupId]; - if (!group) - { - // Create a group instance for the groups that the current user did not join. - group = [[MXGroup alloc] initWithGroupId:groupId]; - [self->externalRelatedGroups setObject:group forKey:groupId]; - } - - // Refresh the group profile from server. - [self.mxSession updateGroupProfile:group success:^{ - - if (self.delegate && !(--count)) - { - // All the requests have been done. - [self.delegate dataSource:self didCellChange:nil]; - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[MXKRoomDataSource][%p] group profile update failed %@", self, groupId); - - if (self.delegate && !(--count)) - { - // All the requests have been done. - [self.delegate dataSource:self didCellChange:nil]; - } - - }]; - } - } - }]; - } } - (void)initializeTimelineForThread @@ -1008,7 +946,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { [_timeline removeListener:liveEventsListener]; [_timeline removeListener:redactionListener]; [_timeline removeListener:receiptsListener]; - [_timeline removeListener:relatedGroupsListener]; } // Listen to live events only for live timeline @@ -1071,16 +1008,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { [self didReceiveReceiptEvent:event roomState:roomState]; } }]; - - // Flair handling: register a listener for the related groups state event in this room. - relatedGroupsListener = [_timeline listenToEventsOfTypes:@[kMXEventTypeStringRoomRelatedGroups] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - if (MXTimelineDirectionForwards == direction) - { - // The flair settings have been updated: flush the current bubble data and rebuild them. - [self reload]; - } - }]; } // Register a listener to handle redaction which can affect live and past timelines @@ -2693,32 +2620,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { } } -- (void)didMXSessionUpdatePublicisedGroupsForUsers:(NSNotification *)notif -{ - // Retrieved the list of the concerned users - NSArray *userIds = notif.userInfo[kMXSessionNotificationUserIdsArrayKey]; - if (userIds.count) - { - // Check whether at least one listed user is a room member. - for (NSString* userId in userIds) - { - MXRoomMember * roomMember = [self.roomState.members memberWithUserId:userId]; - if (roomMember) - { - // Inform the delegate to refresh the bubble display - // We dispatch here this action in order to let each bubble data update their sender flair. - if (self.delegate) - { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.delegate dataSource:self didCellChange:nil]; - }); - } - break; - } - } - } -} - - (void)eventDidChangeSentState:(NSNotification *)notif { MXEvent *event = notif.object; @@ -3993,34 +3894,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { return cell; } -#pragma mark - Groups - -- (MXGroup *)groupWithGroupId:(NSString*)groupId -{ - MXGroup *group = [self.mxSession groupWithGroupId:groupId]; - if (!group) - { - // Check whether an instance has been already created. - group = [externalRelatedGroups objectForKey:groupId]; - } - - if (!group) - { - // Create a new group instance. - group = [[MXGroup alloc] initWithGroupId:groupId]; - [externalRelatedGroups setObject:group forKey:groupId]; - - // Retrieve at least the group profile - [self.mxSession updateGroupProfile:group success:nil failure:^(NSError *error) { - - MXLogDebug(@"[MXKRoomDataSource][%p] groupWithGroupId: group profile update failed %@", self, groupId); - - }]; - } - - return group; -} - #pragma mark - MXScanManager notifications - (void)registerScanManagerNotifications diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.h b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.h index 35c1508ef..776b79927 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.h +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.h @@ -99,7 +99,6 @@ typedef enum : NSUInteger { @property (nonatomic) BOOL treatMatrixRoomIdAsLink; @property (nonatomic) BOOL treatMatrixRoomAliasAsLink; @property (nonatomic) BOOL treatMatrixEventIdAsLink; -@property (nonatomic) BOOL treatMatrixGroupIdAsLink; /** Initialise the event formatter. diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index a4b7a8f3b..fa6495fd5 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -1022,21 +1022,6 @@ static NSString *const kHTMLATagRegexPattern = @"( } break; } - case MXEventTypeRoomRelatedGroups: - { - NSArray *groups; - MXJSONModelSetArray(groups, event.content[@"groups"]); - if (groups) - { - displayText = [VectorL10n noticeRoomRelatedGroups:[groups componentsJoinedByString:@", "]]; - // Append redacted info if any - if (redactedInfo) - { - displayText = [NSString stringWithFormat:@"%@\n %@", displayText, redactedInfo]; - } - } - break; - } case MXEventTypeRoomEncrypted: { // Is redacted? @@ -2081,12 +2066,6 @@ static NSString *const kHTMLATagRegexPattern = @"( { enabledMatrixIdsBitMask |= MXKTOOLS_EVENT_IDENTIFIER_BITWISE; } - - // If enabled, make group id clickable - if (_treatMatrixGroupIdAsLink) - { - enabledMatrixIdsBitMask |= MXKTOOLS_GROUP_IDENTIFIER_BITWISE; - } [MXKTools createLinksInMutableAttributedString:mutableAttributedString forEnabledMatrixIds:enabledMatrixIdsBitMask]; } diff --git a/Riot/Modules/MatrixKit/Utils/MXKTools.h b/Riot/Modules/MatrixKit/Utils/MXKTools.h index a94d390d7..f5326d405 100644 --- a/Riot/Modules/MatrixKit/Utils/MXKTools.h +++ b/Riot/Modules/MatrixKit/Utils/MXKTools.h @@ -25,7 +25,6 @@ #define MXKTOOLS_ROOM_IDENTIFIER_BITWISE 0x02 #define MXKTOOLS_ROOM_ALIAS_BITWISE 0x04 #define MXKTOOLS_EVENT_IDENTIFIER_BITWISE 0x08 -#define MXKTOOLS_GROUP_IDENTIFIER_BITWISE 0x10 // Attribute in an NSAttributeString that marks a blockquote block that was in the original HTML string. extern NSString *const kMXKToolsBlockquoteMarkAttribute; diff --git a/Riot/Modules/MatrixKit/Utils/MXKTools.m b/Riot/Modules/MatrixKit/Utils/MXKTools.m index 9f611867f..10cf491a6 100644 --- a/Riot/Modules/MatrixKit/Utils/MXKTools.m +++ b/Riot/Modules/MatrixKit/Utils/MXKTools.m @@ -42,7 +42,6 @@ static NSRegularExpression *userIdRegex; static NSRegularExpression *roomIdRegex; static NSRegularExpression *roomAliasRegex; static NSRegularExpression *eventIdRegex; -static NSRegularExpression *groupIdRegex; // A regex to find http URLs. static NSRegularExpression *httpLinksRegex; // A regex to find all HTML tags @@ -59,7 +58,6 @@ static NSRegularExpression *htmlTagsRegex; roomIdRegex = [NSRegularExpression regularExpressionWithPattern:kMXToolsRegexStringForMatrixRoomIdentifier options:NSRegularExpressionCaseInsensitive error:nil]; roomAliasRegex = [NSRegularExpression regularExpressionWithPattern:kMXToolsRegexStringForMatrixRoomAlias options:NSRegularExpressionCaseInsensitive error:nil]; eventIdRegex = [NSRegularExpression regularExpressionWithPattern:kMXToolsRegexStringForMatrixEventIdentifier options:NSRegularExpressionCaseInsensitive error:nil]; - groupIdRegex = [NSRegularExpression regularExpressionWithPattern:kMXToolsRegexStringForMatrixGroupIdentifier options:NSRegularExpressionCaseInsensitive error:nil]; httpLinksRegex = [NSRegularExpression regularExpressionWithPattern:@"(?i)\\b(https?://\\S*)\\b" options:NSRegularExpressionCaseInsensitive error:nil]; htmlTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\w+)[^>]*>" options:NSRegularExpressionCaseInsensitive error:nil]; @@ -1039,12 +1037,6 @@ manualChangeMessageForVideo:(NSString*)manualChangeMessageForVideo { [MXKTools createLinksInMutableAttributedString:mutableAttributedString matchingRegex:eventIdRegex]; } - - // If enabled, make group id clickable - if (enabledMatrixIdsBitMask & MXKTOOLS_GROUP_IDENTIFIER_BITWISE) - { - [MXKTools createLinksInMutableAttributedString:mutableAttributedString matchingRegex:groupIdRegex]; - } } + (void)createLinksInMutableAttributedString:(NSMutableAttributedString*)mutableAttributedString matchingRegex:(NSRegularExpression*)regex diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index a9b809cc1..958403acc 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -162,7 +162,6 @@ const CGFloat kTypingCellHeight = 24; self.eventFormatter.treatMatrixUserIdAsLink = YES; self.eventFormatter.treatMatrixRoomIdAsLink = YES; self.eventFormatter.treatMatrixRoomAliasAsLink = YES; - self.eventFormatter.treatMatrixGroupIdAsLink = YES; // Apply the event types filter to display only the wanted event types. self.eventFormatter.eventTypesFilterForMessages = [MXKAppSettings standardAppSettings].eventsFilterForMessages; diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift index b908e5cb0..341357304 100644 --- a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift @@ -98,7 +98,6 @@ final class EditHistoryCoordinatorBridgePresenter: NSObject { formatter.treatMatrixUserIdAsLink = true formatter.treatMatrixRoomIdAsLink = true formatter.treatMatrixRoomAliasAsLink = true - formatter.treatMatrixGroupIdAsLink = true formatter.eventTypesFilterForMessages = MXKAppSettings.standard()?.eventsFilterForMessages // But do not display "...(Edited)" diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index abfe72807..99eff65aa 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -4285,16 +4285,6 @@ static CGSize kThreadListBarButtonItemImageSize; [self handleUniversalLinkURL:permalinkURL]; } - // Preview the clicked group - else if ([MXTools isMatrixGroupIdentifier:absoluteURLString]) - { - shouldDoAction = NO; - - // Open the group or preview it - NSString *fragment = [NSString stringWithFormat:@"/group/%@", [MXTools encodeURIComponent:absoluteURLString]]; - - [self handleUniversalLinkFragment:fragment fromURL:url]; - } else if ([absoluteURLString hasPrefix:EventFormatterOnReRequestKeysLinkAction]) { NSArray *arguments = [absoluteURLString componentsSeparatedByString:EventFormatterLinkActionSeparator]; diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index d0c08b588..e25f706e3 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -41,7 +41,6 @@ enum SECTION_TAG_PROMOTION, SECTION_TAG_HISTORY, SECTION_TAG_ADDRESSES, - SECTION_TAG_FLAIR, SECTION_TAG_BANNED_USERS, SECTION_TAG_BANNED_ADVANCED }; @@ -77,12 +76,6 @@ enum ROOM_SETTINGS_HISTORY_VISIBILITY_SECTION_ROW_MEMBERS_ONLY_SINCE_JOINED }; -enum -{ - ROOM_SETTINGS_RELATED_GROUPS_NEW_GROUP, - ROOM_SETTINGS_RELATED_GROUPS_OFFSET = 1000 -}; - enum { ROOM_SETTINGS_ADVANCED_ROOM_ID, @@ -115,8 +108,6 @@ NSString *const kRoomSettingsHistoryVisibilityKey = @"kRoomSettingsHistoryVisibi NSString *const kRoomSettingsNewAliasesKey = @"kRoomSettingsNewAliasesKey"; NSString *const kRoomSettingsRemovedAliasesKey = @"kRoomSettingsRemovedAliasesKey"; NSString *const kRoomSettingsCanonicalAliasKey = @"kRoomSettingsCanonicalAliasKey"; -NSString *const kRoomSettingsNewRelatedGroupKey = @"kRoomSettingsNewRelatedGroupKey"; -NSString *const kRoomSettingsRemovedRelatedGroupKey = @"kRoomSettingsRemovedRelatedGroupKey"; NSString *const kRoomSettingsEncryptionKey = @"kRoomSettingsEncryptionKey"; NSString *const kRoomSettingsEncryptionBlacklistUnverifiedDevicesKey = @"kRoomSettingsEncryptionBlacklistUnverifiedDevicesKey"; @@ -124,7 +115,6 @@ NSString *const kRoomSettingsNameCellViewIdentifier = @"kRoomSettingsNameCellVie NSString *const kRoomSettingsTopicCellViewIdentifier = @"kRoomSettingsTopicCellViewIdentifier"; NSString *const kRoomSettingsWarningCellViewIdentifier = @"kRoomSettingsWarningCellViewIdentifier"; NSString *const kRoomSettingsNewAddressCellViewIdentifier = @"kRoomSettingsNewAddressCellViewIdentifier"; -NSString *const kRoomSettingsNewCommunityCellViewIdentifier = @"kRoomSettingsNewCommunityCellViewIdentifier"; NSString *const kRoomSettingsAddressCellViewIdentifier = @"kRoomSettingsAddressCellViewIdentifier"; NSString *const kRoomSettingsAdvancedCellViewIdentifier = @"kRoomSettingsAdvancedCellViewIdentifier"; NSString *const kRoomSettingsAdvancedEnableE2eCellViewIdentifier = @"kRoomSettingsAdvancedEnableE2eCellViewIdentifier"; @@ -156,10 +146,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti NSUInteger localAddressesCount; UITextField* addAddressTextField; - // Related groups/communities - NSMutableArray *relatedGroups; - UITextField* addGroupTextField; - // The potential image loader MXMediaLoader *uploader; @@ -248,7 +234,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti historyVisibilityTickCells = [[NSMutableDictionary alloc] initWithCapacity:4]; roomAddresses = [NSMutableArray array]; - relatedGroups = [NSMutableArray array]; [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; [self.tableView registerClass:MXKTableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; @@ -258,7 +243,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:kRoomSettingsNameCellViewIdentifier]; [self.tableView registerClass:TableViewCellWithLabelAndLargeTextView.class forCellReuseIdentifier:kRoomSettingsTopicCellViewIdentifier]; [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:kRoomSettingsNewAddressCellViewIdentifier]; - [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:kRoomSettingsNewCommunityCellViewIdentifier]; [self.tableView registerClass:UITableViewCell.class forCellReuseIdentifier:kRoomSettingsAddressCellViewIdentifier]; [self.tableView registerClass:UITableViewCell.class forCellReuseIdentifier:kRoomSettingsWarningCellViewIdentifier]; @@ -421,7 +405,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti historyVisibilityTickCells = nil; roomAddresses = nil; - relatedGroups = nil; if (extraEventsListener) { @@ -516,8 +499,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti localAddressesCount++; } - [self refreshRelatedGroups]; - // create sections NSMutableArray *tmpSections = [NSMutableArray arrayWithCapacity:SECTION_TAG_BANNED_ADVANCED + 1]; @@ -606,34 +587,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [tmpSections addObject:sectionAddresses]; } - if (RiotSettings.shared.roomSettingsScreenShowFlairSettings) - { - Section *sectionFlair = [Section sectionWithTag:SECTION_TAG_FLAIR]; - - for (NSInteger counter = 0; counter < relatedGroups.count; counter++) - { - [sectionFlair addRowWithTag:counter + ROOM_SETTINGS_RELATED_GROUPS_OFFSET]; - } - - if (self.mainSession) - { - // Check user's power level to know whether the user is allowed to add communities to this room - MXRoomPowerLevels *powerLevels = [mxRoomState powerLevels]; - NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; - - if (oneSelfPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:kMXEventTypeStringRoomRelatedGroups]) - { - [sectionFlair addRowWithTag:ROOM_SETTINGS_RELATED_GROUPS_NEW_GROUP]; - } - } - - sectionFlair.headerTitle = [VectorL10n roomDetailsFlairSection]; - if ([sectionFlair hasAnyRows]) - { - [tmpSections addObject:sectionFlair]; - } - } - if (bannedMembers.count) { Section *sectionBannedUsers = [Section sectionWithTag:SECTION_TAG_BANNED_USERS]; @@ -1156,36 +1109,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti }]; } -- (void)refreshRelatedGroups -{ - // Refresh here the related communities list. - [relatedGroups removeAllObjects]; - [relatedGroups addObjectsFromArray:mxRoomState.relatedGroups]; - NSArray *removedCommunities = updatedItemsDict[kRoomSettingsRemovedRelatedGroupKey]; - if (removedCommunities.count) - { - for (NSUInteger index = 0; index < relatedGroups.count;) - { - NSString *groupId = relatedGroups[index]; - - // Check whether the user did not remove it - if ([removedCommunities indexOfObject:groupId] != NSNotFound) - { - [relatedGroups removeObjectAtIndex:index]; - } - else - { - index++; - } - } - } - NSArray *communities = updatedItemsDict[kRoomSettingsNewRelatedGroupKey]; - if (communities) - { - [relatedGroups addObjectsFromArray:communities]; - } -} - #pragma mark - UITextViewDelegate - (void)textViewDidBeginEditing:(UITextView *)textView; @@ -1347,18 +1270,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti addAddressTextField.text = nil; } } - else if (textField == addGroupTextField) - { - // Dismiss the keyboard - [addGroupTextField resignFirstResponder]; - - NSString *groupId = addGroupTextField.text; - if (!groupId.length || [self addCommunity:groupId]) - { - // Reset the input field - addGroupTextField.text = nil; - } - } return YES; } @@ -1920,52 +1831,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti return; } - - // Related groups - if (updatedItemsDict[kRoomSettingsNewRelatedGroupKey] || updatedItemsDict[kRoomSettingsRemovedRelatedGroupKey]) - { - [self refreshRelatedGroups]; - - pendingOperation = [mxRoom setRelatedGroups:relatedGroups success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->pendingOperation = nil; - - [self->updatedItemsDict removeObjectForKey:kRoomSettingsNewRelatedGroupKey]; - [self->updatedItemsDict removeObjectForKey:kRoomSettingsRemovedRelatedGroupKey]; - - [self onSave:nil]; - } - - } failure:^(NSError *error) { - - MXLogDebug(@"[RoomSettingsViewController] Update room communities failed"); - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->pendingOperation = nil; - - dispatch_async(dispatch_get_main_queue(), ^{ - - NSString* message = error.localizedDescription; - if (!message.length) - { - message = [VectorL10n roomDetailsFailToUpdateRoomCommunities]; - } - [self onSaveFailed:message withKeys:@[kRoomSettingsNewRelatedGroupKey,kRoomSettingsRemovedRelatedGroupKey]]; - - }); - } - - }]; - - return; - } } // Update here other room settings @@ -2793,64 +2658,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti cell = addressCell; } } - else if (section == SECTION_TAG_FLAIR) - { - if (row == ROOM_SETTINGS_RELATED_GROUPS_NEW_GROUP) - { - MXKTableViewCellWithLabelAndTextField *addCommunityCell = [tableView dequeueReusableCellWithIdentifier:kRoomSettingsNewCommunityCellViewIdentifier forIndexPath:indexPath]; - - // Retrieve the current edited value if any - NSString *currentValue = (addGroupTextField ? addGroupTextField.text : nil); - - addCommunityCell.mxkLabelLeadingConstraint.constant = 0; - addCommunityCell.mxkTextFieldLeadingConstraint.constant = tableView.vc_separatorInset.left; - addCommunityCell.mxkTextFieldTrailingConstraint.constant = 15; - - addCommunityCell.mxkLabel.text = nil; - - addCommunityCell.accessoryType = UITableViewCellAccessoryNone; - addCommunityCell.accessoryView = [[UIImageView alloc] initWithImage:[AssetImages.plusIcon.image vc_tintedImageUsingColor:ThemeService.shared.theme.textPrimaryColor]]; - - addGroupTextField = addCommunityCell.mxkTextField; - addGroupTextField.placeholder = [VectorL10n roomDetailsNewFlairPlaceholder:self.mainSession.matrixRestClient.homeserverSuffix]; - addGroupTextField.attributedPlaceholder = [[NSAttributedString alloc] - initWithString:addGroupTextField.placeholder - attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.placeholderTextColor}]; - addGroupTextField.userInteractionEnabled = YES; - addGroupTextField.text = currentValue; - addGroupTextField.textColor = ThemeService.shared.theme.textSecondaryColor; - - addGroupTextField.tintColor = ThemeService.shared.theme.tintColor; - addGroupTextField.font = [UIFont systemFontOfSize:17]; - addGroupTextField.borderStyle = UITextBorderStyleNone; - addGroupTextField.textAlignment = NSTextAlignmentLeft; - - addGroupTextField.autocorrectionType = UITextAutocorrectionTypeNo; - addGroupTextField.spellCheckingType = UITextSpellCheckingTypeNo; - addGroupTextField.delegate = self; - - cell = addCommunityCell; - } - else if (row >= ROOM_SETTINGS_RELATED_GROUPS_OFFSET) - { - UITableViewCell *communityCell = [tableView dequeueReusableCellWithIdentifier:kRoomSettingsAddressCellViewIdentifier forIndexPath:indexPath]; - - communityCell.textLabel.font = [UIFont systemFontOfSize:16]; - communityCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - communityCell.textLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; - communityCell.accessoryView = nil; - communityCell.accessoryType = UITableViewCellAccessoryNone; - communityCell.selectionStyle = UITableViewCellSelectionStyleNone; - - NSInteger index = row - ROOM_SETTINGS_RELATED_GROUPS_OFFSET; - - if (index < relatedGroups.count) - { - communityCell.textLabel.text = relatedGroups[index]; - } - cell = communityCell; - } - } else if (section == SECTION_TAG_BANNED_USERS) { UITableViewCell *addressCell = [tableView dequeueReusableCellWithIdentifier:kRoomSettingsAddressCellViewIdentifier forIndexPath:indexPath]; @@ -3171,18 +2978,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti }); } } - else if (section == SECTION_TAG_FLAIR) - { - if (row == ROOM_SETTINGS_RELATED_GROUPS_NEW_GROUP) - { - NSString *groupId = addGroupTextField.text; - if (!groupId.length || [self addCommunity:groupId]) - { - // Reset the input field - addGroupTextField.text = nil; - } - } - } else if (section == SECTION_TAG_BANNED_USERS) { // Show the RoomMemberDetailsViewController on this member so that @@ -3240,27 +3035,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti swipeActionConfiguration.performsFirstActionWithFullSwipe = NO; return swipeActionConfiguration; } - else if (section == SECTION_TAG_FLAIR && row >= ROOM_SETTINGS_RELATED_GROUPS_OFFSET) - { - UIContextualAction *removeAddressAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive - title:@" " - handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { - [self removeCommunityAtIndexPath:indexPath]; - completionHandler(YES); - }]; - removeAddressAction.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - removeAddressAction.image = [AssetImages.removeIcon.image vc_notRenderedImage]; - - // Create swipe action configuration - - NSArray *actions = @[ - removeAddressAction - ]; - - UISwipeActionsConfiguration *swipeActionConfiguration = [UISwipeActionsConfiguration configurationWithActions:actions]; - swipeActionConfiguration.performsFirstActionWithFullSwipe = NO; - return swipeActionConfiguration; - } return nil; } @@ -3693,20 +3467,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti } } -- (void)removeCommunityAtIndexPath:(NSIndexPath *)indexPath -{ - indexPath = [_tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath]; - NSInteger row = indexPath.row; - - NSInteger index = row - ROOM_SETTINGS_RELATED_GROUPS_OFFSET; - - if (index < relatedGroups.count) - { - NSString *groupId = relatedGroups[index]; - [self removeCommunity:groupId]; - } -} - - (void)removeRoomAlias:(NSString*)roomAlias { NSString *canonicalAlias; @@ -3762,36 +3522,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti } } -- (void)removeCommunity:(NSString*)groupId -{ - // Check whether the alias has just been added - NSMutableArray *addedGroup = updatedItemsDict[kRoomSettingsNewRelatedGroupKey]; - if (addedGroup && [addedGroup indexOfObject:groupId] != NSNotFound) - { - [addedGroup removeObject:groupId]; - - if (!addedGroup.count) - { - [updatedItemsDict removeObjectForKey:kRoomSettingsNewRelatedGroupKey]; - } - } - else - { - NSMutableArray *removedGroup = updatedItemsDict[kRoomSettingsRemovedRelatedGroupKey]; - if (!removedGroup) - { - removedGroup = [NSMutableArray array]; - updatedItemsDict[kRoomSettingsRemovedRelatedGroupKey] = removedGroup; - } - - [removedGroup addObject:groupId]; - } - - [self updateSections]; - - [self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0); -} - - (BOOL)addRoomAlias:(NSString*)roomAlias { // Check whether the provided alias is valid @@ -3873,71 +3603,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti return NO; } -- (BOOL)addCommunity:(NSString*)groupId -{ - // Check whether the provided id is valid - if ([MXTools isMatrixGroupIdentifier:groupId]) - { - // Check whether this group has just been deleted - NSMutableArray *removedGroups = updatedItemsDict[kRoomSettingsRemovedRelatedGroupKey]; - if (removedGroups && [removedGroups indexOfObject:groupId] != NSNotFound) - { - [removedGroups removeObject:groupId]; - - if (!removedGroups.count) - { - [updatedItemsDict removeObjectForKey:kRoomSettingsRemovedRelatedGroupKey]; - } - } - // Check whether this alias is not already defined for this room - else if ([relatedGroups indexOfObject:groupId] == NSNotFound) - { - NSMutableArray *addedGroup = updatedItemsDict[kRoomSettingsNewRelatedGroupKey]; - if (!addedGroup) - { - addedGroup = [NSMutableArray array]; - updatedItemsDict[kRoomSettingsNewRelatedGroupKey] = addedGroup; - } - - [addedGroup addObject:groupId]; - } - - [self updateSections]; - - [self getNavigationItem].rightBarButtonItem.enabled = (updatedItemsDict.count != 0); - - return YES; - } - - // Prompt here user for invalid id - __weak typeof(self) weakSelf = self; - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - NSString *alertMsg = [VectorL10n roomDetailsFlairInvalidIdPromptMsg:groupId]; - - currentAlert = [UIAlertController alertControllerWithTitle:[VectorL10n roomDetailsFlairInvalidIdPromptTitle] - message:alertMsg - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"RoomSettingsVCAddCommunityAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - - return NO; -} - #pragma mark - TableViewCellWithCheckBoxesDelegate - (void)tableViewCellWithCheckBoxes:(TableViewCellWithCheckBoxes *)tableViewCellWithCheckBoxes didTapOnCheckBoxAtIndex:(NSUInteger)index diff --git a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h index 15e18f89b..fca4f31aa 100644 --- a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h +++ b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.h @@ -304,11 +304,6 @@ extern NSString *const kMXKRoomBubbleCellUrlItemInteraction; */ - (void)prepareRender:(MXKCellData*)cellData; -/** - Refresh the flair information added to the sender display name. - */ -- (void)renderSenderFlair; - /** Highlight text message related to a specific event in the displayed message. diff --git a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m index fe4a3558d..13eac3bcc 100644 --- a/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m +++ b/Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m @@ -497,16 +497,7 @@ static BOOL _disableLongPressGestureOnEvent; // Display sender's name except if the name appears in the displayed text (see emote and membership events) if (bubbleData.shouldHideSenderName == NO) { - if (bubbleData.senderFlair) - { - [self renderSenderFlair]; - } - else - { - self.userNameLabel.text = bubbleData.senderDisplayName; - } - - + self.userNameLabel.text = bubbleData.senderDisplayName; self.userNameLabel.hidden = NO; self.userNameTapGestureMaskView.userInteractionEnabled = YES; } @@ -800,75 +791,6 @@ static BOOL _disableLongPressGestureOnEvent; mxkCellData = cellData; } -- (void)renderSenderFlair -{ - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ ", bubbleData.senderDisplayName]]; - - NSUInteger index = 0; - - for (MXGroup *group in bubbleData.senderFlair) - { - NSString *mxcAvatarURI = group.profile.avatarUrl; - NSString *cacheFilePath = [MXMediaManager thumbnailCachePathForMatrixContentURI:mxcAvatarURI andType:@"image/jpeg" inFolder:kMXMediaManagerDefaultCacheFolder toFitViewSize:CGSizeMake(12, 12) withMethod:MXThumbnailingMethodCrop]; - - // Check whether the avatar url is valid - if (cacheFilePath) - { - UIImage *image = [MXMediaManager loadThroughCacheWithFilePath:cacheFilePath]; - if (image) - { - NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; - textAttachment.image = [MXKTools resizeImageWithRoundedCorners:image toSize:CGSizeMake(12, 12)]; - NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment]; - [attributedString appendAttributedString:attrStringWithImage]; - [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; - } - else - { - NSString *downloadId = [MXMediaManager thumbnailDownloadIdForMatrixContentURI:mxcAvatarURI - inFolder:kMXMediaManagerDefaultCacheFolder - toFitViewSize:CGSizeMake(12, 12) - withMethod:MXThumbnailingMethodCrop]; - // Check whether the download is in progress. - MXMediaLoader* loader = [MXMediaManager existingDownloaderWithIdentifier:downloadId]; - if (loader) - { - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXMediaLoaderStateDidChangeNotification object:loader]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onFlairDownloadStateChange:) name:kMXMediaLoaderStateDidChangeNotification object:loader]; - } - else - { - MXWeakify(self); - [bubbleData.mxSession.mediaManager downloadThumbnailFromMatrixContentURI:mxcAvatarURI - withType:@"image/jpeg" - inFolder:kMXMediaManagerDefaultCacheFolder - toFitViewSize:CGSizeMake(12, 12) - withMethod:MXThumbnailingMethodCrop - success:^(NSString *outputFilePath) { - // Refresh sender flair - MXStrongifyAndReturnIfNil(self); - [self renderSenderFlair]; - } - failure:nil]; - } - } - - index++; - if (index == 3) - { - if (bubbleData.senderFlair.count > 3) - { - NSAttributedString *more = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"+%tu", (bubbleData.senderFlair.count - 3)] attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:11.0], NSBaselineOffsetAttributeName:@(+2)}]; - [attributedString appendAttributedString:more]; - } - break; - } - } - } - - self.userNameLabel.attributedText = attributedString; -} - - (void)renderGif { if (self.attachmentView && bubbleData.attachment) @@ -1292,23 +1214,6 @@ static BOOL _disableLongPressGestureOnEvent; } } -- (void)onFlairDownloadStateChange:(NSNotification *)notif -{ - MXMediaLoader *loader = (MXMediaLoader*)notif.object; - switch (loader.state) { - case MXMediaLoaderStateDownloadCompleted: - [self renderSenderFlair]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXMediaLoaderStateDidChangeNotification object:loader]; - break; - case MXMediaLoaderStateDownloadFailed: - case MXMediaLoaderStateCancelled: - [[NSNotificationCenter defaultCenter] removeObserver:self name:kMXMediaLoaderStateDidChangeNotification object:loader]; - break; - default: - break; - } -} - - (void)startProgressUI { self.progressView.hidden = YES; diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Sticker/RoomSelectedStickerBubbleCell.m b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Sticker/RoomSelectedStickerBubbleCell.m index cc0506fd2..110515638 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Sticker/RoomSelectedStickerBubbleCell.m +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Sticker/RoomSelectedStickerBubbleCell.m @@ -102,15 +102,7 @@ // Display sender's name except if the name appears in the displayed text (see emote and membership events) if (bubbleData.shouldHideSenderName == NO) { - if (bubbleData.senderFlair) - { - [self renderSenderFlair]; - } - else - { - self.userNameLabel.text = bubbleData.senderDisplayName; - } - + self.userNameLabel.text = bubbleData.senderDisplayName; self.userNameLabel.hidden = NO; self.userNameTapGestureMaskView.userInteractionEnabled = YES; } diff --git a/changelog.d/6523.api b/changelog.d/6523.api new file mode 100644 index 000000000..b916a4263 --- /dev/null +++ b/changelog.d/6523.api @@ -0,0 +1 @@ +Communities: GroupsViewController etc have all been removed now that Spaces are available in the app. diff --git a/changelog.d/6523.change b/changelog.d/6523.change new file mode 100644 index 000000000..e31ca639b --- /dev/null +++ b/changelog.d/6523.change @@ -0,0 +1 @@ +Groups: Support for groups has been removed now that Spaces are fully available. From c57e148d3a620e44d5bd431c502b36294407e064 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 10 Aug 2022 16:39:13 +0100 Subject: [PATCH 06/80] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index cd95bca59..55cc0ae89 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.8.26 -CURRENT_PROJECT_VERSION = 1.8.26 +MARKETING_VERSION = 1.8.27 +CURRENT_PROJECT_VERSION = 1.8.27 From 073eb73302bad5a952c454e7f9dc12a99154bed8 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 11 Aug 2022 13:52:01 +0300 Subject: [PATCH 07/80] Adapt api changes from key backup module --- .../KeyBackup/Recover/KeyBackupRecoverCoordinator.swift | 2 +- Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift | 2 +- .../Setup/Passphrase/KeyBackupSetupPassphraseViewModel.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift index e12cb2e69..d7d0a9a31 100644 --- a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift +++ b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift @@ -84,7 +84,7 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { let coordinator: Coordinator & Presentable // Check if a passphrase has been set for given backup - if let megolmBackupAuthData = MXMegolmBackupAuthData(fromJSON: self.keyBackupVersion.authData), megolmBackupAuthData.privateKeySalt != nil { + if self.keyBackupVersion.authData["private_key_salt"] != nil { coordinator = self.createRecoverFromPassphraseCoordinator() } else { coordinator = self.createRecoverFromRecoveryKeyCoordinator() diff --git a/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift b/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift index c14c56272..03171ebd2 100644 --- a/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift +++ b/Riot/Modules/KeyBackup/Setup/KeyBackupSetupCoordinator.swift @@ -134,7 +134,7 @@ final class KeyBackupSetupCoordinator: KeyBackupSetupCoordinatorType { return } - keyBackup.prepareKeyBackupVersion(withPassword: nil, success: { megolmBackupCreationInfo in + keyBackup.prepareKeyBackupVersion(withPassword: nil, algorithm: nil, success: { megolmBackupCreationInfo in keyBackup.createKeyBackupVersion(megolmBackupCreationInfo, success: { _ in recoveryService.updateRecovery(forSecrets: [MXSecretId.keyBackup.takeUnretainedValue() as String], withPrivateKey: privateKey) { completion(.success(Void())) diff --git a/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModel.swift b/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModel.swift index ea2fd4752..b2dd9163c 100644 --- a/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModel.swift +++ b/Riot/Modules/KeyBackup/Setup/Passphrase/KeyBackupSetupPassphraseViewModel.swift @@ -96,7 +96,7 @@ final class KeyBackupSetupPassphraseViewModel: KeyBackupSetupPassphraseViewModel self.update(viewState: .loading) - self.keyBackup.prepareKeyBackupVersion(withPassword: passphrase, success: { [weak self] (megolmBackupCreationInfo) in + self.keyBackup.prepareKeyBackupVersion(withPassword: passphrase, algorithm: nil, success: { [weak self] (megolmBackupCreationInfo) in guard let sself = self else { return } @@ -122,7 +122,7 @@ final class KeyBackupSetupPassphraseViewModel: KeyBackupSetupPassphraseViewModel private func setupRecoveryKey() { self.update(viewState: .loading) - self.keyBackup.prepareKeyBackupVersion(withPassword: nil, success: { [weak self] (megolmBackupCreationInfo) in + self.keyBackup.prepareKeyBackupVersion(withPassword: nil, algorithm: nil, success: { [weak self] (megolmBackupCreationInfo) in guard let sself = self else { return } From def793d2aa7c06b669dfdf0419103d28ca6c83d5 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 11 Aug 2022 13:52:28 +0300 Subject: [PATCH 08/80] Add an entry for event untrusted status into the encryption info view --- .../Views/EncryptionInfoView/MXKEncryptionInfoView.m | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Riot/Modules/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m b/Riot/Modules/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m index 5ae061284..cbaea4f84 100644 --- a/Riot/Modules/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m +++ b/Riot/Modules/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.m @@ -192,6 +192,7 @@ static NSAttributedString *verticalWhitespace = nil; NSString *claimedKey = _mxEvent.keysClaimed[@"ed25519"]; NSString *algorithm = _mxEvent.wireContent[@"algorithm"]; NSString *sessionId = _mxEvent.wireContent[@"session_id"]; + NSString *untrusted = _mxEvent.isUntrusted ? [VectorL10n userVerificationSessionsListSessionUntrusted] : [VectorL10n userVerificationSessionsListSessionTrusted]; NSString *decryptionError; if (_mxEvent.decryptionError) @@ -277,6 +278,16 @@ static NSAttributedString *verticalWhitespace = nil; attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; + + [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:[NSString stringWithFormat:@"%@\n", [VectorL10n sslTrust]] + attributes:@{NSForegroundColorAttributeName: _defaultTextColor, + NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; + [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:untrusted + attributes:@{NSForegroundColorAttributeName: _defaultTextColor, + NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; + [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [textViewAttributedString appendAttributedString:eventInformationString]; } From a91fc081205345578ebc5aa4dbfac4073ae944df Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 11 Aug 2022 13:54:46 +0300 Subject: [PATCH 09/80] Add changelog --- changelog.d/pr-6555.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-6555.change diff --git a/changelog.d/pr-6555.change b/changelog.d/pr-6555.change new file mode 100644 index 000000000..03a9c5ca5 --- /dev/null +++ b/changelog.d/pr-6555.change @@ -0,0 +1 @@ +KeyBackup: Adapt changes from sdk, add an entry into encryption info view of a message. From 4d0f8ec8872959955ddef6dd750feb846cbd19cb Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 12 Aug 2022 10:32:20 +0100 Subject: [PATCH 10/80] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 55cc0ae89..b97194e3c 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.8.27 -CURRENT_PROJECT_VERSION = 1.8.27 +MARKETING_VERSION = 1.8.28 +CURRENT_PROJECT_VERSION = 1.8.28 From da5ad4535fb23dc13214e5b0ed89054d8a6a21a5 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 12 Aug 2022 15:50:15 +0300 Subject: [PATCH 11/80] Fix `UITableViewAlertForVisibleCellsAccessDuringUpdate` errors --- Riot/Modules/Room/RoomViewController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 99eff65aa..e287353c5 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -4938,7 +4938,9 @@ static CGSize kThreadListBarButtonItemImageSize; { readMarkerTableViewCell = roomBubbleTableViewCell; - [self checkReadMarkerVisibility]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self checkReadMarkerVisibility]; + }); } } } From 96475032f38efa2d8365828351b5c19a2890aad2 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 12 Aug 2022 15:50:55 +0300 Subject: [PATCH 12/80] Add changelog --- changelog.d/5932.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5932.bugfix diff --git a/changelog.d/5932.bugfix b/changelog.d/5932.bugfix new file mode 100644 index 000000000..967a872aa --- /dev/null +++ b/changelog.d/5932.bugfix @@ -0,0 +1 @@ +RoomViewController: Wait for table view updates before checing read marker visibility. From 2adeb37bbebffdd4329c4a82f00c3c359630352f Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 12 Aug 2022 17:46:17 +0300 Subject: [PATCH 13/80] Fix `UITableViewAlertForLayoutOutsideViewHierarchy` errors --- Riot/Modules/Room/MXKRoomViewController.m | 4 ++- Riot/Modules/Room/RoomViewController.m | 41 +++++++++++++---------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index a3ec8d280..ffb91024b 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -308,7 +308,9 @@ } // Finalize view controller appearance - [self updateViewControllerAppearanceOnRoomDataSourceState]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateViewControllerAppearanceOnRoomDataSourceState]; + }); // no need to reload the tableview at this stage // IOS is going to load it after calling this method diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index e287353c5..1c9114ab1 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -353,7 +353,9 @@ static CGSize kThreadListBarButtonItemImageSize; // Replace the default input toolbar view. // Note: this operation will force the layout of subviews. That is why cell view classes must be registered before. - [self updateRoomInputToolbarViewClassIfNeeded]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateRoomInputToolbarViewClassIfNeeded]; + }); // set extra area [self setRoomActivitiesViewClass:RoomActivitiesView.class]; @@ -5680,25 +5682,28 @@ static CGSize kThreadListBarButtonItemImageSize; continueBlock(threadDataSource, YES); }]; } - else if (self.isContextPreview) + else if (self.roomDataSource.roomId) { - [RoomPreviewDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId - andMatrixSession:self.mainSession - onComplete:^(RoomPreviewDataSource *roomDataSource) - { - continueBlock(roomDataSource, YES); - }]; - } - else - { - // Switch back to the room live timeline managed by MXKRoomDataSourceManager - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; + if (self.isContextPreview) + { + [RoomPreviewDataSource loadRoomDataSourceWithRoomId:self.roomDataSource.roomId + andMatrixSession:self.mainSession + onComplete:^(RoomPreviewDataSource *roomDataSource) + { + continueBlock(roomDataSource, YES); + }]; + } + else + { + // Switch back to the room live timeline managed by MXKRoomDataSourceManager + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; - [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId - create:YES - onComplete:^(MXKRoomDataSource *roomDataSource) { - continueBlock(roomDataSource, NO); - }]; + [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId + create:YES + onComplete:^(MXKRoomDataSource *roomDataSource) { + continueBlock(roomDataSource, NO); + }]; + } } } } From 5db71c92c717c81522008714cd9f29c688efe3ad Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 12 Aug 2022 17:46:44 +0300 Subject: [PATCH 14/80] Remove unnecessary inputAccessoryView from room input toolbar --- Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 106fa3bb3..40f177fe2 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -80,10 +80,6 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; [self updateUIWithAttributedTextMessage:nil animated:NO]; self.textView.toolbarDelegate = self; - - // Add an accessory view to the text view in order to retrieve keyboard view. - inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero]; - self.textView.inputAccessoryView = inputAccessoryView; } - (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView From 33a968aa1b40d2cad3a2a989531a34256d1961ed Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Sun, 14 Aug 2022 06:32:30 +0000 Subject: [PATCH 15/80] Change text when swiping on room from Delete to Leave --- Riot/Modules/Common/Recents/RecentsViewController.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index ba23d57c4..4a590b87d 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -1505,6 +1505,10 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro } } +- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath { + return [VectorL10n leave]; +} + #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView From 0989a83cad80e54c64189262c8d664db68e700c2 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Sun, 14 Aug 2022 06:42:36 +0000 Subject: [PATCH 16/80] Add changelog --- changelog.d/6568.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6568.change diff --git a/changelog.d/6568.change b/changelog.d/6568.change new file mode 100644 index 000000000..39fc2da1a --- /dev/null +++ b/changelog.d/6568.change @@ -0,0 +1 @@ +Change text when swiping on room from Delete to Leave. From 53e17f9c5e3a79dd52a06293391cc07023accc9b Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 15 Aug 2022 13:18:01 +0300 Subject: [PATCH 17/80] Fixes #6569 - Provide SSO backup for homeservers that don't return an identity providers list. --- .../Authentication/Common/Service/MatrixSDK/LoginModels.swift | 4 +++- changelog.d/6569.bugfix | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6569.bugfix diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift index 6af694b5e..c3332146c 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift @@ -52,7 +52,9 @@ enum LoginMode { var ssoIdentityProviders: [SSOIdentityProvider]? { switch self { case .sso(let ssoIdentityProviders), .ssoAndPassword(let ssoIdentityProviders): - return ssoIdentityProviders + // Provide a backup for homeservers that support SSO but don't offer any identity providers + // https://spec.matrix.org/latest/client-server-api/#client-login-via-sso + return ssoIdentityProviders.count > 0 ? ssoIdentityProviders : [SSOIdentityProvider(id: "", name: "SSO", brand: nil, iconURL: nil)] default: return nil } diff --git a/changelog.d/6569.bugfix b/changelog.d/6569.bugfix new file mode 100644 index 000000000..02a1b3d7e --- /dev/null +++ b/changelog.d/6569.bugfix @@ -0,0 +1 @@ +Add a login and signup fallback SSO option for homeservers that don't offer a list of identity providers. \ No newline at end of file From 4f71fe7e1c0cacf53158640de0310676ba0e8975 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Mon, 15 Aug 2022 14:49:24 +0300 Subject: [PATCH 18/80] Revert the fix for keyboard stickiness, rename inputAccessoryView --- .../Views/RoomInputToolbar/MXKRoomInputToolbarView.h | 4 ++-- .../Views/RoomInputToolbar/MXKRoomInputToolbarView.m | 4 ++-- .../MXKRoomInputToolbarViewWithSimpleTextView.m | 4 ++-- Riot/Modules/Room/MXKRoomViewController.m | 2 +- Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m | 3 +++ 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h index 4282c505d..503441a52 100644 --- a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h +++ b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h @@ -205,7 +205,7 @@ typedef enum : NSUInteger UIView *messageComposerContainer; @protected - UIView *inputAccessoryView; + UIView *inputAccessoryViewForKeyboard; } /** @@ -333,7 +333,7 @@ typedef enum : NSUInteger actually used to retrieve the keyboard view. Indeed the keyboard view is the superview of the accessory view when the message composer become the first responder. */ -@property (readonly) UIView *inputAccessoryView; +@property (readonly) UIView *inputAccessoryViewForKeyboard; /** Display the keyboard. diff --git a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m index 40d0d5356..61933d8d9 100644 --- a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m +++ b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m @@ -61,7 +61,7 @@ @end @implementation MXKRoomInputToolbarView -@synthesize messageComposerContainer, inputAccessoryView; +@synthesize messageComposerContainer, inputAccessoryViewForKeyboard; + (UINib *)nib { @@ -103,7 +103,7 @@ - (void)dealloc { - inputAccessoryView = nil; + inputAccessoryViewForKeyboard = nil; [self destroy]; } diff --git a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarViewWithSimpleTextView.m b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarViewWithSimpleTextView.m index 3cdb38eda..c48bfda5a 100644 --- a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarViewWithSimpleTextView.m +++ b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarViewWithSimpleTextView.m @@ -30,8 +30,8 @@ [super awakeFromNib]; // Add an accessory view to the text view in order to retrieve keyboard view. - inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero]; - self.messageComposerTextView.inputAccessoryView = self.inputAccessoryView; + inputAccessoryViewForKeyboard = [[UIView alloc] initWithFrame:CGRectZero]; + self.messageComposerTextView.inputAccessoryView = inputAccessoryViewForKeyboard; } -(void)customizeViewRendering diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index ffb91024b..2002d7344 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -495,7 +495,7 @@ if (!keyboardView) { // Check whether the first responder is the input tool bar text composer - keyboardView = inputToolbarView.inputAccessoryView.superview; + keyboardView = inputToolbarView.inputAccessoryViewForKeyboard.superview; } // Report the keyboard view in order to track keyboard frame changes diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 40f177fe2..cd57bd0ed 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -80,6 +80,9 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; [self updateUIWithAttributedTextMessage:nil animated:NO]; self.textView.toolbarDelegate = self; + + inputAccessoryViewForKeyboard = [[UIView alloc] initWithFrame:CGRectZero]; + self.textView.inputAccessoryView = inputAccessoryViewForKeyboard; } - (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView From 7078173b2d0927a836898d74be3fc9671cf3dd3a Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Mon, 15 Aug 2022 14:49:55 +0300 Subject: [PATCH 19/80] Fix table view height constraints in the room screen --- Riot/Modules/Room/RoomViewController.xib | 35 ++++++++---------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index 783146c29..aac9ed79a 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -1,9 +1,9 @@ - + - + @@ -41,25 +41,14 @@ - - - - - - - - - - + + + + + - - + + @@ -160,8 +149,8 @@ - - + From 55191b3a7c6e87d1c0f64358fa4e54460d17214a Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Wed, 17 Aug 2022 17:22:03 +0200 Subject: [PATCH 20/80] App Layout: Cancel and Back on Spaces Bottom Sheet - Fixed --- .../SpaceSelectorBottomCoordinator.swift | 3 ++- .../Coordinator/SpaceSelectorCoordinator.swift | 7 +++++-- .../MockSpaceSelectorScreenState.swift | 2 +- .../SpaceSelector/SpaceSelectorModels.swift | 2 ++ .../SpaceSelector/SpaceSelectorViewModel.swift | 13 +++++++------ .../SpaceSelectorViewModelProtocol.swift | 2 +- .../SpaceSelector/View/SpaceSelector.swift | 6 ++++-- changelog.d/6572.bugfix | 1 + 8 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 changelog.d/6572.bugfix diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomCoordinator.swift index c649ed558..850d5525a 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/Coordinator/SpaceSelectorBottomCoordinator.swift @@ -95,7 +95,8 @@ final class SpaceSelectorBottomSheetCoordinator: Coordinator, Presentable { let parameters = SpaceSelectorCoordinatorParameters(session: parameters.session, parentSpaceId: parentSpaceId, selectedSpaceId: parameters.selectedSpaceId, - showHomeSpace: parameters.showHomeSpace) + showHomeSpace: parameters.showHomeSpace, + showCancel: navigationRouter.modules.isEmpty) let coordinator = SpaceSelectorCoordinator(parameters: parameters) coordinator.completion = { [weak self] result in guard let self = self else { return } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift index 51766d9f5..5e655ecbd 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Coordinator/SpaceSelectorCoordinator.swift @@ -22,15 +22,18 @@ struct SpaceSelectorCoordinatorParameters { let parentSpaceId: String? let selectedSpaceId: String? let showHomeSpace: Bool + let showCancel: Bool init(session: MXSession, parentSpaceId: String? = nil, selectedSpaceId: String? = nil, - showHomeSpace: Bool = false) { + showHomeSpace: Bool = false, + showCancel: Bool) { self.session = session self.parentSpaceId = parentSpaceId self.selectedSpaceId = selectedSpaceId self.showHomeSpace = showHomeSpace + self.showCancel = showCancel } } @@ -58,7 +61,7 @@ final class SpaceSelectorCoordinator: Coordinator, Presentable { init(parameters: SpaceSelectorCoordinatorParameters) { self.parameters = parameters let service = SpaceSelectorService(session: parameters.session, parentSpaceId: parameters.parentSpaceId, showHomeSpace: parameters.showHomeSpace, selectedSpaceId: parameters.selectedSpaceId) - let viewModel = SpaceSelectorViewModel.makeViewModel(service: service) + let viewModel = SpaceSelectorViewModel.makeViewModel(service: service, showCancel: parameters.showCancel) let view = SpaceSelector(viewModel: viewModel.context) .addDependency(AvatarService.instantiate(mediaManager: parameters.session.mediaManager)) self.viewModel = viewModel diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift index 261c39a90..cedbbb1c2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift @@ -48,7 +48,7 @@ enum MockSpaceSelectorScreenState: MockScreenState, CaseIterable { case .selection: service = MockSpaceSelectorService(selectedSpaceId: MockSpaceSelectorService.defaultSpaceList[2].id) } - let viewModel = SpaceSelectorViewModel.makeViewModel(service: service) + let viewModel = SpaceSelectorViewModel.makeViewModel(service: service, showCancel: true) // can simulate service and viewModel actions here if needs be. diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift index ade9a78f0..829919958 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorModels.swift @@ -96,6 +96,8 @@ struct SpaceSelectorViewState: BindableState { var selectedSpaceId: String? /// String to be displayed as title for the navigation bar var navigationTitle: String + /// `true` if the view should display the cancel button in the navigation bar + let showCancel: Bool } enum SpaceSelectorViewAction { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift index fe3abd531..15db1691f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModel.swift @@ -35,20 +35,21 @@ class SpaceSelectorViewModel: SpaceSelectorViewModelType, SpaceSelectorViewModel // MARK: - Setup - static func makeViewModel(service: SpaceSelectorServiceProtocol) -> SpaceSelectorViewModelProtocol { - return SpaceSelectorViewModel(service: service) + static func makeViewModel(service: SpaceSelectorServiceProtocol, showCancel: Bool) -> SpaceSelectorViewModelProtocol { + return SpaceSelectorViewModel(service: service, showCancel: showCancel) } - private init(service: SpaceSelectorServiceProtocol) { + private init(service: SpaceSelectorServiceProtocol, showCancel: Bool) { self.service = service - super.init(initialViewState: Self.defaultState(service: service)) + super.init(initialViewState: Self.defaultState(service: service, showCancel: showCancel)) } - private static func defaultState(service: SpaceSelectorServiceProtocol) -> SpaceSelectorViewState { + private static func defaultState(service: SpaceSelectorServiceProtocol, showCancel: Bool) -> SpaceSelectorViewState { let parentName = service.parentSpaceNameSubject.value return SpaceSelectorViewState(items: service.spaceListSubject.value, selectedSpaceId: service.selectedSpaceId, - navigationTitle: parentName ?? VectorL10n.spaceSelectorTitle) + navigationTitle: parentName ?? VectorL10n.spaceSelectorTitle, + showCancel: showCancel) } // MARK: - Public diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift index eb53edc93..79263720d 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/SpaceSelectorViewModelProtocol.swift @@ -19,6 +19,6 @@ import Foundation protocol SpaceSelectorViewModelProtocol { var completion: ((SpaceSelectorViewModelResult) -> Void)? { get set } - static func makeViewModel(service: SpaceSelectorServiceProtocol) -> SpaceSelectorViewModelProtocol + static func makeViewModel(service: SpaceSelectorServiceProtocol, showCancel: Bool) -> SpaceSelectorViewModelProtocol var context: SpaceSelectorViewModelType.Context { get } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift index 11ea3aa0e..d9cc21c77 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift @@ -59,8 +59,10 @@ struct SpaceSelector: View { } } ToolbarItem(placement: .cancellationAction) { - Button(VectorL10n.cancel) { - viewModel.send(viewAction: .cancel) + if viewModel.viewState.showCancel { + Button(VectorL10n.cancel) { + viewModel.send(viewAction: .cancel) + } } } } diff --git a/changelog.d/6572.bugfix b/changelog.d/6572.bugfix new file mode 100644 index 000000000..d0d247de4 --- /dev/null +++ b/changelog.d/6572.bugfix @@ -0,0 +1 @@ +App Layout: fixed Cancel and Back on Spaces Bottom Sheet \ No newline at end of file From 269067f16948dcfdb088aff83a2bafaad017a7f1 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Wed, 17 Aug 2022 19:01:58 +0200 Subject: [PATCH 21/80] App Layout: Cancel and Back on Spaces Bottom Sheet - Make CI happy --- .../SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift index 4d590a8ad..58d5f9304 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/Unit/SpaceSelectorViewModelTests.swift @@ -28,7 +28,7 @@ class SpaceSelectorViewModelTests: XCTestCase { override func setUpWithError() throws { service = MockSpaceSelectorService() - viewModel = SpaceSelectorViewModel.makeViewModel(service: service) + viewModel = SpaceSelectorViewModel.makeViewModel(service: service, showCancel: true) context = viewModel.context } From 945d005dd04e1470a3c83953f9c717e98eaaf64e Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 12 Jul 2022 13:22:11 +0100 Subject: [PATCH 22/80] Upload Dsyms to Sentry when building Alpha --- Gemfile.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 722a75731..033ac3108 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -159,6 +159,7 @@ GEM fastlane-plugin-brew (0.1.1) fastlane-plugin-diawi (2.1.0) rest-client (>= 2.0.0) + fastlane-plugin-sentry (1.12.1) fastlane-plugin-versioning (0.5.0) fastlane-plugin-xcodegen (1.1.0) fastlane-plugin-brew (~> 0.1.1) @@ -310,10 +311,11 @@ DEPENDENCIES cocoapods (~> 1.11.2) fastlane fastlane-plugin-diawi + fastlane-plugin-sentry fastlane-plugin-versioning fastlane-plugin-xcodegen slather xcode-install BUNDLED WITH - 2.3.9 + 2.3.14 From 46cfdc76cc1ce675684f50f80988a3a3819df72c Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 12 Jul 2022 13:24:17 +0100 Subject: [PATCH 23/80] Changelog --- changelog.d/pr-6413.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-6413.misc diff --git a/changelog.d/pr-6413.misc b/changelog.d/pr-6413.misc new file mode 100644 index 000000000..f1155bafd --- /dev/null +++ b/changelog.d/pr-6413.misc @@ -0,0 +1 @@ +Sentry: Upload Dsyms to Sentry when building Alpha From 1e2c0f23fd767d72d9b481602b6dc024a20114f2 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 12 Jul 2022 19:27:33 +0100 Subject: [PATCH 24/80] Pass secret as env --- .github/workflows/release-alpha.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index 889f57a75..0bcd0834b 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -91,6 +91,7 @@ jobs: FASTLANE_USER: ${{ secrets.FASTLANE_USER }} FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} DIAWI_API_TOKEN: ${{ secrets.DIAWI_API_TOKEN }} + SENTRY_API_TOKEN: ${{ secrets.SENTRY_API_TOKEN }} - name: Add or update PR comment with Ad-hoc release informations uses: NejcZdovc/comment-pr@v1 From c003dd50076149824bd64ec215f1359567fb556d Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 12 Jul 2022 20:07:36 +0100 Subject: [PATCH 25/80] Fix name --- .github/workflows/release-alpha.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index 0bcd0834b..f9c45d4f0 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -91,7 +91,7 @@ jobs: FASTLANE_USER: ${{ secrets.FASTLANE_USER }} FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} DIAWI_API_TOKEN: ${{ secrets.DIAWI_API_TOKEN }} - SENTRY_API_TOKEN: ${{ secrets.SENTRY_API_TOKEN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Add or update PR comment with Ad-hoc release informations uses: NejcZdovc/comment-pr@v1 From 02a3c45de07da023f11fa1c18ce22174f543ae3b Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Wed, 13 Jul 2022 09:56:19 +0100 Subject: [PATCH 26/80] Cache gems via setup-ruby --- .github/workflows/release-alpha.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index f9c45d4f0..be483820d 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -49,13 +49,13 @@ jobs: restore-keys: | ${{ runner.os }}-pods- + # Note: it is recommended to use setup-ruby action to cache gems + # https://github.com/actions/cache/blob/main/examples.md#ruby---bundler - name: Cache Ruby gems - uses: actions/cache@v2 + uses: ruby/setup-ruby@v1 with: - path: vendor/bundle - key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gems- + ruby-version: 2.6.9 + bundler-cache: true # Make sure we use the latest version of MatrixSDK - name: Reset MatrixSDK pod From 93aabb9c8288bb38335abae94d88e129b6f0aa15 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Wed, 13 Jul 2022 10:53:49 +0100 Subject: [PATCH 27/80] Update plugins --- .github/workflows/release-alpha.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index be483820d..14b460919 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -69,6 +69,7 @@ jobs: run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 + bundle exec fastlane update_plugins - name: Use right MatrixSDK versions run: bundle exec fastlane point_dependencies_to_related_branches From 1598a77ec2a4d623c50bc5d30b1732e198fb3886 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 14 Jul 2022 08:44:55 +0100 Subject: [PATCH 28/80] Install sentry-cli --- .github/workflows/release-alpha.yml | 11 +++++------ Brewfile | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index 14b460919..f9c45d4f0 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -49,13 +49,13 @@ jobs: restore-keys: | ${{ runner.os }}-pods- - # Note: it is recommended to use setup-ruby action to cache gems - # https://github.com/actions/cache/blob/main/examples.md#ruby---bundler - name: Cache Ruby gems - uses: ruby/setup-ruby@v1 + uses: actions/cache@v2 with: - ruby-version: 2.6.9 - bundler-cache: true + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- # Make sure we use the latest version of MatrixSDK - name: Reset MatrixSDK pod @@ -69,7 +69,6 @@ jobs: run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 - bundle exec fastlane update_plugins - name: Use right MatrixSDK versions run: bundle exec fastlane point_dependencies_to_related_branches diff --git a/Brewfile b/Brewfile index f3c727fda..e7b7707b9 100644 --- a/Brewfile +++ b/Brewfile @@ -1,2 +1,3 @@ brew "xcodegen" brew "mint" +brew "getsentry/tools/sentry-cli" From 4f4c4134f6b08947c5022ae431ef4aa7de7bb29f Mon Sep 17 00:00:00 2001 From: Philippe Loriaux Date: Thu, 11 Aug 2022 10:52:39 +0200 Subject: [PATCH 29/80] Display the option "Share invite link" only when the room is accessible by link (#6496) --- .../ContactsPicker/RoomInviteViewController.swift | 9 +++++++-- changelog.d/6496.change | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 changelog.d/6496.change diff --git a/Riot/Modules/Room/ParticipantsInviteModal/ContactsPicker/RoomInviteViewController.swift b/Riot/Modules/Room/ParticipantsInviteModal/ContactsPicker/RoomInviteViewController.swift index 900bd0c3c..44643f498 100644 --- a/Riot/Modules/Room/ParticipantsInviteModal/ContactsPicker/RoomInviteViewController.swift +++ b/Riot/Modules/Room/ParticipantsInviteModal/ContactsPicker/RoomInviteViewController.swift @@ -19,7 +19,8 @@ import Foundation class RoomInviteViewController: ContactsTableViewController { var room: MXRoom? - var roomAlias: String? + private var roomAlias: String? + private var joinRule: MXRoomJoinRule? private lazy var shareLinkPresenter: ShareInviteLinkPresenter = ShareInviteLinkPresenter() @@ -27,11 +28,15 @@ class RoomInviteViewController: ContactsTableViewController { super.viewDidLoad() roomAlias = room?.summary?.aliases?.first + joinRule = MXRoomJoinRule(identifier: room?.summary?.joinRule) setupShareInviteLinkHeader() } private func setupShareInviteLinkHeader() { - guard roomAlias != nil, RiotSettings.shared.allowInviteExernalUsers else { + guard roomAlias != nil, + RiotSettings.shared.allowInviteExernalUsers, + joinRule != .invite, + joinRule != .restricted else { contactsTableView.tableHeaderView = nil return } diff --git a/changelog.d/6496.change b/changelog.d/6496.change new file mode 100644 index 000000000..e2d12eb78 --- /dev/null +++ b/changelog.d/6496.change @@ -0,0 +1 @@ +Display the option "Share invite link" only when the room is accessible by link. From f9d5fc47a3fb0a4fd0bb9652a7a634293345a5c4 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Thu, 18 Aug 2022 16:15:47 +0200 Subject: [PATCH 30/80] App Layout: Context Menus - Fixed --- .../Contents.json | 25 ---- .../home_my_spaces_action.svg | 6 - Riot/Assets/en.lproj/Vector.strings | 2 + Riot/Generated/Images.swift | 1 - Riot/Generated/Strings.swift | 8 + .../AllChatsActionProvider.swift | 2 +- .../AllChatsEditActionProvider.swift | 68 ++------- .../AllChatsSpaceActionProvider.swift | 141 ++++++++++++++++++ .../AllChats/AllChatsViewController.swift | 37 ++++- changelog.d/6574.bugfix | 1 + 10 files changed, 193 insertions(+), 98 deletions(-) delete mode 100644 Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/Contents.json delete mode 100644 Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/home_my_spaces_action.svg create mode 100644 Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift create mode 100644 changelog.d/6574.bugfix diff --git a/Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/Contents.json b/Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/Contents.json deleted file mode 100644 index d50c95b02..000000000 --- a/Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "home_my_spaces_action.svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/home_my_spaces_action.svg b/Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/home_my_spaces_action.svg deleted file mode 100644 index 3b956bb6b..000000000 --- a/Riot/Assets/Images.xcassets/Home/home_my_spaces_action.imageset/home_my_spaces_action.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 5ae490f35..0b761a2af 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2006,6 +2006,7 @@ Tap the + to start adding people."; "leave_space_only_action" = "Don't leave any rooms"; "leave_space_and_all_rooms_action" = "Leave all rooms and spaces"; "spaces_explore_rooms" = "Explore rooms"; +"spaces_explore_rooms_format" = "Explore %@"; "spaces_suggested_room" = "Suggested"; "spaces_explore_rooms_room_number" = "%@ rooms"; "spaces_explore_rooms_one_room" = "1 room"; @@ -2097,6 +2098,7 @@ Tap the + to start adding people."; "spaces_creation_in_one_space" = "in 1 space"; "spaces_invite_people" = "Invite people"; +"spaces_invite_people_format" = "Invite to %@"; "spaces_add_room" = "Add room"; "spaces_add_room_missing_permission_message" = "You do not have permissions to add rooms to this space."; "spaces_add_space" = "Add space"; diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index d0373b94f..beb99dc8f 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -115,7 +115,6 @@ internal class Asset: NSObject { internal static let roomActionPriorityLow = ImageAsset(name: "room_action_priority_low") internal static let homeEmptyScreenArtwork = ImageAsset(name: "home_empty_screen_artwork") internal static let homeEmptyScreenArtworkDark = ImageAsset(name: "home_empty_screen_artwork_dark") - internal static let homeMySpacesAction = ImageAsset(name: "home_my_spaces_action") internal static let plusFloatingAction = ImageAsset(name: "plus_floating_action") internal static let versionCheckCloseIcon = ImageAsset(name: "version_check_close_icon") internal static let versionCheckInfoIcon = ImageAsset(name: "version_check_info_icon") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 08c2a34af..ec1d90ca3 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -8047,6 +8047,10 @@ public class VectorL10n: NSObject { public static var spacesExploreRooms: String { return VectorL10n.tr("Vector", "spaces_explore_rooms") } + /// Explore %@ + public static func spacesExploreRoomsFormat(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_explore_rooms_format", p1) + } /// 1 room public static var spacesExploreRoomsOneRoom: String { return VectorL10n.tr("Vector", "spaces_explore_rooms_one_room") @@ -8067,6 +8071,10 @@ public class VectorL10n: NSObject { public static var spacesInvitePeople: String { return VectorL10n.tr("Vector", "spaces_invite_people") } + /// Invite to %@ + public static func spacesInvitePeopleFormat(_ p1: String) -> String { + return VectorL10n.tr("Vector", "spaces_invite_people_format", p1) + } /// Invites coming soon public static var spacesInvitesComingSoonTitle: String { return VectorL10n.tr("Vector", "spaces_invites_coming_soon_title") diff --git a/Riot/Modules/ContextMenu/ActionProviders/AllChatsActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/AllChatsActionProvider.swift index 620d903ea..ded2fe69a 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/AllChatsActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/AllChatsActionProvider.swift @@ -28,7 +28,7 @@ class AllChatsActionProvider { // MARK: - RoomActionProviderProtocol var menu: UIMenu { - return UIMenu(title: VectorL10n.allChatsEditLayout, children: [ + return UIMenu(title: "", children: [ self.recentsAction, self.filtersAction, UIMenu(title: "", options: .displayInline, children: [ diff --git a/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift index af296a4c2..c63b6318f 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/AllChatsEditActionProvider.swift @@ -21,10 +21,6 @@ enum AllChatsEditActionProviderOption { case exploreRooms case createRoom case startChat - case invitePeople - case spaceMembers - case spaceSettings - case leaveSpace case createSpace } @@ -56,30 +52,26 @@ class AllChatsEditActionProvider { var menu: UIMenu { guard parentSpace != nil else { var createActions = [ - self.startChatAction, - self.createRoomAction + self.createRoomAction, + self.startChatAction ] if rootSpaceCount > 0 { - createActions.append(self.createSpaceAction) + createActions.insert(self.createSpaceAction, at: 0) } - return UIMenu(title: VectorL10n.allChatsTitle, children: [ + return UIMenu(title: "", children: [ self.exploreRoomsAction, UIMenu(title: "", options: .displayInline, children: createActions) ]) } - return UIMenu(title: parentName, children: [ + return UIMenu(title: "", children: [ UIMenu(title: "", options: .displayInline, children: [ - self.spaceMembersAction, - self.exploreRoomsAction, - self.spaceSettingsAction + self.exploreRoomsAction ]), UIMenu(title: "", options: .displayInline, children: [ - self.invitePeopleAction, - self.createRoomAction, - self.createSpaceAction - ]), - self.leaveSpaceAction + self.createSpaceAction, + self.createRoomAction + ]) ]) } @@ -139,8 +131,8 @@ class AllChatsEditActionProvider { // MARK: - Private private var exploreRoomsAction: UIAction { - UIAction(title: VectorL10n.spacesExploreRooms, - image: parentSpace == nil ? UIImage(systemName: "list.bullet") : UIImage(systemName: "square.fill.text.grid.1x2")) { [weak self] action in + UIAction(title: parentSpace == nil ? VectorL10n.spacesExploreRooms : VectorL10n.spacesExploreRoomsFormat(parentName), + image: UIImage(systemName: "list.bullet")) { [weak self] action in guard let self = self else { return } self.delegate?.allChatsEditActionProvider(self, didSelect: .exploreRooms) @@ -175,42 +167,4 @@ class AllChatsEditActionProvider { self.delegate?.allChatsEditActionProvider(self, didSelect: .createSpace) } } - - private var invitePeopleAction: UIAction { - UIAction(title: VectorL10n.spacesInvitePeople, - image: UIImage(systemName: "person.badge.plus"), - attributes: isInviteAvailable ? [] : .disabled) { [weak self] action in - guard let self = self else { return } - - self.delegate?.allChatsEditActionProvider(self, didSelect: .invitePeople) - } - } - - private var spaceMembersAction: UIAction { - UIAction(title: VectorL10n.roomDetailsPeople, - image: UIImage(systemName: "person.3")) { [weak self] action in - guard let self = self else { return } - - self.delegate?.allChatsEditActionProvider(self, didSelect: .spaceMembers) - } - } - - private var spaceSettingsAction: UIAction { - UIAction(title: VectorL10n.allChatsEditMenuSpaceSettings, - image: UIImage(systemName: "gearshape")) { [weak self] action in - guard let self = self else { return } - - self.delegate?.allChatsEditActionProvider(self, didSelect: .spaceSettings) - } - } - - private var leaveSpaceAction: UIAction { - UIAction(title: VectorL10n.allChatsEditMenuLeaveSpace(parentName), - image: UIImage(systemName: "rectangle.portrait.and.arrow.right.fill"), - attributes: .destructive) { [weak self] action in - guard let self = self else { return } - - self.delegate?.allChatsEditActionProvider(self, didSelect: .leaveSpace) - } - } } diff --git a/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift new file mode 100644 index 000000000..24020410c --- /dev/null +++ b/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift @@ -0,0 +1,141 @@ +// +// Copyright 2022 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 UIKit +import MatrixSDK + +enum AllChatsSpaceActionProviderOption { + case invitePeople + case spaceMembers + case spaceSettings + case leaveSpace +} + +protocol AllChatsSpaceActionProviderDelegate: AnyObject { + func allChatsSpaceActionProvider(_ actionProvider: AllChatsSpaceActionProvider, didSelect option: AllChatsSpaceActionProviderOption) +} + +/// `AllChatsSpaceActionProvider` provides the menu for accessing space options according to the current space +class AllChatsSpaceActionProvider { + + // MARK: - Properties + + weak var delegate: AllChatsSpaceActionProviderDelegate? + + // MARK: - Private + + private var currentSpace: MXSpace? { + didSet { + spaceName = currentSpace?.summary?.displayname ?? VectorL10n.spaceTag + } + } + private var spaceName: String = VectorL10n.spaceTag + private var isInviteAvailable: Bool = false + + // MARK: - RoomActionProviderProtocol + + var menu: UIMenu { + guard currentSpace != nil else { + return UIMenu(title: "", children: []) + } + + return UIMenu(title: "", children: [ + UIMenu(title: "", options: .displayInline, children: [ + self.spaceSettingsAction, + self.spaceMembersAction, + self.invitePeopleAction + ]), + self.leaveSpaceAction + ]) + } + + // MARK: - Public + + /// Returns an instance of the updated menu accordingly to the given parameters. + /// + /// Some menu items can be disabled depending on the required power levels of the `parentSpace`. Therefore, `updateMenu()` first returns a temporary context menu + /// with all sensible items disabled, asynchronously fetches power levels of the `parentSpace`, then gives a new instance of the menu with, potentially, all sensible items + /// enabled via the `completion` callback. + /// + /// - Parameters: + /// - session: The current `MXSession` instance + /// - space: The current space (`nil` for home space) + /// - completion: callback called once the power levels of the `parentSpace` have been fetched and the menu items have been computed accordingly. + /// - Returns: If the `parentSpace` is `nil`, the context menu, the temporary context menu otherwise. + func updateMenu(with session: MXSession?, space: MXSpace?, completion: @escaping (UIMenu) -> Void) -> UIMenu { + self.currentSpace = space + isInviteAvailable = false + + guard let currentSpace = currentSpace, let spaceRoom = currentSpace.room, let session = session else { + return self.menu + } + + spaceRoom.state { [weak self] roomState in + guard let self = self else { return } + + guard let powerLevels = roomState?.powerLevels, let userId = session.myUserId else { + return + } + let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + + self.isInviteAvailable = userPowerLevel >= powerLevels.invite + + completion(self.menu) + } + + return self.menu + } + + // MARK: - Private + + private var invitePeopleAction: UIAction { + UIAction(title: VectorL10n.spacesInvitePeopleFormat(spaceName), + image: UIImage(systemName: "square.and.arrow.up"), + attributes: isInviteAvailable ? [] : .disabled) { [weak self] action in + guard let self = self else { return } + + self.delegate?.allChatsSpaceActionProvider(self, didSelect: .invitePeople) + } + } + + private var spaceMembersAction: UIAction { + UIAction(title: VectorL10n.roomDetailsPeople, + image: UIImage(systemName: "person")) { [weak self] action in + guard let self = self else { return } + + self.delegate?.allChatsSpaceActionProvider(self, didSelect: .spaceMembers) + } + } + + private var spaceSettingsAction: UIAction { + UIAction(title: VectorL10n.allChatsEditMenuSpaceSettings, + image: UIImage(systemName: "gearshape")) { [weak self] action in + guard let self = self else { return } + + self.delegate?.allChatsSpaceActionProvider(self, didSelect: .spaceSettings) + } + } + + private var leaveSpaceAction: UIAction { + UIAction(title: VectorL10n.allChatsEditMenuLeaveSpace(spaceName), + image: UIImage(systemName: "rectangle.portrait.and.arrow.right.fill"), + attributes: .destructive) { [weak self] action in + guard let self = self else { return } + + self.delegate?.allChatsSpaceActionProvider(self, didSelect: .leaveSpace) + } + } +} diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index cf749e649..bd6bad3cc 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -37,6 +37,8 @@ class AllChatsViewController: HomeViewController { private let searchController = UISearchController(searchResultsController: nil) + private let spaceActionProvider = AllChatsSpaceActionProvider() + private let editActionProvider = AllChatsEditActionProvider() private var spaceSelectorBridgePresenter: SpaceSelectorBottomSheetCoordinatorBridgePresenter? @@ -49,6 +51,7 @@ class AllChatsViewController: HomeViewController { super.viewDidLoad() editActionProvider.delegate = self + spaceActionProvider.delegate = self recentsTableView.tag = RecentsDataSourceMode.allChats.rawValue recentsTableView.clipsToBounds = false @@ -59,7 +62,6 @@ class AllChatsViewController: HomeViewController { searchController.obscuresBackgroundDuringPresentation = false searchController.searchResultsUpdater = self - self.setupEditOptions() NotificationCenter.default.addObserver(self, selector: #selector(self.setupEditOptions), name: AllChatsLayoutSettingsManager.didUpdateSettings, object: nil) } @@ -171,26 +173,37 @@ class AllChatsViewController: HomeViewController { // MARK: - Private @objc private func setupEditOptions() { - self.tabBarController?.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "line.3.horizontal.decrease.circle"), menu: AllChatsActionProvider().menu) + guard let currentSpace = self.dataSource?.currentSpace else { + updateRightNavigationItem(with: AllChatsActionProvider().menu) + return + } + + updateRightNavigationItem(with: spaceActionProvider.updateMenu(with: mainSession, space: currentSpace) { [weak self] menu in + self?.updateRightNavigationItem(with: menu) + }) } private func updateUI() { let currentSpace = self.dataSource?.currentSpace self.tabBarController?.title = currentSpace?.summary?.displayname ?? VectorL10n.allChatsTitle + setupEditOptions() updateToolbar(with: editActionProvider.updateMenu(with: mainSession, parentSpace: currentSpace, completion: { [weak self] menu in self?.updateToolbar(with: menu) })) } + private func updateRightNavigationItem(with menu: UIMenu) { + self.tabBarController?.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu) + } + private func updateToolbar(with menu: UIMenu) { - let currentSpace = self.dataSource?.currentSpace self.navigationController?.isToolbarHidden = false self.update(with: ThemeService.shared().theme) self.tabBarController?.setToolbarItems([ - UIBarButtonItem(image: Asset.Images.homeMySpacesAction.image, style: .done, target: self, action: #selector(self.showSpaceSelectorAction(sender: ))), + UIBarButtonItem(image: UIImage(systemName: "square.grid.2x2"), style: .done, target: self, action: #selector(self.showSpaceSelectorAction(sender: ))), UIBarButtonItem.flexibleSpace(), - UIBarButtonItem(image: UIImage(systemName: currentSpace == nil ? "square.and.pencil" : "ellipsis.circle"), menu: menu) + UIBarButtonItem(image: UIImage(systemName: "square.and.pencil"), menu: menu) ], animated: true) } @@ -393,6 +406,17 @@ extension AllChatsViewController: AllChatsEditActionProviderDelegate { createNewRoom() case .startChat: startChat() + case .createSpace: + showCreateSpace(parentSpaceId: dataSource.currentSpace?.spaceId) + } + } + +} + +// MARK: - AllChatsSpaceActionProviderDelegate +extension AllChatsViewController: AllChatsSpaceActionProviderDelegate { + func allChatsSpaceActionProvider(_ actionProvider: AllChatsSpaceActionProvider, didSelect option: AllChatsSpaceActionProviderOption) { + switch option { case .invitePeople: showSpaceInvite() case .spaceMembers: @@ -401,11 +425,8 @@ extension AllChatsViewController: AllChatsEditActionProviderDelegate { showSpaceSettings() case .leaveSpace: showLeaveSpace() - case .createSpace: - showCreateSpace(parentSpaceId: dataSource.currentSpace?.spaceId) } } - } // MARK: - ContactsPickerCoordinatorDelegate diff --git a/changelog.d/6574.bugfix b/changelog.d/6574.bugfix new file mode 100644 index 000000000..b451e9f27 --- /dev/null +++ b/changelog.d/6574.bugfix @@ -0,0 +1 @@ +App Layout: updated context menus according to last design update From 0128c1a3fe34e78751bb50660538c03b5558aa35 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Thu, 18 Aug 2022 16:34:04 +0200 Subject: [PATCH 31/80] App Layout: Context Menus - Updated avatar menu --- Riot/Assets/en.lproj/Vector.strings | 2 +- Riot/Generated/Strings.swift | 8 ++++---- .../ActionProviders/AllChatsSpaceActionProvider.swift | 2 +- Riot/Modules/TabBar/TabBarCoordinator.swift | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 0b761a2af..1a8152b55 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -77,6 +77,7 @@ "suggest" = "Suggest"; "edit" = "Edit"; "confirm" = "Confirm"; +"invite_to" = "Invite to %@"; // Activities "loading" = "Loading"; @@ -2098,7 +2099,6 @@ Tap the + to start adding people."; "spaces_creation_in_one_space" = "in 1 space"; "spaces_invite_people" = "Invite people"; -"spaces_invite_people_format" = "Invite to %@"; "spaces_add_room" = "Add room"; "spaces_add_room_missing_permission_message" = "You do not have permissions to add rooms to this space."; "spaces_add_space" = "Add space"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index ec1d90ca3..cf2b7fa68 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2491,6 +2491,10 @@ public class VectorL10n: NSObject { public static func inviteFriendsShareText(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "invite_friends_share_text", p1, p2) } + /// Invite to %@ + public static func inviteTo(_ p1: String) -> String { + return VectorL10n.tr("Vector", "invite_to", p1) + } /// Invite matrix User public static var inviteUser: String { return VectorL10n.tr("Vector", "invite_user") @@ -8071,10 +8075,6 @@ public class VectorL10n: NSObject { public static var spacesInvitePeople: String { return VectorL10n.tr("Vector", "spaces_invite_people") } - /// Invite to %@ - public static func spacesInvitePeopleFormat(_ p1: String) -> String { - return VectorL10n.tr("Vector", "spaces_invite_people_format", p1) - } /// Invites coming soon public static var spacesInvitesComingSoonTitle: String { return VectorL10n.tr("Vector", "spaces_invites_coming_soon_title") diff --git a/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift index 24020410c..0d9e3d2fa 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/AllChatsSpaceActionProvider.swift @@ -102,7 +102,7 @@ class AllChatsSpaceActionProvider { // MARK: - Private private var invitePeopleAction: UIAction { - UIAction(title: VectorL10n.spacesInvitePeopleFormat(spaceName), + UIAction(title: VectorL10n.inviteTo(spaceName), image: UIImage(systemName: "square.and.arrow.up"), attributes: isInviteAvailable ? [] : .disabled) { [weak self] action in guard let self = self else { return } diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 221986e14..d059aa5d3 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -743,13 +743,13 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { private func createAvatarButtonItem(for viewController: UIViewController) { var actions: [UIMenuElement] = [] - actions.append(UIAction(title: VectorL10n.allChatsUserMenuSettings, image: UIImage(systemName: "gearshape")) { [weak self] action in + actions.append(UIAction(title: VectorL10n.settings, image: UIImage(systemName: "gearshape")) { [weak self] action in self?.showSettings() }) var subMenuActions: [UIAction] = [] if BuildSettings.sideMenuShowInviteFriends { - subMenuActions.append(UIAction(title: VectorL10n.sideMenuActionInviteFriends, image: UIImage(systemName: "square.and.arrow.up.fill")) { [weak self] action in + subMenuActions.append(UIAction(title: VectorL10n.inviteTo(AppInfo.current.displayName), image: UIImage(systemName: "envelope")) { [weak self] action in self?.showInviteFriends(from: nil) }) } From 49851694ebbddca8762063d2d4fb7ddba516a9cc Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Thu, 18 Aug 2022 17:44:24 +0200 Subject: [PATCH 32/80] App Layout: reintroduced existing Notification left markers on room cells --- Riot/Modules/Common/Recents/Views/RecentTableViewCell.m | 2 +- changelog.d/6578.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6578.bugfix diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m index 2e3052fc0..6cd41134f 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -95,7 +95,7 @@ // Notify unreads and bing if (roomCellData.hasUnread) { - self.missedNotifAndUnreadIndicator.hidden = BuildSettings.newAppLayoutEnabled; + self.missedNotifAndUnreadIndicator.hidden = NO; if (0 < roomCellData.notificationCount) { diff --git a/changelog.d/6578.bugfix b/changelog.d/6578.bugfix new file mode 100644 index 000000000..d0f1fa45e --- /dev/null +++ b/changelog.d/6578.bugfix @@ -0,0 +1 @@ +App Layout: reintroduced existing Notification left markers on room cells From 34db12c94d67ac16ee5bf32dd3a156935bce9e65 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Thu, 18 Aug 2022 17:48:55 +0200 Subject: [PATCH 33/80] App Layout: Leaving a Space now sends user to All Chats --- Riot/Modules/Home/AllChats/AllChatsViewController.swift | 2 ++ changelog.d/6581.bugfix | 1 + 2 files changed, 3 insertions(+) create mode 100644 changelog.d/6581.bugfix diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index cf749e649..a0f9cfbe1 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -314,6 +314,8 @@ class AllChatsViewController: HomeViewController { coordinator.start() add(childCoordinator: coordinator) coordinator.completion = { [weak self] result in + // switching to home space + self?.switchSpace(withId: nil) coordinator.toPresentable().dismiss(animated: true) { self?.remove(childCoordinator: coordinator) } diff --git a/changelog.d/6581.bugfix b/changelog.d/6581.bugfix new file mode 100644 index 000000000..e2a60be27 --- /dev/null +++ b/changelog.d/6581.bugfix @@ -0,0 +1 @@ +App Layout: Leaving a Space now sends user to All Chats \ No newline at end of file From 5fecaea4534e607020d8606e17abb4a1c8724ef5 Mon Sep 17 00:00:00 2001 From: Doug Date: Thu, 18 Aug 2022 11:50:08 +0100 Subject: [PATCH 34/80] Revert "Replace DesignKit with package from ElementX." This reverts the following commits: ef5365ab240a1449e0490d2eb011dd69f594e27b 545b641e53a845b722f571b48ab408000048714b 702b7a696dd1a8319d6af907d4766a035cf30234 2398c1534dd1ca5b6329c888c50f55fb1b0ec23f --- DesignKit/Common.xcconfig | 28 ++++ DesignKit/Debug.xcconfig | 20 +++ DesignKit/DesignKit.h | 27 ++++ DesignKit/Extensions/UIFont.swift | 55 +++++++ DesignKit/Info.plist | 22 +++ DesignKit/Release.xcconfig | 20 +++ .../Source}/AvatarSize.swift | 2 - DesignKit/Source/ColorValues.swift | 52 ++++++ DesignKit/Source/Colors.swift | 73 +++++++++ DesignKit/Source/ColorsSwiftUI.swift | 69 ++++++++ DesignKit/Source/ColorsUIkit.swift | 67 ++++++++ DesignKit/Source/Fonts.swift | 85 ++++++++++ DesignKit/Source/FontsSwiftUI.swift | 91 +++++++++++ DesignKit/Source/FontsUIkit.swift | 87 ++++++++++ .../Theme => DesignKit/Source}/ThemeV2.swift | 19 ++- .../Variants/Colors/Dark/DarkColors.swift | 51 ++++++ .../Variants/Colors/Light/LightColors.swift | 57 +++++++ DesignKit/Variants/Fonts/ElementFonts.swift | 150 ++++++++++++++++++ DesignKit/target.yml | 33 ++++ .../xcshareddata/swiftpm/Package.resolved | 27 ---- .../Theme/ElementUIColorsResolved.swift | 82 ---------- Riot/Managers/Theme/Themes/DarkTheme.swift | 6 +- Riot/Managers/Theme/Themes/DefaultTheme.swift | 7 +- Riot/target.yml | 2 +- RiotShareExtension/target.yml | 2 - .../Common/Avatar/View/AvatarImage.swift | 2 +- .../Avatar/View/PlaceholderAvatarImage.swift | 2 +- .../Common/Avatar/View/SpaceAvatarImage.swift | 6 +- .../Common/EffectsScene/EffectsScene.swift | 2 +- .../Modules/Common/Theme/ThemeSwiftUI.swift | 25 +-- .../Theme/ThemeUsersColorsExtension.swift | 4 +- .../Theme/Themes/DarkThemeSwiftUI.swift | 6 +- .../Theme/Themes/DefaultThemeSwiftUI.swift | 6 +- .../Common/Util/BorderedInputFieldStyle.swift | 2 +- .../Common/Util/ClearViewModifier.swift | 2 +- .../Common/Util/MultilineTextField.swift | 4 +- .../Modules/Common/Util/OptionButton.swift | 2 +- .../Modules/Common/Util/SearchBar.swift | 6 +- .../Util/SecondaryActionButtonStyle.swift | 2 +- .../Modules/Common/Util/WaitOverlay.swift | 2 +- .../OnboardingAvatarCoordinator.swift | 2 +- .../MockOnboardingAvatarScreenState.swift | 2 +- .../Unit/OnboardingAvatarViewModelTests.swift | 2 +- .../OnboardingSplashScreenPageIndicator.swift | 2 +- .../View/RoomAccessTypeChooserRow.swift | 2 +- ...RestrictedAccessSpaceChooserSelector.swift | 4 +- .../View/TimelinePollAnswerOptionButton.swift | 4 +- .../View/FormInputFieldStyle.swift | 2 +- .../View/MatrixItemChooser.swift | 2 +- .../View/MatrixItemChooserSectionHeader.swift | 2 +- .../SpaceSettings/View/SpaceSettings.swift | 2 +- .../View/SpaceSettingsOptionListItem.swift | 2 +- RiotSwiftUI/target.yml | 2 +- RiotSwiftUI/targetUITests.yml | 1 - project.yml | 4 +- 55 files changed, 1051 insertions(+), 191 deletions(-) create mode 100644 DesignKit/Common.xcconfig create mode 100644 DesignKit/Debug.xcconfig create mode 100644 DesignKit/DesignKit.h create mode 100644 DesignKit/Extensions/UIFont.swift create mode 100644 DesignKit/Info.plist create mode 100644 DesignKit/Release.xcconfig rename {RiotSwiftUI/Modules/Common/Avatar/Model => DesignKit/Source}/AvatarSize.swift (95%) create mode 100644 DesignKit/Source/ColorValues.swift create mode 100644 DesignKit/Source/Colors.swift create mode 100644 DesignKit/Source/ColorsSwiftUI.swift create mode 100644 DesignKit/Source/ColorsUIkit.swift create mode 100644 DesignKit/Source/Fonts.swift create mode 100644 DesignKit/Source/FontsSwiftUI.swift create mode 100644 DesignKit/Source/FontsUIkit.swift rename {Riot/Managers/Theme => DesignKit/Source}/ThemeV2.swift (70%) create mode 100644 DesignKit/Variants/Colors/Dark/DarkColors.swift create mode 100644 DesignKit/Variants/Colors/Light/LightColors.swift create mode 100644 DesignKit/Variants/Fonts/ElementFonts.swift create mode 100644 DesignKit/target.yml delete mode 100644 Riot/Managers/Theme/ElementUIColorsResolved.swift diff --git a/DesignKit/Common.xcconfig b/DesignKit/Common.xcconfig new file mode 100644 index 000000000..eb4b88c16 --- /dev/null +++ b/DesignKit/Common.xcconfig @@ -0,0 +1,28 @@ +// +// Copyright 2021 Vector Creations 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. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include "Config/AppIdentifiers.xcconfig" +#include "Config/AppVersion.xcconfig" + +PRODUCT_NAME = DesignKit +PRODUCT_BUNDLE_IDENTIFIER = $(BASE_BUNDLE_IDENTIFIER).designkit + +INFOPLIST_FILE = DesignKit/Info.plist + +SKIP_INSTALL = YES diff --git a/DesignKit/Debug.xcconfig b/DesignKit/Debug.xcconfig new file mode 100644 index 000000000..11a7288a4 --- /dev/null +++ b/DesignKit/Debug.xcconfig @@ -0,0 +1,20 @@ +// +// Copyright 2021 Vector Creations 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. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include "Common.xcconfig" diff --git a/DesignKit/DesignKit.h b/DesignKit/DesignKit.h new file mode 100644 index 000000000..4ff68e722 --- /dev/null +++ b/DesignKit/DesignKit.h @@ -0,0 +1,27 @@ +// +// 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 + +//! Project version number for DesignKit. +FOUNDATION_EXPORT double DesignKitVersionNumber; + +//! Project version string for DesignKit. +FOUNDATION_EXPORT const unsigned char DesignKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/DesignKit/Extensions/UIFont.swift b/DesignKit/Extensions/UIFont.swift new file mode 100644 index 000000000..7804c8066 --- /dev/null +++ b/DesignKit/Extensions/UIFont.swift @@ -0,0 +1,55 @@ +// +// 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 UIKit + +public extension UIFont { + + // MARK: - Convenient methods + + /// Update current font with a SymbolicTraits + func vc_withTraits(_ traits: UIFontDescriptor.SymbolicTraits) -> UIFont { + guard let descriptor = fontDescriptor.withSymbolicTraits(traits) else { + return self + } + return UIFont(descriptor: descriptor, size: 0) // Size 0 means keep the size as it is + } + + /// Update current font with a given Weight + func vc_withWeight(weight: Weight) -> UIFont { + // Add the font weight to the descriptor + let weightedFontDescriptor = fontDescriptor.addingAttributes([ + UIFontDescriptor.AttributeName.traits: [ + UIFontDescriptor.TraitKey.weight: weight + ] + ]) + return UIFont(descriptor: weightedFontDescriptor, size: 0) + } + + // MARK: - Shortcuts + + var vc_bold: UIFont { + return self.vc_withTraits(.traitBold) + } + + var vc_semiBold: UIFont { + return self.vc_withWeight(weight: .semibold) + } + + var vc_italic: UIFont { + return self.vc_withTraits(.traitItalic) + } +} diff --git a/DesignKit/Info.plist b/DesignKit/Info.plist new file mode 100644 index 000000000..c0701c6d7 --- /dev/null +++ b/DesignKit/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/DesignKit/Release.xcconfig b/DesignKit/Release.xcconfig new file mode 100644 index 000000000..11a7288a4 --- /dev/null +++ b/DesignKit/Release.xcconfig @@ -0,0 +1,20 @@ +// +// Copyright 2021 Vector Creations 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. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include "Common.xcconfig" diff --git a/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarSize.swift b/DesignKit/Source/AvatarSize.swift similarity index 95% rename from RiotSwiftUI/Modules/Common/Avatar/Model/AvatarSize.swift rename to DesignKit/Source/AvatarSize.swift index 09daff1d2..bac46e6f3 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Model/AvatarSize.swift +++ b/DesignKit/Source/AvatarSize.swift @@ -17,8 +17,6 @@ import Foundation import UIKit -// TODO: Move into element-design-tokens repo. - // Figma Avatar Sizes: https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1258%3A19678 public enum AvatarSize: Int { case xxSmall = 16 diff --git a/DesignKit/Source/ColorValues.swift b/DesignKit/Source/ColorValues.swift new file mode 100644 index 000000000..338d1cfe8 --- /dev/null +++ b/DesignKit/Source/ColorValues.swift @@ -0,0 +1,52 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import UIKit + +/** + Struct for holding colour values for a particular theme. + */ +public struct ColorValues: Colors { + + public let accent: UIColor + + public let alert: UIColor + + public let primaryContent: UIColor + + public let secondaryContent: UIColor + + public let tertiaryContent: UIColor + + public let quarterlyContent: UIColor + + public let quinaryContent: UIColor + + public let separator: UIColor + + public let system: UIColor + + public let tile: UIColor + + public let navigation: UIColor + + public let background: UIColor + + public let ems: UIColor + + public let namesAndAvatars: [UIColor] +} diff --git a/DesignKit/Source/Colors.swift b/DesignKit/Source/Colors.swift new file mode 100644 index 000000000..bf3e9abd3 --- /dev/null +++ b/DesignKit/Source/Colors.swift @@ -0,0 +1,73 @@ +// +// 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 + +/// Colors at https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1255%3A1104 +public protocol Colors { + + associatedtype ColorType + + /// - Focused/Active states + /// - CTAs + var accent: ColorType { get } + + /// - Error messages + /// - Content requiring user attention + /// - Notification, alerts + var alert: ColorType { get } + + /// - Text + /// - Icons + var primaryContent: ColorType { get } + + /// - Text + /// - Icons + var secondaryContent: ColorType { get } + + /// - Text + /// - Icons + var tertiaryContent: ColorType { get } + + /// - Text + /// - Icons + var quarterlyContent: ColorType { get } + + /// - separating lines and other UI components + var quinaryContent: ColorType { get } + + /// - System-based areas and backgrounds + var system: ColorType { get } + + /// Separating line + var separator: ColorType { get } + + /// Cards, tiles + var tile: ColorType { get } + + /// Top navigation background on iOS + var navigation: ColorType { get } + + /// Background UI color + var background: ColorType { get } + + /// Global color: The EMS brand's purple colour. + var ems: ColorType { get } + + /// - Names in chat timeline + /// - Avatars default states that include first name letter + var namesAndAvatars: [ColorType] { get } +} diff --git a/DesignKit/Source/ColorsSwiftUI.swift b/DesignKit/Source/ColorsSwiftUI.swift new file mode 100644 index 000000000..ea3ca6779 --- /dev/null +++ b/DesignKit/Source/ColorsSwiftUI.swift @@ -0,0 +1,69 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import SwiftUI + +/** + Struct for holding colors for use in SwiftUI. + */ +public struct ColorSwiftUI: Colors { + + public let accent: Color + + public let alert: Color + + public let primaryContent: Color + + public let secondaryContent: Color + + public let tertiaryContent: Color + + public let quarterlyContent: Color + + public let quinaryContent: Color + + public let separator: Color + + public var system: Color + + public let tile: Color + + public let navigation: Color + + public let background: Color + + public var ems: Color + + public let namesAndAvatars: [Color] + + init(values: ColorValues) { + accent = Color(values.accent) + alert = Color(values.alert) + primaryContent = Color(values.primaryContent) + secondaryContent = Color(values.secondaryContent) + tertiaryContent = Color(values.tertiaryContent) + quarterlyContent = Color(values.quarterlyContent) + quinaryContent = Color(values.quinaryContent) + separator = Color(values.separator) + system = Color(values.system) + tile = Color(values.tile) + navigation = Color(values.navigation) + background = Color(values.background) + ems = Color(values.ems) + namesAndAvatars = values.namesAndAvatars.map({ Color($0) }) + } +} diff --git a/DesignKit/Source/ColorsUIkit.swift b/DesignKit/Source/ColorsUIkit.swift new file mode 100644 index 000000000..3add385c3 --- /dev/null +++ b/DesignKit/Source/ColorsUIkit.swift @@ -0,0 +1,67 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import UIKit + +/** + ObjC class for holding colors for use in UIKit. + */ +@objcMembers public class ColorsUIKit: NSObject { + + public let accent: UIColor + + public let alert: UIColor + + public let primaryContent: UIColor + + public let secondaryContent: UIColor + + public let tertiaryContent: UIColor + + public let quarterlyContent: UIColor + + public let quinaryContent: UIColor + + public let separator: UIColor + + public let system: UIColor + + public let tile: UIColor + + public let navigation: UIColor + + public let background: UIColor + + public let namesAndAvatars: [UIColor] + + init(values: ColorValues) { + accent = values.accent + alert = values.alert + primaryContent = values.primaryContent + secondaryContent = values.secondaryContent + tertiaryContent = values.tertiaryContent + quarterlyContent = values.quarterlyContent + quinaryContent = values.quinaryContent + separator = values.separator + system = values.system + tile = values.tile + navigation = values.navigation + background = values.background + namesAndAvatars = values.namesAndAvatars + } +} + diff --git a/DesignKit/Source/Fonts.swift b/DesignKit/Source/Fonts.swift new file mode 100644 index 000000000..1203a2888 --- /dev/null +++ b/DesignKit/Source/Fonts.swift @@ -0,0 +1,85 @@ +// +// 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 UIKit + +/// Describe fonts used in the application. +/// Font names are based on Element typograhy https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1362%3A0 which is based on Apple font text styles (UIFont.TextStyle): https://developer.apple.com/documentation/uikit/uifonttextstyle +/// Create a custom TextStyle enum (like DesignKit.Fonts.TextStyle) is also a possiblity +public protocol Fonts { + + associatedtype FontType + + /// The font for large titles. + var largeTitle: FontType { get } + + /// `largeTitle` with a Bold weight. + var largeTitleB: FontType { get } + + /// The font for first-level hierarchical headings. + var title1: FontType { get } + + /// `title1` with a Bold weight. + var title1B: FontType { get } + + /// The font for second-level hierarchical headings. + var title2: FontType { get } + + /// `title2` with a Bold weight. + var title2B: FontType { get } + + /// The font for third-level hierarchical headings. + var title3: FontType { get } + + /// `title3` with a Semi Bold weight. + var title3SB: FontType { get } + + /// The font for headings. + var headline: FontType { get } + + /// The font for subheadings. + var subheadline: FontType { get } + + /// The font for body text. + var body: FontType { get } + + /// `body` with a Semi Bold weight. + var bodySB: FontType { get } + + /// The font for callouts. + var callout: FontType { get } + + /// `callout` with a Semi Bold weight. + var calloutSB: FontType { get } + + /// The font for footnotes. + var footnote: FontType { get } + + /// `footnote` with a Semi Bold weight. + var footnoteSB: FontType { get } + + /// The font for standard captions. + var caption1: FontType { get } + + /// `caption1` with a Semi Bold weight. + var caption1SB: FontType { get } + + /// The font for alternate captions. + var caption2: FontType { get } + + /// `caption2` with a Semi Bold weight. + var caption2SB: FontType { get } +} diff --git a/DesignKit/Source/FontsSwiftUI.swift b/DesignKit/Source/FontsSwiftUI.swift new file mode 100644 index 000000000..83b4e820b --- /dev/null +++ b/DesignKit/Source/FontsSwiftUI.swift @@ -0,0 +1,91 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import SwiftUI + +/** + Struct for holding fonts for use in SwiftUI. + */ +public struct FontSwiftUI: Fonts { + + public let uiFonts: FontsUIKit + + public var largeTitle: Font + + public var largeTitleB: Font + + public var title1: Font + + public var title1B: Font + + public var title2: Font + + public var title2B: Font + + public var title3: Font + + public var title3SB: Font + + public var headline: Font + + public var subheadline: Font + + public var body: Font + + public var bodySB: Font + + public var callout: Font + + public var calloutSB: Font + + public var footnote: Font + + public var footnoteSB: Font + + public var caption1: Font + + public var caption1SB: Font + + public var caption2: Font + + public var caption2SB: Font + + public init(values: ElementFonts) { + self.uiFonts = FontsUIKit(values: values) + + self.largeTitle = values.largeTitle.font + self.largeTitleB = values.largeTitleB.font + self.title1 = values.title1.font + self.title1B = values.title1B.font + self.title2 = values.title2.font + self.title2B = values.title2B.font + self.title3 = values.title3.font + self.title3SB = values.title3SB.font + self.headline = values.headline.font + self.subheadline = values.subheadline.font + self.body = values.body.font + self.bodySB = values.bodySB.font + self.callout = values.callout.font + self.calloutSB = values.calloutSB.font + self.footnote = values.footnote.font + self.footnoteSB = values.footnoteSB.font + self.caption1 = values.caption1.font + self.caption1SB = values.caption1SB.font + self.caption2 = values.caption2.font + self.caption2SB = values.caption2SB.font + } +} diff --git a/DesignKit/Source/FontsUIkit.swift b/DesignKit/Source/FontsUIkit.swift new file mode 100644 index 000000000..ec65cdaa6 --- /dev/null +++ b/DesignKit/Source/FontsUIkit.swift @@ -0,0 +1,87 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import UIKit + +/** + ObjC class for holding fonts for use in UIKit. + */ +@objcMembers public class FontsUIKit: NSObject, Fonts { + + public var largeTitle: UIFont + + public var largeTitleB: UIFont + + public var title1: UIFont + + public var title1B: UIFont + + public var title2: UIFont + + public var title2B: UIFont + + public var title3: UIFont + + public var title3SB: UIFont + + public var headline: UIFont + + public var subheadline: UIFont + + public var body: UIFont + + public var bodySB: UIFont + + public var callout: UIFont + + public var calloutSB: UIFont + + public var footnote: UIFont + + public var footnoteSB: UIFont + + public var caption1: UIFont + + public var caption1SB: UIFont + + public var caption2: UIFont + + public var caption2SB: UIFont + + public init(values: ElementFonts) { + self.largeTitle = values.largeTitle.uiFont + self.largeTitleB = values.largeTitleB.uiFont + self.title1 = values.title1.uiFont + self.title1B = values.title1B.uiFont + self.title2 = values.title2.uiFont + self.title2B = values.title2B.uiFont + self.title3 = values.title3.uiFont + self.title3SB = values.title3SB.uiFont + self.headline = values.headline.uiFont + self.subheadline = values.subheadline.uiFont + self.body = values.body.uiFont + self.bodySB = values.bodySB.uiFont + self.callout = values.callout.uiFont + self.calloutSB = values.calloutSB.uiFont + self.footnote = values.footnote.uiFont + self.footnoteSB = values.footnoteSB.uiFont + self.caption1 = values.caption1.uiFont + self.caption1SB = values.caption1SB.uiFont + self.caption2 = values.caption2.uiFont + self.caption2SB = values.caption2SB.uiFont + } +} diff --git a/Riot/Managers/Theme/ThemeV2.swift b/DesignKit/Source/ThemeV2.swift similarity index 70% rename from Riot/Managers/Theme/ThemeV2.swift rename to DesignKit/Source/ThemeV2.swift index 56a97a93a..dedc4d6df 100644 --- a/Riot/Managers/Theme/ThemeV2.swift +++ b/DesignKit/Source/ThemeV2.swift @@ -14,18 +14,29 @@ // limitations under the License. // +import Foundation import UIKit -import DesignKit -import DesignTokens /// Theme v2. May be named again as `Theme` when the migration completed. @objc public protocol ThemeV2 { /// Colors object - var colors: ElementUIColorsResolved { get } + var colors: ColorsUIKit { get } /// Fonts object - var fonts: ElementUIFonts { get } + var fonts: FontsUIKit { get } + + /// may contain more design components in future, like icons, audio files etc. +} + +/// Theme v2 for SwiftUI. +public protocol ThemeSwiftUIType { + + /// Colors object + var colors: ColorSwiftUI { get } + + /// Fonts object + var fonts: FontSwiftUI { get } /// may contain more design components in future, like icons, audio files etc. } diff --git a/DesignKit/Variants/Colors/Dark/DarkColors.swift b/DesignKit/Variants/Colors/Dark/DarkColors.swift new file mode 100644 index 000000000..88bd12ff3 --- /dev/null +++ b/DesignKit/Variants/Colors/Dark/DarkColors.swift @@ -0,0 +1,51 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import UIKit +import SwiftUI + +/// Dark theme colors. +public class DarkColors { + private static let values = ColorValues( + accent: UIColor(rgb:0x0DBD8B), + alert: UIColor(rgb:0xFF4B55), + primaryContent: UIColor(rgb:0xFFFFFF), + secondaryContent: UIColor(rgb:0xA9B2BC), + tertiaryContent: UIColor(rgb:0x8E99A4), + quarterlyContent: UIColor(rgb:0x6F7882), + quinaryContent: UIColor(rgb:0x394049), + separator: UIColor(rgb:0x21262C), + system: UIColor(rgb:0x21262C), + tile: UIColor(rgb:0x394049), + navigation: UIColor(rgb:0x21262C), + background: UIColor(rgb:0x15191E), + ems: UIColor(rgb: 0x7E69FF), + namesAndAvatars: [ + UIColor(rgb:0x368BD6), + UIColor(rgb:0xAC3BA8), + UIColor(rgb:0x03B381), + UIColor(rgb:0xE64F7A), + UIColor(rgb:0xFF812D), + UIColor(rgb:0x2DC2C5), + UIColor(rgb:0x5C56F5), + UIColor(rgb:0x74D12C) + ] + ) + + public static var uiKit = ColorsUIKit(values: values) + public static var swiftUI = ColorSwiftUI(values: values) +} diff --git a/DesignKit/Variants/Colors/Light/LightColors.swift b/DesignKit/Variants/Colors/Light/LightColors.swift new file mode 100644 index 000000000..93cb3eadb --- /dev/null +++ b/DesignKit/Variants/Colors/Light/LightColors.swift @@ -0,0 +1,57 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import UIKit +import SwiftUI + + +/// Light theme colors. +public class LightColors { + private static let values = ColorValues( + accent: UIColor(rgb:0x0DBD8B), + alert: UIColor(rgb:0xFF4B55), + primaryContent: UIColor(rgb:0x17191C), + secondaryContent: UIColor(rgb:0x737D8C), + tertiaryContent: UIColor(rgb:0x8D97A5), + quarterlyContent: UIColor(rgb:0xC1C6CD), + quinaryContent: UIColor(rgb:0xE3E8F0), + separator: UIColor(rgb:0xE3E8F0), + system: UIColor(rgb:0xF4F6FA), + tile: UIColor(rgb:0xF3F8FD), + navigation: UIColor(rgb:0xF4F6FA), + background: UIColor(rgb:0xFFFFFF), + ems: UIColor(rgb: 0x7E69FF), + namesAndAvatars: [ + UIColor(rgb:0x368BD6), + UIColor(rgb:0xAC3BA8), + UIColor(rgb:0x03B381), + UIColor(rgb:0xE64F7A), + UIColor(rgb:0xFF812D), + UIColor(rgb:0x2DC2C5), + UIColor(rgb:0x5C56F5), + UIColor(rgb:0x74D12C) + ] + ) + + public static var uiKit = ColorsUIKit(values: values) + public static var swiftUI = ColorSwiftUI(values: values) +} + + + + + diff --git a/DesignKit/Variants/Fonts/ElementFonts.swift b/DesignKit/Variants/Fonts/ElementFonts.swift new file mode 100644 index 000000000..e0a612f85 --- /dev/null +++ b/DesignKit/Variants/Fonts/ElementFonts.swift @@ -0,0 +1,150 @@ +// +// 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 SwiftUI + +/// Fonts at https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1362%3A0 +@objcMembers +public class ElementFonts { + + // MARK: - Types + + /// A wrapper to provide both a `UIFont` and a SwiftUI `Font` in the same type. + /// The need for this comes from `Font` not adapting for dynamic type until the app + /// is restarted (or working at all in Xcode Previews) when initialised from a `UIFont` + /// (even if that font was created with the appropriate metrics). + public struct SharedFont { + public let uiFont: UIFont + public let font: Font + } + + // MARK: - Setup + + public init() { + } + + // MARK: - Private + + /// Returns an instance of the font associated with the text style and scaled appropriately for the content size category defined in the trait collection. + /// Keep this method private method at the moment and create a DesignKit.Fonts.TextStyle if needed. + fileprivate func font(forTextStyle textStyle: UIFont.TextStyle, compatibleWith traitCollection: UITraitCollection? = nil) -> UIFont { + return UIFont.preferredFont(forTextStyle: textStyle, compatibleWith: traitCollection) + } +} + +// MARK: - Fonts protocol +extension ElementFonts: Fonts { + + public var largeTitle: SharedFont { + let uiFont = self.font(forTextStyle: .largeTitle) + return SharedFont(uiFont: uiFont, font: .largeTitle) + } + + public var largeTitleB: SharedFont { + let uiFont = self.largeTitle.uiFont.vc_bold + return SharedFont(uiFont: uiFont, font: .largeTitle.bold()) + } + + public var title1: SharedFont { + let uiFont = self.font(forTextStyle: .title1) + return SharedFont(uiFont: uiFont, font: .title) + } + + public var title1B: SharedFont { + let uiFont = self.title1.uiFont.vc_bold + return SharedFont(uiFont: uiFont, font: .title.bold()) + } + + public var title2: SharedFont { + let uiFont = self.font(forTextStyle: .title2) + return SharedFont(uiFont: uiFont, font: .title2) + } + + public var title2B: SharedFont { + let uiFont = self.title2.uiFont.vc_bold + return SharedFont(uiFont: uiFont, font: .title2.bold()) + } + + public var title3: SharedFont { + let uiFont = self.font(forTextStyle: .title3) + return SharedFont(uiFont: uiFont, font: .title3) + } + + public var title3SB: SharedFont { + let uiFont = self.title3.uiFont.vc_semiBold + return SharedFont(uiFont: uiFont, font: .title3.weight(.semibold)) + } + + public var headline: SharedFont { + let uiFont = self.font(forTextStyle: .headline) + return SharedFont(uiFont: uiFont, font: .headline) + } + + public var subheadline: SharedFont { + let uiFont = self.font(forTextStyle: .subheadline) + return SharedFont(uiFont: uiFont, font: .subheadline) + } + + public var body: SharedFont { + let uiFont = self.font(forTextStyle: .body) + return SharedFont(uiFont: uiFont, font: .body) + } + + public var bodySB: SharedFont { + let uiFont = self.body.uiFont.vc_semiBold + return SharedFont(uiFont: uiFont, font: .body.weight(.semibold)) + } + + public var callout: SharedFont { + let uiFont = self.font(forTextStyle: .callout) + return SharedFont(uiFont: uiFont, font: .callout) + } + + public var calloutSB: SharedFont { + let uiFont = self.callout.uiFont.vc_semiBold + return SharedFont(uiFont: uiFont, font: .callout.weight(.semibold)) + } + + public var footnote: SharedFont { + let uiFont = self.font(forTextStyle: .footnote) + return SharedFont(uiFont: uiFont, font: .footnote) + } + + public var footnoteSB: SharedFont { + let uiFont = self.footnote.uiFont.vc_semiBold + return SharedFont(uiFont: uiFont, font: .footnote.weight(.semibold)) + } + + public var caption1: SharedFont { + let uiFont = self.font(forTextStyle: .caption1) + return SharedFont(uiFont: uiFont, font: .caption) + } + + public var caption1SB: SharedFont { + let uiFont = self.caption1.uiFont.vc_semiBold + return SharedFont(uiFont: uiFont, font: .caption.weight(.semibold)) + } + + public var caption2: SharedFont { + let uiFont = self.font(forTextStyle: .caption2) + return SharedFont(uiFont: uiFont, font: .caption2) + } + + public var caption2SB: SharedFont { + let uiFont = self.caption2.uiFont.vc_semiBold + return SharedFont(uiFont: uiFont, font: .caption2.weight(.semibold)) + } +} diff --git a/DesignKit/target.yml b/DesignKit/target.yml new file mode 100644 index 000000000..e10f76f12 --- /dev/null +++ b/DesignKit/target.yml @@ -0,0 +1,33 @@ +name: DesignKit + +schemes: + DesignKit: + analyze: + config: Debug + archive: + config: Release + build: + targets: + DesignKit: + - running + - profiling + - analyzing + - archiving + profile: + config: Release + run: + config: Debug + disableMainThreadChecker: true + +targets: + DesignKit: + type: framework + platform: iOS + + configFiles: + Debug: Debug.xcconfig + Release: Release.xcconfig + + sources: + - path: . + - path: ../Riot/Categories/UIColor.swift diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 899701b2f..a4ed2f4c0 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,23 +1,5 @@ { "pins" : [ - { - "identity" : "element-design-tokens", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/element-design-tokens.git", - "state" : { - "revision" : "02ba42d9ec02f90370a6cfc35a68d7312696636c", - "version" : "0.0.2" - } - }, - { - "identity" : "element-x-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/element-x-ios", - "state" : { - "revision" : "0a199ee61126feb8c8a462200cb4749d6eb3ba77", - "version" : "1.0.1-202207011447" - } - }, { "identity" : "maplibre-gl-native-distribution", "kind" : "remoteSourceControl", @@ -62,15 +44,6 @@ "branch" : "main", "revision" : "0ffad3f7b45a6a4760db090d503b00f094bbecc0" } - }, - { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/SwiftUI-Introspect.git", - "state" : { - "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", - "version" : "0.1.4" - } } ], "version" : 2 diff --git a/Riot/Managers/Theme/ElementUIColorsResolved.swift b/Riot/Managers/Theme/ElementUIColorsResolved.swift deleted file mode 100644 index 20118bfd3..000000000 --- a/Riot/Managers/Theme/ElementUIColorsResolved.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright 2022 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 UIKit -import DesignTokens - -extension UIColor { - /// The colors from DesignKit, resolved for light mode only. - static let elementLight = ElementUIColorsResolved(dynamicColors: element, userInterfaceStyle: .light) - /// The colors from DesignKit, resolved for dark mode only. - static let elementDark = ElementUIColorsResolved(dynamicColors: element, userInterfaceStyle: .dark) -} - -/// The dynamic colors from DesignKit, resolved to light or dark mode for use in the UIKit themes. -/// -/// As Element doesn't (currently) update the app's `UIUserInterfaceStyle` when selecting -/// a custom theme, the dynamic colors provided by DesignKit need resolving for each theme to -/// prevent them from respecting the interface style and rendering in the wrong style. -@objcMembers public class ElementUIColorsResolved: NSObject { - // MARK: Compound - public let accent: UIColor - public let alert: UIColor - public let primaryContent: UIColor - public let secondaryContent: UIColor - public let tertiaryContent: UIColor - public let quaternaryContent: UIColor - public let quinaryContent: UIColor - public let system: UIColor - public let background: UIColor - - public let namesAndAvatars: [UIColor] - - // MARK: Legacy - public let quarterlyContent: UIColor - public let navigation: UIColor - public let tile: UIColor - public let separator: UIColor - - // MARK: Setup - public init(dynamicColors: ElementUIColors, userInterfaceStyle: UIUserInterfaceStyle) { - let traitCollection = UITraitCollection(userInterfaceStyle: userInterfaceStyle) - - self.accent = dynamicColors.accent.resolvedColor(with: traitCollection) - self.alert = dynamicColors.alert.resolvedColor(with: traitCollection) - self.primaryContent = dynamicColors.primaryContent.resolvedColor(with: traitCollection) - self.secondaryContent = dynamicColors.secondaryContent.resolvedColor(with: traitCollection) - self.tertiaryContent = dynamicColors.tertiaryContent.resolvedColor(with: traitCollection) - self.quaternaryContent = dynamicColors.quaternaryContent.resolvedColor(with: traitCollection) - self.quinaryContent = dynamicColors.quinaryContent.resolvedColor(with: traitCollection) - self.system = dynamicColors.system.resolvedColor(with: traitCollection) - self.background = dynamicColors.background.resolvedColor(with: traitCollection) - - self.namesAndAvatars = dynamicColors.contentAndAvatars - - // Legacy colours - self.quarterlyContent = dynamicColors.quaternaryContent.resolvedColor(with: traitCollection) - self.navigation = dynamicColors.system.resolvedColor(with: traitCollection) - - if userInterfaceStyle == .light { - self.tile = UIColor(rgb: 0xF3F8FD) - self.separator = dynamicColors.quinaryContent.resolvedColor(with: traitCollection) - } else { - self.tile = dynamicColors.quinaryContent.resolvedColor(with: traitCollection) - self.separator = dynamicColors.system.resolvedColor(with: traitCollection) - } - - super.init() - } -} diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift index eaf4c4184..35651e6b1 100644 --- a/Riot/Managers/Theme/Themes/DarkTheme.swift +++ b/Riot/Managers/Theme/Themes/DarkTheme.swift @@ -185,9 +185,9 @@ class DarkTheme: NSObject, Theme { button.setTitleColor(self.tintColor, for: .normal) } - // MARK: - Theme v2 - var colors = UIColor.elementDark + /// MARK: - Theme v2 + var colors: ColorsUIKit = DarkColors.uiKit - var fonts = UIFont.element + var fonts: FontsUIKit = FontsUIKit(values: ElementFonts()) } diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift index f2d9231ec..412390a47 100644 --- a/Riot/Managers/Theme/Themes/DefaultTheme.swift +++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift @@ -14,6 +14,7 @@ limitations under the License. */ +import Foundation import UIKit import DesignKit @@ -193,8 +194,8 @@ class DefaultTheme: NSObject, Theme { button.setTitleColor(self.tintColor, for: .normal) } - // MARK: - Theme v2 - var colors = UIColor.elementLight + /// MARK: - Theme v2 + var colors: ColorsUIKit = LightColors.uiKit - var fonts = UIFont.element + var fonts: FontsUIKit = FontsUIKit(values: ElementFonts()) } diff --git a/Riot/target.yml b/Riot/target.yml index d8ee465a3..f2a1bb16b 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -34,8 +34,8 @@ targets: - target: RiotShareExtension - target: SiriIntents - target: RiotNSE + - target: DesignKit - target: CommonKit - - package: DesignKit - package: Mapbox - package: OrderedCollections - package: SwiftOGG diff --git a/RiotShareExtension/target.yml b/RiotShareExtension/target.yml index c50705aa0..bf2a032f5 100644 --- a/RiotShareExtension/target.yml +++ b/RiotShareExtension/target.yml @@ -30,8 +30,6 @@ targets: RiotShareExtension: platform: iOS type: app-extension - dependencies: - - package: DesignKit configFiles: Debug: Debug.xcconfig diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift index 55904b5d1..2b7fa9e60 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift @@ -49,7 +49,7 @@ struct AvatarImage: View { mxContentUri: mxContentUri, matrixItemId: matrixItemId, displayName: displayName, - colorCount: theme.colors.contentAndAvatars.count, + colorCount: theme.colors.namesAndAvatars.count, avatarSize: size ) } diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift index 7dbc2ba4f..f119a7e14 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift @@ -36,7 +36,7 @@ struct PlaceholderAvatarImage: View { var body: some View { ZStack { - theme.colors.contentAndAvatars[colorIndex] + theme.colors.namesAndAvatars[colorIndex] Text(String(firstCharacter)) .padding(4) diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift index 31e734c58..d82e2107f 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift @@ -38,7 +38,7 @@ struct SpaceAvatarImage: View { .padding(10) .frame(width: CGFloat(size.rawValue), height: CGFloat(size.rawValue)) .foregroundColor(.white) - .background(theme.colors.contentAndAvatars[colorIndex]) + .background(theme.colors.namesAndAvatars[colorIndex]) .clipShape(RoundedRectangle(cornerRadius: 8)) // Make the text resizable (i.e. Make it large and then allow it to scale down) .font(.system(size: 200)) @@ -55,7 +55,7 @@ struct SpaceAvatarImage: View { mxContentUri: mxContentUri, matrixItemId: matrixItemId, displayName: value, - colorCount: theme.colors.contentAndAvatars.count, + colorCount: theme.colors.namesAndAvatars.count, avatarSize: size ) }) @@ -65,7 +65,7 @@ struct SpaceAvatarImage: View { mxContentUri: mxContentUri, matrixItemId: matrixItemId, displayName: displayName, - colorCount: theme.colors.contentAndAvatars.count, + colorCount: theme.colors.namesAndAvatars.count, avatarSize: size ) } diff --git a/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift b/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift index daf298461..38eb5db11 100644 --- a/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift +++ b/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift @@ -31,7 +31,7 @@ class EffectsScene: SCNScene { static func confetti(with theme: ThemeSwiftUI) -> EffectsScene? { guard let scene = EffectsScene(named: Constants.confettiSceneName) else { return nil } - let colors: [[Float]] = theme.colors.contentAndAvatars.compactMap { $0.floatComponents } + let colors: [[Float]] = theme.colors.namesAndAvatars.compactMap { $0.floatComponents } if let particles = scene.rootNode.childNode(withName: Constants.particlesNodeName, recursively: false)?.particleSystems?.first { // The particles need a non-zero color variation for the handler to affect the color diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift index 7e8fe5308..f5a15424f 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift @@ -14,33 +14,10 @@ // limitations under the License. // -import SwiftUI +import Foundation import DesignKit -import DesignTokens protocol ThemeSwiftUI: ThemeSwiftUIType { var identifier: ThemeIdentifier { get } var isDark: Bool { get } } - -/// Theme v2 for SwiftUI. -@available(iOS 14.0, *) -public protocol ThemeSwiftUIType { - - /// Colors object - var colors: ElementColors { get } - - /// Fonts object - var fonts: ElementFonts { get } - - /// may contain more design components in future, like icons, audio files etc. -} - -// MARK: - Legacy Colors - -public extension ElementColors { - var legacyTile: Color { - let dynamicColor = UIColor { $0.userInterfaceStyle == .light ? .elementLight.tile : .elementDark.tile } - return Color(dynamicColor) - } -} diff --git a/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift b/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift index 6c3e3c2e2..ad1eeb222 100644 --- a/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift +++ b/RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift @@ -23,7 +23,7 @@ extension ThemeSwiftUI { /// - Parameter userId: The user id used to hash. /// - Returns: The SwiftUI color for the associated userId. func userColor(for userId: String) -> Color { - let senderNameColorIndex = Int(userId.vc_hashCode % Int32(colors.contentAndAvatars.count)) - return colors.contentAndAvatars[senderNameColorIndex] + let senderNameColorIndex = Int(userId.vc_hashCode % Int32(colors.namesAndAvatars.count)) + return colors.namesAndAvatars[senderNameColorIndex] } } diff --git a/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift b/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift index a572a4694..0e9250070 100644 --- a/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift +++ b/RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift @@ -14,12 +14,12 @@ // limitations under the License. // -import SwiftUI +import Foundation import DesignKit struct DarkThemeSwiftUI: ThemeSwiftUI { var identifier: ThemeIdentifier = .dark let isDark: Bool = true - var colors = Color.element - var fonts = Font.element + var colors: ColorSwiftUI = DarkColors.swiftUI + var fonts: FontSwiftUI = FontSwiftUI(values: ElementFonts()) } diff --git a/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift b/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift index bfc2e87c0..85ba4d810 100644 --- a/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift +++ b/RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift @@ -14,12 +14,12 @@ // limitations under the License. // -import SwiftUI +import Foundation import DesignKit struct DefaultThemeSwiftUI: ThemeSwiftUI { var identifier: ThemeIdentifier = .light let isDark: Bool = false - var colors = Color.element - var fonts = Font.element + var colors: ColorSwiftUI = LightColors.swiftUI + var fonts: FontSwiftUI = FontSwiftUI(values: ElementFonts()) } diff --git a/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift b/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift index 2615efa47..fe75aa300 100644 --- a/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift +++ b/RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift @@ -50,7 +50,7 @@ struct BorderedInputFieldStyle: TextFieldStyle { if (theme.identifier == ThemeIdentifier.dark) { return (isEnabled ? theme.colors.primaryContent : theme.colors.tertiaryContent) } else { - return (isEnabled ? theme.colors.primaryContent : theme.colors.quaternaryContent) + return (isEnabled ? theme.colors.primaryContent : theme.colors.quarterlyContent) } } diff --git a/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift b/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift index 6ab0832ac..7eb67d39c 100644 --- a/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift +++ b/RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift @@ -47,7 +47,7 @@ struct ClearViewModifier: ViewModifier { }) { Image(systemName: "xmark.circle.fill") .renderingMode(.template) - .foregroundColor(theme.colors.quaternaryContent) + .foregroundColor(theme.colors.quarterlyContent) } .padding(.top, alignment == .top ? 8 : 0) .padding(.bottom, alignment == .bottom ? 8 : 0) diff --git a/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift b/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift index a03c4b21b..5e20f11b0 100644 --- a/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift +++ b/RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift @@ -56,7 +56,7 @@ struct MultilineTextField: View { return theme.colors.accent } - return theme.colors.quaternaryContent + return theme.colors.quarterlyContent } private var borderWidth: CGFloat { @@ -75,7 +75,7 @@ struct MultilineTextField: View { .overlay(rect.stroke(borderColor, lineWidth: borderWidth)) .introspectTextView { textView in textView.textColor = UIColor(textColor) - textView.font = .element.callout + textView.font = theme.fonts.uiFonts.callout } } diff --git a/RiotSwiftUI/Modules/Common/Util/OptionButton.swift b/RiotSwiftUI/Modules/Common/Util/OptionButton.swift index 428ecf09c..17e54bbda 100644 --- a/RiotSwiftUI/Modules/Common/Util/OptionButton.swift +++ b/RiotSwiftUI/Modules/Common/Util/OptionButton.swift @@ -55,7 +55,7 @@ struct OptionButton: View { } } Spacer() - Image(systemName: "chevron.right").font(.system(size: 16, weight: .regular)).foregroundColor(theme.colors.quaternaryContent) + Image(systemName: "chevron.right").font(.system(size: 16, weight: .regular)).foregroundColor(theme.colors.quarterlyContent) } .padding(EdgeInsets(top: 15, leading: 16, bottom: 15, trailing: 16)) .background(theme.colors.quinaryContent) diff --git a/RiotSwiftUI/Modules/Common/Util/SearchBar.swift b/RiotSwiftUI/Modules/Common/Util/SearchBar.swift index 3901ea65a..4edaa2e5c 100644 --- a/RiotSwiftUI/Modules/Common/Util/SearchBar.swift +++ b/RiotSwiftUI/Modules/Common/Util/SearchBar.swift @@ -38,7 +38,7 @@ struct SearchBar: View { } .padding(8) .padding(.horizontal, 25) - .background(theme.colors.system) + .background(theme.colors.navigation) .cornerRadius(8) .padding(.leading) .padding(.trailing, isEditing ? 8 : 16) @@ -46,7 +46,7 @@ struct SearchBar: View { HStack { Image(systemName: "magnifyingglass") .renderingMode(.template) - .foregroundColor(theme.colors.quaternaryContent) + .foregroundColor(theme.colors.quarterlyContent) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) if isEditing && !text.isEmpty { @@ -55,7 +55,7 @@ struct SearchBar: View { }) { Image(systemName: "multiply.circle.fill") .renderingMode(.template) - .foregroundColor(theme.colors.quaternaryContent) + .foregroundColor(theme.colors.quarterlyContent) } } } diff --git a/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift b/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift index a20eba58e..8f0eb6aac 100644 --- a/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift +++ b/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift @@ -68,7 +68,7 @@ struct SecondaryActionButtonStyle_Previews: PreviewProvider { Text("Custom") .foregroundColor(theme.colors.secondaryContent) } - .buttonStyle(SecondaryActionButtonStyle(customColor: theme.colors.quaternaryContent)) + .buttonStyle(SecondaryActionButtonStyle(customColor: theme.colors.quarterlyContent)) } .padding() } diff --git a/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift b/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift index 4abada5e5..60f7315bb 100644 --- a/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift +++ b/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift @@ -89,7 +89,7 @@ struct WaitOverlay: ViewModifier { } .padding(12) .background(RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(theme.colors.system.opacity(0.9))) + .fill(theme.colors.navigation.opacity(0.9))) } .edgesIgnoringSafeArea(.all) .transition(.opacity) diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift index 5884e363e..216a65ea4 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift @@ -72,7 +72,7 @@ final class OnboardingAvatarCoordinator: Coordinator, Presentable { self.parameters = parameters let viewModel = OnboardingAvatarViewModel(userId: parameters.userSession.userId, displayName: parameters.userSession.account.userDisplayName, - avatarColorCount: DefaultThemeSwiftUI().colors.contentAndAvatars.count) + avatarColorCount: DefaultThemeSwiftUI().colors.namesAndAvatars.count) viewModel.updateAvatarImage(with: parameters.avatar) let view = OnboardingAvatarScreen(viewModel: viewModel.context) diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift index 8982961f6..7cf96984a 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift @@ -44,7 +44,7 @@ enum MockOnboardingAvatarScreenState: MockScreenState, CaseIterable { /// Generate the view struct for the screen state. var screenView: ([Any], AnyView) { - let avatarColorCount = DefaultThemeSwiftUI().colors.contentAndAvatars.count + let avatarColorCount = DefaultThemeSwiftUI().colors.namesAndAvatars.count let viewModel: OnboardingAvatarViewModel switch self { case .placeholderAvatar(let userId, let displayName): diff --git a/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift b/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift index 0fe73b772..fd0f284d3 100644 --- a/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift +++ b/RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift @@ -23,7 +23,7 @@ class OnboardingAvatarViewModelTests: XCTestCase { private enum Constants { static let userId = "@user:matrix.org" static let displayName = "Alice" - static let avatarColorCount = DefaultThemeSwiftUI().colors.contentAndAvatars.count + static let avatarColorCount = DefaultThemeSwiftUI().colors.namesAndAvatars.count static let avatarImage = Asset.Images.appSymbol.image } diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPageIndicator.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPageIndicator.swift index f2306eaf4..63e8507a0 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPageIndicator.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPageIndicator.swift @@ -47,7 +47,7 @@ struct OnboardingSplashScreenPageIndicator: View { ForEach(0.. Date: Thu, 18 Aug 2022 11:50:32 +0100 Subject: [PATCH 35/80] Fix build errors. --- .../View/LiveLocationSharingViewer.swift | 2 +- .../Congratulations/OnboardingCongratulationsModels.swift | 2 +- changelog.d/pr-6586.api | 1 + docs/Customisation.md | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog.d/pr-6586.api diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift index afd2e47a5..1fd256b5a 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift @@ -87,7 +87,7 @@ struct LiveLocationSharingViewer: View { HStack(spacing: 10) { Image(uiImage: Asset.Images.locationLiveCellIcon.image) .renderingMode(.template) - .foregroundColor(theme.colors.quaternaryContent) + .foregroundColor(theme.colors.quarterlyContent) .frame(width: 40, height: 40) Text(VectorL10n.liveLocationSharingEnded) .font(theme.fonts.body) diff --git a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift index a94adf6f1..bfc53c682 100644 --- a/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift @@ -37,7 +37,7 @@ struct OnboardingCongratulationsViewState: BindableState { let attributedMessage = NSMutableAttributedString(string: message) let range = (message as NSString).range(of: userId) if range.location != NSNotFound { - attributedMessage.addAttributes([.font: UIFont.element.body.bold], range: range) + attributedMessage.addAttributes([.font: UIFont.preferredFont(forTextStyle: .body).vc_bold], range: range) } return attributedMessage diff --git a/changelog.d/pr-6586.api b/changelog.d/pr-6586.api new file mode 100644 index 000000000..3be1cd7b0 --- /dev/null +++ b/changelog.d/pr-6586.api @@ -0,0 +1 @@ +Reverts #6275, bringing the local DesignKit package back. diff --git a/docs/Customisation.md b/docs/Customisation.md index 7bf816815..b3de09469 100644 --- a/docs/Customisation.md +++ b/docs/Customisation.md @@ -12,6 +12,6 @@ Various features of Element iOS can be enabled/disabled/configured via flags in ## Theme -Element iOS has a [dependency](https://github.com/vector-im/element-ios/blob/92fc7046ede2720d4b46bffd07d97ce59b50d95f/project.yml#L42-L44) on our DesignKit package which supplies the fonts, colours and some common components we use across multiple apps. The fonts are defined [directly](https://github.com/vector-im/element-x-ios/tree/develop/DesignKit/Sources/Fonts) in this package. The colours come from a [dependency](https://github.com/vector-im/element-x-ios/blob/2f69c9978231b6e7cf0b0c3126846f2369e999bb/Package.swift#L13) on our [Design Tokens](https://github.com/vector-im/element-design-tokens) repo which is a style dictionary that allows to us share definitions across multiple platforms. +The themes used in Element iOS can be found in `Riot/Managers/Theme/Themes`. A newer theming system is available as nested `colors` and `fonts` properties on these themes and can be found in `DesignKit/Variants/Colors` and `DesignKit/Variants/Fonts` respectively. The newer system is used for screens built in UIKit with Swift and all of the SwiftUI screens. For logos, they're currently regular assets that can be found either in [Images.xcassets](https://github.com/vector-im/element-ios/tree/develop/Riot/Assets/Images.xcassets) or [SharedImages.xcassets](https://github.com/vector-im/element-ios/tree/develop/Riot/Assets/SharedImages.xcassets). From 74454d11cbd2eb1c001285a7cfc1415de0ca32bc Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Fri, 19 Aug 2022 17:12:33 +0200 Subject: [PATCH 36/80] New App Layout: Added missing empty states in room list and space bottom sheet --- .../Contents.json | 25 +++++ .../all_chats_empty_list_placeholder_icon.svg | 4 + Riot/Assets/en.lproj/Vector.strings | 7 ++ Riot/Generated/Images.swift | 1 + Riot/Generated/Strings.swift | 28 ++++++ .../Recents/DataSources/RecentsDataSource.m | 19 +++- .../RecentEmptySectionTableViewCell.swift | 59 ++++++++++++ .../Views/RecentEmptySectionTableViewCell.xib | 91 +++++++++++++++++++ .../AllChats/AllChatsViewController.swift | 50 ++++++++++ .../MockSpaceSelectorScreenState.swift | 8 +- .../MatrixSDK/SpaceSelectorService.swift | 16 ++-- .../Test/UI/SpaceSelectorUITests.swift | 22 +++++ .../SpaceSelector/View/SpaceSelector.swift | 40 +++++++- changelog.d/6514.change | 1 + 14 files changed, 355 insertions(+), 16 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/all_chats_empty_list_placeholder_icon.svg create mode 100644 Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.swift create mode 100644 Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.xib create mode 100644 changelog.d/6514.change diff --git a/Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/Contents.json new file mode 100644 index 000000000..90791b942 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "all_chats_empty_list_placeholder_icon.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/all_chats_empty_list_placeholder_icon.svg b/Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/all_chats_empty_list_placeholder_icon.svg new file mode 100644 index 000000000..ac8c69997 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Home/all_chats_empty_list_placeholder_icon.imageset/all_chats_empty_list_placeholder_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 1a8152b55..f79e739b5 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2178,6 +2178,10 @@ Tap the + to start adding people."; "all_chats_edit_layout_activity_order" = "Sort by activity"; "all_chats_edit_layout_alphabetical_order" = "Sort A-Z"; "all_chats_all_filter" = "All"; +"all_chats_empty_view_title" = "%@\nis looking a little empty."; +"all_chats_empty_view_information" = "Spaces are a new way to group rooms and people. Add an existing room, or create a new one, to get started."; +"all_chats_empty_list_placeholder_title" = "Nothing to report."; +"all_chats_empty_unreads_placeholder_message" = "This is where you're unread messages will show up, when you have some."; "room_recents_recently_viewed_section" = "Recently viewed"; @@ -2189,6 +2193,9 @@ Tap the + to start adding people."; // Mark: - Space Selector "space_selector_title" = "My spaces"; +"space_selector_empty_view_title" = "No spaces yet."; +"space_selector_empty_view_information" = "Spaces are a way to group rooms and people. Create a space to get started."; +"space_selector_create_space" = "Create Space"; // Mark: - Polls diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index beb99dc8f..c9c7dd37c 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -113,6 +113,7 @@ internal class Asset: NSObject { internal static let roomActionNotificationMuted = ImageAsset(name: "room_action_notification_muted") internal static let roomActionPriorityHigh = ImageAsset(name: "room_action_priority_high") internal static let roomActionPriorityLow = ImageAsset(name: "room_action_priority_low") + internal static let allChatsEmptyListPlaceholderIcon = ImageAsset(name: "all_chats_empty_list_placeholder_icon") internal static let homeEmptyScreenArtwork = ImageAsset(name: "home_empty_screen_artwork") internal static let homeEmptyScreenArtworkDark = ImageAsset(name: "home_empty_screen_artwork_dark") internal static let plusFloatingAction = ImageAsset(name: "plus_floating_action") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index cf2b7fa68..1dab1ae5e 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -179,6 +179,22 @@ public class VectorL10n: NSObject { public static var allChatsEditMenuSpaceSettings: String { return VectorL10n.tr("Vector", "all_chats_edit_menu_space_settings") } + /// Nothing to report. + public static var allChatsEmptyListPlaceholderTitle: String { + return VectorL10n.tr("Vector", "all_chats_empty_list_placeholder_title") + } + /// This is where you're unread messages will show up, when you have some. + public static var allChatsEmptyUnreadsPlaceholderMessage: String { + return VectorL10n.tr("Vector", "all_chats_empty_unreads_placeholder_message") + } + /// Spaces are a new way to group rooms and people. Add an existing room, or create a new one, to get started. + public static var allChatsEmptyViewInformation: String { + return VectorL10n.tr("Vector", "all_chats_empty_view_information") + } + /// %@\nis looking a little empty. + public static func allChatsEmptyViewTitle(_ p1: String) -> String { + return VectorL10n.tr("Vector", "all_chats_empty_view_title", p1) + } /// Chats public static var allChatsSectionTitle: String { return VectorL10n.tr("Vector", "all_chats_section_title") @@ -7795,6 +7811,18 @@ public class VectorL10n: NSObject { public static var spacePublicJoinRuleDetail: String { return VectorL10n.tr("Vector", "space_public_join_rule_detail") } + /// Create Space + public static var spaceSelectorCreateSpace: String { + return VectorL10n.tr("Vector", "space_selector_create_space") + } + /// Spaces are a way to group rooms and people. Create a space to get started. + public static var spaceSelectorEmptyViewInformation: String { + return VectorL10n.tr("Vector", "space_selector_empty_view_information") + } + /// No spaces yet. + public static var spaceSelectorEmptyViewTitle: String { + return VectorL10n.tr("Vector", "space_selector_empty_view_title") + } /// My spaces public static var spaceSelectorTitle: String { return VectorL10n.tr("Vector", "space_selector_title") diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index dcf25b5cd..0b15242ae 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -637,7 +637,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } else if (sectionType == RecentsDataSourceSectionTypeAllChats && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_ALL_CHATS)) { - count = self.allChatsRoomCellDataArray.count; + count = self.allChatsRoomCellDataArray.count ?: 1; } // Adjust this count according to the potential dragged cell. @@ -1052,8 +1052,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return cell; } else if ((sectionType == RecentsDataSourceSectionTypeConversation && !self.conversationCellDataArray.count) - || (sectionType == RecentsDataSourceSectionTypePeople && !self.peopleCellDataArray.count) - || (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsRoomCellDataArray.count)) + || (sectionType == RecentsDataSourceSectionTypePeople && !self.peopleCellDataArray.count)) { MXKTableViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]]; if (!tableViewCell) @@ -1080,6 +1079,15 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return tableViewCell; } + else if (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsRoomCellDataArray.count) { + RecentEmptySectionTableViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:[RecentEmptySectionTableViewCell defaultReuseIdentifier]]; + + tableViewCell.iconView.image = self.searchPatternsList ? [UIImage systemImageNamed:@"magnifyingglass"] : AssetImages.allChatsEmptyListPlaceholderIcon.image; + tableViewCell.titleLabel.text = VectorL10n.allChatsEmptyListPlaceholderTitle; + tableViewCell.messageLabel.text = self.searchPatternsList ? VectorL10n.searchNoResult : VectorL10n.allChatsEmptyUnreadsPlaceholderMessage; + + return tableViewCell; + } return [super tableView:tableView cellForRowAtIndexPath:indexPath]; } @@ -1184,10 +1192,13 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return self.droppingCellBackGroundView.frame.size.height; } if ((sectionType == RecentsDataSourceSectionTypeConversation && !self.conversationCellDataArray.count) - || (sectionType == RecentsDataSourceSectionTypePeople && !self.peopleCellDataArray.count)) + || (sectionType == RecentsDataSourceSectionTypePeople && !self.peopleCellDataArray.count)) { return 50.0; } + if (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsRoomCellDataArray.count) { + return 300.0; + } // Override this method here to use our own cellDataAtIndexPath id cellData = [self cellDataAtIndexPath:indexPath]; diff --git a/Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.swift b/Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.swift new file mode 100644 index 000000000..11a7c794c --- /dev/null +++ b/Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.swift @@ -0,0 +1,59 @@ +// +// Copyright 2022 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 UIKit +import Reusable + +/// `RecentEmptySectionTableViewCell` can be used as a placeholder for empty sections. +class RecentEmptySectionTableViewCell: UITableViewCell, NibReusable, Themable { + + @IBOutlet private var iconBackgroundView: UIView! + @IBOutlet var iconView: UIImageView! + @IBOutlet var titleLabel: UILabel! + @IBOutlet var messageLabel: UILabel! + + @objc static func defaultReuseIdentifier() -> String { + return reuseIdentifier + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + self.iconBackgroundView.layer.cornerRadius = self.iconBackgroundView.bounds.height / 2 + self.iconBackgroundView.layer.masksToBounds = true + + self.selectionStyle = .none + + update(theme: ThemeService.shared().theme) + } + + // MARK: - Themable + + func update(theme: Theme) { + self.backgroundColor = theme.colors.background + + self.iconBackgroundView.backgroundColor = theme.colors.quinaryContent + self.iconView.tintColor = theme.colors.secondaryContent + + self.titleLabel.textColor = theme.colors.primaryContent + self.titleLabel.font = theme.fonts.title3SB + + self.messageLabel.textColor = theme.colors.secondaryContent + self.messageLabel.font = theme.fonts.callout + } +} diff --git a/Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.xib b/Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.xib new file mode 100644 index 000000000..bcae1f769 --- /dev/null +++ b/Riot/Modules/Common/Recents/Views/RecentEmptySectionTableViewCell.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 0bbc173eb..392a09a52 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -55,6 +55,7 @@ class AllChatsViewController: HomeViewController { recentsTableView.tag = RecentsDataSourceMode.allChats.rawValue recentsTableView.clipsToBounds = false + recentsTableView.register(RecentEmptySectionTableViewCell.nib, forCellReuseIdentifier: RecentEmptySectionTableViewCell.reuseIdentifier) updateUI() vc_setLargeTitleDisplayMode(.automatic) @@ -154,6 +155,54 @@ class AllChatsViewController: HomeViewController { lastScrollPosition = scrollPosition } + // MARK: - Empty view management + + override func updateEmptyView() { + guard let mainSession = self.mainSession else { + return + } + + let title: String + let informationText: String + if let currentSpace = self.dataSource?.currentSpace { + title = VectorL10n.allChatsEmptyViewTitle(currentSpace.summary?.displayname ?? VectorL10n.spaceTag) + informationText = VectorL10n.allChatsEmptyViewInformation + } else { + let myUser = mainSession.myUser + let displayName = (myUser?.displayName ?? myUser?.userId) ?? "" + let appName = AppInfo.current.displayName + title = VectorL10n.homeEmptyViewTitle(appName, displayName) + informationText = VectorL10n.homeEmptyViewInformation + } + + self.emptyView?.fill(with: emptyViewArtwork, title: title, informationText: informationText) + } + + private var emptyViewArtwork: UIImage { + if self.dataSource?.currentSpace == nil { + return ThemeService.shared().isCurrentThemeDark() ? Asset.Images.roomsEmptyScreenArtworkDark.image : Asset.Images.roomsEmptyScreenArtwork.image + } else { + return ThemeService.shared().isCurrentThemeDark() ? Asset.Images.homeEmptyScreenArtworkDark.image : Asset.Images.homeEmptyScreenArtwork.image + } + } + + override func shouldShowEmptyView() -> Bool { + let shouldShowEmptyView = super.shouldShowEmptyView() + + if shouldShowEmptyView { + self.tabBarController?.navigationItem.searchController = nil + navigationItem.largeTitleDisplayMode = .never + navigationController?.navigationBar.prefersLargeTitles = false + } else { + self.tabBarController?.navigationItem.searchController = searchController + navigationItem.largeTitleDisplayMode = .automatic + navigationController?.navigationBar.prefersLargeTitles = true + } + + return shouldShowEmptyView + } + + // MARK: - Theme management override func userInterfaceThemeDidChange() { @@ -191,6 +240,7 @@ class AllChatsViewController: HomeViewController { updateToolbar(with: editActionProvider.updateMenu(with: mainSession, parentSpace: currentSpace, completion: { [weak self] menu in self?.updateToolbar(with: menu) })) + updateEmptyView() } private func updateRightNavigationItem(with menu: UIMenu) { diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift index cedbbb1c2..c0f2c4830 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/MockSpaceSelectorScreenState.swift @@ -23,8 +23,8 @@ enum MockSpaceSelectorScreenState: MockScreenState, CaseIterable { // A case for each state you want to represent // with specific, minimal associated data that will allow you // mock that screen. - case initialList case emptyList + case initialList case selection /// The associated screen @@ -34,17 +34,17 @@ enum MockSpaceSelectorScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockSpaceSelectorScreenState] { - [.initialList, .emptyList, .selection] + [.emptyList, .initialList, .selection] } /// Generate the view struct for the screen state. var screenView: ([Any], AnyView) { let service: MockSpaceSelectorService switch self { + case .emptyList: + service = MockSpaceSelectorService(spaceList: []) case .initialList: service = MockSpaceSelectorService() - case .emptyList: - service = MockSpaceSelectorService(spaceList: [MockSpaceSelectorService.homeItem]) case .selection: service = MockSpaceSelectorService(selectedSpaceId: MockSpaceSelectorService.defaultSpaceList[2].id) } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift index da40b076f..d9bfbea9f 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Service/MatrixSDK/SpaceSelectorService.swift @@ -29,19 +29,23 @@ class SpaceSelectorService: SpaceSelectorServiceProtocol { private let showHomeSpace: Bool private var spaceList: [SpaceSelectorListItemData] { - var itemList = showHomeSpace && parentSpaceId == nil ? [SpaceSelectorListItemData(id: SpaceSelectorConstants.homeSpaceId, icon: Asset.Images.sideMenuActionIconFeedback.image, displayName: VectorL10n.allChatsTitle)] : [] - let notificationCounter = session.spaceService.notificationCounter + let childSpaces: [SpaceSelectorListItemData] if let parentSpaceId = parentSpaceId, let parentSpace = session.spaceService.getSpace(withId: parentSpaceId) { - itemList.append(contentsOf: parentSpace.childSpaces.compactMap { space in + childSpaces = parentSpace.childSpaces.compactMap { space in SpaceSelectorListItemData.itemData(with: space, notificationCounter: notificationCounter) - }) + } } else { - itemList.append(contentsOf: session.spaceService.rootSpaces.compactMap { space in + childSpaces = session.spaceService.rootSpaces.compactMap { space in SpaceSelectorListItemData.itemData(with: space, notificationCounter: notificationCounter) - }) + } } + + var itemList = showHomeSpace && parentSpaceId == nil && !childSpaces.isEmpty ? [SpaceSelectorListItemData(id: SpaceSelectorConstants.homeSpaceId, icon: Asset.Images.sideMenuActionIconFeedback.image, displayName: VectorL10n.allChatsTitle)] : [] + + itemList.append(contentsOf: childSpaces) + return itemList } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift index 4770a7602..5022da628 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/Test/UI/SpaceSelectorUITests.swift @@ -37,6 +37,28 @@ class SpaceSelectorUITests: MockScreenTestCase { for (index, item) in MockSpaceSelectorService.defaultSpaceList.enumerated() { XCTAssertEqual(item.displayName, spaceItemNameList[index].label) } + + checkIfEmptyPlaceholder(exists: false) + } + + func testEmptyList() { + app.goToScreenWithIdentifier(MockSpaceSelectorScreenState.emptyList.title) + + let disclosureButtons = app.buttons.matching(identifier: "disclosureButton").allElementsBoundByIndex + XCTAssertEqual(disclosureButtons.count, 0) + let notificationBadges = app.staticTexts.matching(identifier: "notificationBadge").allElementsBoundByIndex + XCTAssertEqual(notificationBadges.count, 0) + let spaceItemNameList = app.staticTexts.matching(identifier: "itemName").allElementsBoundByIndex + XCTAssertEqual(spaceItemNameList.count, 0) + checkIfEmptyPlaceholder(exists: true) + } + + // MARK: - Private methods + + private func checkIfEmptyPlaceholder(exists: Bool) { + XCTAssertEqual(app.staticTexts["emptyListPlaceholderTitle"].exists, exists) + XCTAssertEqual(app.staticTexts["emptyListPlaceholderMessage"].exists, exists) + XCTAssertEqual(app.buttons["createSpaceButton"].exists, exists) } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift index d9cc21c77..a64e3d8c2 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceSelectorBottomSheet/SpaceSelector/View/SpaceSelector.swift @@ -29,6 +29,18 @@ struct SpaceSelector: View { @ObservedObject var viewModel: SpaceSelectorViewModel.Context var body: some View { + VStack { + if !viewModel.viewState.items.isEmpty { + itemListView + } else { + emptyListPlaceholder + } + } + .background(theme.colors.background.edgesIgnoringSafeArea(.all)) + .accentColor(theme.colors.accent) + } + + private var itemListView: some View { ScrollView { LazyVStack { ForEach(viewModel.viewState.items) { item in @@ -50,7 +62,6 @@ struct SpaceSelector: View { } } .frame(maxHeight: .infinity) - .background(theme.colors.background.edgesIgnoringSafeArea(.all)) .navigationTitle(viewModel.viewState.navigationTitle) .toolbar { ToolbarItem(placement: .confirmationAction) { @@ -66,7 +77,32 @@ struct SpaceSelector: View { } } } - .accentColor(theme.colors.accent) + } + + private var emptyListPlaceholder: some View { + VStack { + Spacer() + Text(VectorL10n.spaceSelectorEmptyViewTitle) + .foregroundColor(theme.colors.primaryContent) + .font(theme.fonts.title3SB) + .accessibility(identifier: "emptyListPlaceholderTitle") + Spacer() + .frame(height: 24) + Text(VectorL10n.spaceSelectorEmptyViewInformation) + .foregroundColor(theme.colors.secondaryContent) + .font(theme.fonts.callout) + .multilineTextAlignment(.center) + .accessibility(identifier: "emptyListPlaceholderMessage") + Spacer() + Button { viewModel.send(viewAction: .createSpace) } label: { + Text(VectorL10n.spaceSelectorCreateSpace) + .font(theme.fonts.bodySB) + } + .buttonStyle(PrimaryActionButtonStyle()) + .accessibility(identifier: "createSpaceButton") + } + .padding(.horizontal, 24) + .padding(.bottom, 24) } } diff --git a/changelog.d/6514.change b/changelog.d/6514.change new file mode 100644 index 000000000..f1ab57103 --- /dev/null +++ b/changelog.d/6514.change @@ -0,0 +1 @@ +New App Layout: Added missing empty states in room list and space bottom sheet From 9152dfb1160885ba58175a2a78bbbf85972ec20b Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Fri, 19 Aug 2022 23:19:16 +0200 Subject: [PATCH 37/80] New App Layout: Added missing empty states in room list and space bottom sheet - make breadcrumbs visible accordingly with the all chats layout options --- .../Common/Recents/Service/MatrixSDK/RecentsListService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index 64c19bb1a..85cd4208c 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -140,7 +140,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { if space != nil, let fetcher = suggestedRoomListDataFetcher, fetcherTypes.contains(.suggested) { result.append(fetcher) } - if let fetcher = breadcrumbsRoomListDataFetcher, fetcherTypes.contains(.breadcrumbs) { + if let fetcher = breadcrumbsRoomListDataFetcher, fetcherTypes.contains(.breadcrumbs), shouldShowBreadcrumbs { result.append(fetcher) } if let fetcher = allChatsRoomListDataFetcher, fetcherTypes.contains(.allChats) { @@ -493,7 +493,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { } private var shouldShowBreadcrumbs: Bool { - return fetcherTypesForMode[mode]?.contains(.breadcrumbs) ?? false + return AllChatsLayoutSettingsManager.shared.allChatLayoutSettings.sections.contains(.recents) && (fetcherTypesForMode[mode]?.contains(.breadcrumbs) ?? false) } private var shouldShowAllChats: Bool { From 03fc5676bb891d9a9dfdc8d8f41643190e82d176 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Sun, 21 Aug 2022 11:07:21 +0200 Subject: [PATCH 38/80] App Layout: added space invites in space bottom sheet --- .../Recents/DataSources/RecentsDataSource.m | 5 - .../SpaceDetail/SpaceDetailCoordinator.swift | 90 +++++++++++++++ .../SpaceDetail/SpaceDetailPresenter.swift | 2 +- .../SpaceDetailViewController.storyboard | 106 ++++++++++-------- .../SpaceDetailViewController.swift | 30 ++++- .../SpaceSelectorBottomCoordinator.swift | 68 +++++++---- ...ctorBottomCoordinatorBridgePresenter.swift | 2 + .../MatrixSDK/SpaceSelectorService.swift | 51 +++++++-- .../SpaceSelector/SpaceSelectorModels.swift | 6 +- .../SpaceSelectorViewModel.swift | 8 ++ .../SpaceSelector/View/SpaceSelector.swift | 1 + .../View/SpaceSelectorListRow.swift | 40 ++++--- changelog.d/6599.bugfix | 1 + 13 files changed, 306 insertions(+), 104 deletions(-) create mode 100644 Riot/Modules/Spaces/SpaceDetail/SpaceDetailCoordinator.swift create mode 100644 changelog.d/6599.bugfix diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 0b15242ae..9f217b12c 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -229,11 +229,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou [types addObject:@(RecentsDataSourceSectionTypeAllChats)]; } - if (self.currentSpace == nil && BuildSettings.newAppLayoutEnabled && self.invitesCellDataArray.count > 0) - { - [types addObject:@(RecentsDataSourceSectionTypeInvites)]; - } - if (self.currentSpace != nil && self.suggestedRoomCellDataArray.count > 0) { [types addObject:@(RecentsDataSourceSectionTypeSuggestedRooms)]; diff --git a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailCoordinator.swift b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailCoordinator.swift new file mode 100644 index 000000000..54a2f499a --- /dev/null +++ b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailCoordinator.swift @@ -0,0 +1,90 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import UIKit + +struct SpaceDetailCoordinatorParameters { + let spaceId: String + let session: MXSession + let showCancel: Bool +} + +enum SpaceDetailCoordinatorResult { + case cancel + case dismiss + case open + case join +} + +/// Space detail screen +final class SpaceDetailCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: SpaceDetailCoordinatorParameters + private var viewModel: SpaceDetailViewModel! + private let viewController: SpaceDetailViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var completion: ((SpaceDetailCoordinatorResult) -> Void)? + + // MARK: - Setup + + init(parameters: SpaceDetailCoordinatorParameters) { + self.parameters = parameters + + let viewModel = SpaceDetailViewModel(session: parameters.session, spaceId: parameters.spaceId) + let viewController = SpaceDetailViewController.instantiate(mediaManager: parameters.session.mediaManager, viewModel: viewModel, showCancel: parameters.showCancel) + self.viewModel = viewModel + self.viewController = viewController + } + + // MARK: - Public methods + + func start() { + self.viewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.viewController + } +} + +// MARK: - SpaceDetailModelViewModelCoordinatorDelegate + +extension SpaceDetailCoordinator: SpaceDetailModelViewModelCoordinatorDelegate { + func spaceDetailViewModelDidJoin(_ viewModel: SpaceDetailViewModelType) { + completion?(.join) + } + + func spaceDetailViewModelDidOpen(_ viewModel: SpaceDetailViewModelType) { + completion?(.open) + } + + func spaceDetailViewModelDidCancel(_ viewModel: SpaceDetailViewModelType) { + completion?(.cancel) + } + + func spaceDetailViewModelDidDismiss(_ viewModel: SpaceDetailViewModelType) { + completion?(.dismiss) + } +} diff --git a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailPresenter.swift b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailPresenter.swift index ab340fbc0..30302d982 100644 --- a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailPresenter.swift +++ b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailPresenter.swift @@ -84,7 +84,7 @@ class SpaceDetailPresenter: NSObject { // MARK: - Private private func show(with session: MXSession) { - let viewController = SpaceDetailViewController.instantiate(mediaManager: session.mediaManager, viewModel: self.viewModel) + let viewController = SpaceDetailViewController.instantiate(mediaManager: session.mediaManager, viewModel: self.viewModel, showCancel: true) self.present(viewController, animated: true) } diff --git a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.storyboard b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.storyboard index 817c03286..f578db460 100644 --- a/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.storyboard +++ b/Riot/Modules/Spaces/SpaceDetail/SpaceDetailViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -12,34 +12,14 @@ - + - + - - - - - - - - - - @@ -50,22 +30,14 @@ - - + - - - - - - - - - + + @@ -90,29 +62,29 @@ - + - + +