diff --git a/CHANGES.rst b/CHANGES.rst index 36e1d4b85..9806c58eb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,8 @@ Changes to be released in next version * Update "Jump to unread" banner to a pill style button. * CallVC: Add transfer button. * Spaces: Hide spaces from room list and home but keep space invites (#4252). + * Spaces: Show space invites and advertise that they are not available (#4277). + * Advertise that spaces are not available when tapping on a space link or a space invite (#4279). 🐛 Bugfix * RoomVC: Avoid navigation to integration management using integration popup with settings set to integration disabled (#4261). diff --git a/Riot/Assets/Images.xcassets/Spaces/Contents.json b/Riot/Assets/Images.xcassets/Spaces/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/Contents.json new file mode 100644 index 000000000..89de3f057 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "feature_unavaible_artwork.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature_unavaible_artwork@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature_unavaible_artwork@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork.png new file mode 100644 index 000000000..bd404a5e6 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@2x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@2x.png new file mode 100644 index 000000000..e6f2bf781 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@3x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@3x.png new file mode 100644 index 000000000..12be231e7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/Contents.json new file mode 100644 index 000000000..3de7744a6 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "feature_unavaible_artwork_dark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature_unavaible_artwork_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature_unavaible_artwork_dark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark.png new file mode 100644 index 000000000..f90898c72 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@2x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@2x.png new file mode 100644 index 000000000..fb24831b7 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@3x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@3x.png new file mode 100644 index 000000000..bdda22aa4 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@3x.png differ diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 277d820ce..dc0cbc884 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1632,3 +1632,14 @@ Tap the + to start adding people."; "room_intro_cell_information_dm_sentence2" = "Only the two of you are in this conversation, no one else can join."; "room_intro_cell_information_multiple_dm_sentence2" = "Only you are in this conversation, unless any of you invites someone to join."; + +// Mark: - Spaces + +"space_feature_unavailable_title" = "Spaces aren’t here yet"; +"space_feature_unavailable_subtitle" = "Spaces aren't on iOS yet, but you can use them now on Web and Desktop"; +"space_feature_unavailable_information" = "Spaces are a new way to group rooms and people.\n\nThey’ll be here soon. For now, if you join one on another platform, you will be able to access any rooms you join here."; + +"space_beta_announce_badge" = "BETA"; +"space_beta_announce_title" = "Spaces are coming soon"; +"space_beta_announce_subtitle" = "The new version of communities"; +"space_beta_announce_information" = "Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop."; diff --git a/Riot/Categories/UITableViewCell.swift b/Riot/Categories/UITableViewCell.swift index e9ea1f8ba..dfd80a9c2 100644 --- a/Riot/Categories/UITableViewCell.swift +++ b/Riot/Categories/UITableViewCell.swift @@ -33,6 +33,11 @@ extension UITableViewCell { return result } + // Hide separator for one cell, otherwise use `tableView.separatorStyle = .none` + @objc func vc_hideSeparator() { + self.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: .greatestFiniteMagnitude) + } + @objc func vc_setAccessoryDisclosureIndicator(withTintColor tintColor: UIColor) { let disclosureImage = Asset.Images.disclosureIcon.image.withRenderingMode(.alwaysTemplate) let disclosureImageView = UIImageView(image: disclosureImage) diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 4565353fe..08f3dbdf8 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -158,6 +158,8 @@ internal enum Asset { internal static let secretsResetWarning = ImageAsset(name: "secrets_reset_warning") internal static let removeIconPink = ImageAsset(name: "remove_icon_pink") internal static let settingsIcon = ImageAsset(name: "settings_icon") + internal static let featureUnavaibleArtwork = ImageAsset(name: "feature_unavaible_artwork") + internal static let featureUnavaibleArtworkDark = ImageAsset(name: "feature_unavaible_artwork_dark") internal static let tabFavourites = ImageAsset(name: "tab_favourites") internal static let tabGroups = ImageAsset(name: "tab_groups") internal static let tabHome = ImageAsset(name: "tab_home") diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 0b1492b69..8e7290c1a 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -229,6 +229,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: SimpleScreenTemplateViewController.self) } + internal enum SpaceFeatureUnaivableViewController: StoryboardType { + internal static let storyboardName = "SpaceFeatureUnaivableViewController" + + internal static let initialScene = InitialSceneType(storyboard: SpaceFeatureUnaivableViewController.self) + } internal enum TemplateScreenViewController: StoryboardType { internal static let storyboardName = "TemplateScreenViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 11d0dc52d..8a7a8bae0 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -4562,6 +4562,34 @@ internal enum VectorL10n { internal static var socialLoginListTitleSignUp: String { return VectorL10n.tr("Vector", "social_login_list_title_sign_up") } + /// BETA + internal static var spaceBetaAnnounceBadge: String { + return VectorL10n.tr("Vector", "space_beta_announce_badge") + } + /// Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop. + internal static var spaceBetaAnnounceInformation: String { + return VectorL10n.tr("Vector", "space_beta_announce_information") + } + /// The new version of communities + internal static var spaceBetaAnnounceSubtitle: String { + return VectorL10n.tr("Vector", "space_beta_announce_subtitle") + } + /// Spaces are coming soon + internal static var spaceBetaAnnounceTitle: String { + return VectorL10n.tr("Vector", "space_beta_announce_title") + } + /// Spaces are a new way to group rooms and people.\n\nThey’ll be here soon. For now, if you join one on another platform, you will be able to access any rooms you join here. + internal static var spaceFeatureUnavailableInformation: String { + return VectorL10n.tr("Vector", "space_feature_unavailable_information") + } + /// Spaces aren't on iOS yet, but you can use them now on Web and Desktop + internal static var spaceFeatureUnavailableSubtitle: String { + return VectorL10n.tr("Vector", "space_feature_unavailable_subtitle") + } + /// Spaces aren’t here yet + internal static var spaceFeatureUnavailableTitle: String { + return VectorL10n.tr("Vector", "space_feature_unavailable_title") + } /// Start internal static var start: String { return VectorL10n.tr("Vector", "start") diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 954741ccb..533702c2b 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -79,6 +79,7 @@ final class RiotSettings: NSObject { static let roomInfoScreenShowIntegrations = "roomInfoScreenShowIntegrations" static let roomMemberScreenShowIgnore = "roomMemberScreenShowIgnore" static let unifiedSearchScreenShowPublicDirectory = "unifiedSearchScreenShowPublicDirectory" + static let hideSpaceBetaAnnounce = "hideSpaceBetaAnnounce" } static let shared = RiotSettings() @@ -707,4 +708,13 @@ final class RiotSettings: NSObject { } } + // MARK: - Beta + + var hideSpaceBetaAnnounce: Bool { + get { + return defaults.bool(forKey: UserDefaultsKeys.hideSpaceBetaAnnounce) + } set { + defaults.set(newValue, forKey: UserDefaultsKeys.hideSpaceBetaAnnounce) + } + } } diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h index 1cea08e0d..6935122c2 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.h +++ b/Riot/Modules/Application/LegacyAppDelegate.h @@ -224,6 +224,38 @@ UINavigationControllerDelegate */ - (BOOL)handleUniversalLinkFragment:(NSString*)fragment; +/** + Process the fragment part of a vector.im link. + + @param fragment the fragment part of the universal link. + @param universalLinkURL the unprocessed the universal link URL (optional). + @return YES in case of processing success. + */ +- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL; + +/** + Process the URL of a vector.im link. + + @param universalLinkURL the universal link URL. + @return YES in case of processing success. + */ +- (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL; + +#pragma mark - Jitsi call + +/** + Open the Jitsi view controller from a widget. + + @param jitsiWidget the jitsi widget. + @param video to indicate voice or video call. + */ +- (void)displayJitsiViewControllerWithWidget:(Widget*)jitsiWidget andVideo:(BOOL)video; + +/** + The current Jitsi view controller being displayed. + */ +@property (nonatomic, readonly) JitsiViewController *jitsiViewController; + #pragma mark - Call status handling /** diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index ece6aad63..aaf686362 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -228,6 +228,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni @property (nonatomic, strong) MajorUpdateManager *majorUpdateManager; +@property (nonatomic, strong) SpaceFeatureUnavailablePresenter *spaceFeatureUnavailablePresenter; + @end @implementation LegacyAppDelegate @@ -461,6 +463,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni self.pushNotificationStore = [PushNotificationStore new]; self.pushNotificationService = [[PushNotificationService alloc] initWithPushNotificationStore:self.pushNotificationStore]; self.pushNotificationService.delegate = self; + + self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new]; // Add matrix observers, and initialize matrix sessions if the app is not launched in background. [self initMatrixSessions]; @@ -1212,7 +1216,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Continue the registration with the passed nextLink NSLog(@"[AppDelegate] handleUniversalLink. Complete registration with nextLink"); NSURL *nextLink = [NSURL URLWithString:queryParams[@"nextLink"]]; - [self handleUniversalLinkFragment:nextLink.fragment]; + [self handleUniversalLinkFragment:nextLink.fragment fromURL:nextLink]; } else { @@ -1238,10 +1242,15 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return YES; } - return [self handleUniversalLinkFragment:webURL.fragment]; + return [self handleUniversalLinkFragment:webURL.fragment fromURL:webURL]; } - (BOOL)handleUniversalLinkFragment:(NSString*)fragment +{ + return [self handleUniversalLinkFragment:fragment fromURL:nil]; +} + +- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL { BOOL continueUserActivity = NO; MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; @@ -1325,19 +1334,32 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if (account) { NSString *roomId = roomIdOrAlias; + MXRoom *room; // Translate the alias into the room id if ([roomIdOrAlias hasPrefix:@"#"]) { - MXRoom *room = [account.mxSession roomWithAlias:roomIdOrAlias]; + room = [account.mxSession roomWithAlias:roomIdOrAlias]; if (room) { roomId = room.roomId; } } + else + { + room = [account.mxSession roomWithRoomId:roomId]; + } - // Open the room page - [self showRoom:roomId andEventId:eventId withMatrixSession:account.mxSession]; + if (room.summary.roomType == MXRoomTypeSpace) + { + // Indicates that spaces are not supported + [self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self.presentedViewController animated:YES]; + } + else + { + // Open the room page + [self showRoom:roomId andEventId:eventId withMatrixSession:account.mxSession]; + } continueUserActivity = YES; } @@ -1387,7 +1409,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { universalLinkFragmentPendingRoomAlias = @{roomId: roomIdOrAlias}; - [self handleUniversalLinkFragment:newUniversalLinkFragment]; + [self handleUniversalLinkFragment:newUniversalLinkFragment fromURL:universalLinkURL]; } else { @@ -1425,7 +1447,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if (notif.object == account.mxSession && account.mxSession.state == MXSessionStateRunning) { NSLog(@"[AppDelegate] Universal link: The session is running. Retry the link"); - [self handleUniversalLinkFragment:fragment]; + [self handleUniversalLinkFragment:fragment fromURL:universalLinkURL]; } } }]; @@ -1486,7 +1508,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if ([universalLinkFragmentPending isEqualToString:fragment]) { NSLog(@"[AppDelegate] Universal link: The user is now logged in. Retry the link"); - [self handleUniversalLinkFragment:fragment]; + [self handleUniversalLinkFragment:fragment fromURL:universalLinkURL]; } }]; } @@ -1551,7 +1573,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni if ([universalLinkFragmentPending isEqualToString:fragment]) { NSLog(@"[AppDelegate] Universal link: The user is now logged in. Retry the link"); - [self handleUniversalLinkFragment:fragment]; + [self handleUniversalLinkFragment:fragment fromURL:universalLinkURL]; } }]; } @@ -1575,6 +1597,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return continueUserActivity; } +- (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL +{ + // iOS Patch: fix vector.im urls before using it + NSURL *fixedURL = [Tools fixURLWithSeveralHashKeys:universalLinkURL]; + + return [self handleUniversalLinkFragment:fixedURL.fragment fromURL:universalLinkURL]; +} + - (void)resetPendingUniversalLink { universalLinkFragmentPending = nil; @@ -2740,6 +2770,25 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni - (void)showRoom:(NSString*)roomId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession restoreInitialDisplay:(BOOL)restoreInitialDisplay completion:(void (^)(void))completion { + if (roomId && mxSession) + { + MXRoom *room = [mxSession roomWithRoomId:roomId]; + + // Indicates that spaces are not supported + if (room.summary.roomType == MXRoomTypeSpace) + { + + [self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self.presentedViewController animated:YES]; + + if (completion) + { + completion(); + } + + return; + } + } + void (^selectRoom)(void) = ^() { // Select room to display its details (dispatch this action in order to let TabBarController end its refresh) [self.masterTabBarController selectRoomWithId:roomId andEventId:eventId inMatrixSession:mxSession completion:^{ diff --git a/Riot/Modules/Common/Buttons/Close/CloseButton.swift b/Riot/Modules/Common/Buttons/Close/CloseButton.swift index adf2b0226..22a0ba8c8 100644 --- a/Riot/Modules/Common/Buttons/Close/CloseButton.swift +++ b/Riot/Modules/Common/Buttons/Close/CloseButton.swift @@ -21,9 +21,9 @@ final class CloseButton: UIButton, Themable { // MARK: - Constants private enum CircleBackgroundConstants { - static let height: CGFloat = 30.0 + static let height: CGFloat = 26.0 static let highlightedAlha: CGFloat = 0.5 - static let normalAlha: CGFloat = 1.0 + static let normalAlha: CGFloat = 0.8 } // MARK: - Properties @@ -109,6 +109,6 @@ final class CloseButton: UIButton, Themable { func update(theme: Theme) { self.theme = theme - self.circleBackgroundView.backgroundColor = theme.headerTextSecondaryColor + self.circleBackgroundView.backgroundColor = theme.secondaryCircleButtonBackgroundColor } } diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index c396da4ff..e510c5227 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -70,6 +70,8 @@ @property (nonatomic, strong) RoomsDirectoryCoordinatorBridgePresenter *roomsDirectoryCoordinatorBridgePresenter; +@property (nonatomic, strong) SpaceFeatureUnavailablePresenter *spaceFeatureUnavailablePresenter; + @end @implementation RecentsViewController @@ -870,6 +872,16 @@ return recentsDataSource; } +- (void)showSpaceInviteNotAvailable +{ + if (!self.spaceFeatureUnavailablePresenter) + { + self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new]; + } + + [self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self animated:YES]; +} + #pragma mark - MXKDataSourceDelegate - (Class)cellViewClassForCellData:(MXKCellData*)cellData @@ -905,6 +917,13 @@ { // Retrieve the invited room MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; + + if (invitedRoom.summary.roomType == MXRoomTypeSpace) + { + // Indicates that spaces are not supported + [self showSpaceInviteNotAvailable]; + return; + } // Display the room preview [self dispayRoomWithRoomId:invitedRoom.roomId inMatrixSession:invitedRoom.mxSession]; @@ -913,6 +932,13 @@ { // Retrieve the invited room MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; + + if (invitedRoom.summary.roomType == MXRoomTypeSpace) + { + // Indicates that spaces are not supported + [self showSpaceInviteNotAvailable]; + return; + } // Accept invitation [self joinRoom:invitedRoom completion:nil]; @@ -921,7 +947,7 @@ { // Retrieve the invited room MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; - + [self cancelEditionMode:isRefreshPending]; // Decline the invitation @@ -1372,8 +1398,13 @@ // Retrieve the invited room MXRoom* invitedRoom = cellData.roomSummary.room; + if (invitedRoom.summary.roomType == MXRoomTypeSpace) + { + // Indicates that spaces are not supported + [self showSpaceInviteNotAvailable]; + } // Check if can show preview for the invited room - if ([self canShowRoomPreviewFor:invitedRoom]) + else if ([self canShowRoomPreviewFor:invitedRoom]) { // Display the room preview [self dispayRoomWithRoomId:invitedRoom.roomId inMatrixSession:invitedRoom.mxSession]; diff --git a/Riot/Modules/Communities/DataSources/GroupsDataSource.m b/Riot/Modules/Communities/DataSources/GroupsDataSource.m index 28a1cabe8..f0c84e190 100644 --- a/Riot/Modules/Communities/DataSources/GroupsDataSource.m +++ b/Riot/Modules/Communities/DataSources/GroupsDataSource.m @@ -15,9 +15,79 @@ */ #import "GroupsDataSource.h" +#import "Riot-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; @@ -44,4 +114,13 @@ 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.m b/Riot/Modules/Communities/GroupsViewController.m index dadc807af..afeea1bd4 100644 --- a/Riot/Modules/Communities/GroupsViewController.m +++ b/Riot/Modules/Communities/GroupsViewController.m @@ -79,6 +79,7 @@ //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]; diff --git a/Riot/Modules/Communities/Home/GroupHomeViewController.m b/Riot/Modules/Communities/Home/GroupHomeViewController.m index d0c0b2605..a9f939bf6 100644 --- a/Riot/Modules/Communities/Home/GroupHomeViewController.m +++ b/Riot/Modules/Communities/Home/GroupHomeViewController.m @@ -786,11 +786,8 @@ if ([Tools isUniversalLink:URL]) { shouldInteractWithURL = NO; - - // iOS Patch: fix vector.im urls before using it - NSURL *fixedURL = [Tools fixURLWithSeveralHashKeys:URL]; - - [[AppDelegate theDelegate] handleUniversalLinkFragment:fixedURL.fragment]; + + [[AppDelegate theDelegate] handleUniversalLinkURL:URL]; } // Open a detail screen about the clicked user else if ([MXTools isMatrixUserIdentifier:absoluteURLString]) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index d08af5d10..d1f6babe1 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -3410,10 +3410,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { shouldDoAction = NO; - // iOS Patch: fix vector.im urls before using it - NSURL *fixedURL = [Tools fixURLWithSeveralHashKeys:url]; - - [[AppDelegate theDelegate] handleUniversalLinkFragment:fixedURL.fragment]; + [[AppDelegate theDelegate] handleUniversalLinkURL:url]; } // Open a detail screen about the clicked user else if ([MXTools isMatrixUserIdentifier:absoluteURLString]) @@ -3452,7 +3449,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Open the room or preview it NSString *fragment = [NSString stringWithFormat:@"/room/%@", [MXTools encodeURIComponent:roomIdOrAlias]]; - [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment]; + [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:url]; } // Preview the clicked group else if ([MXTools isMatrixGroupIdentifier:absoluteURLString]) @@ -3461,7 +3458,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Open the group or preview it NSString *fragment = [NSString stringWithFormat:@"/group/%@", [MXTools encodeURIComponent:absoluteURLString]]; - [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment]; + [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:url]; } else if ([absoluteURLString hasPrefix:EventFormatterOnReRequestKeysLinkAction]) { diff --git a/Riot/Modules/Spaces/BetaAnnounceCell.swift b/Riot/Modules/Spaces/BetaAnnounceCell.swift new file mode 100644 index 000000000..dac6dca86 --- /dev/null +++ b/Riot/Modules/Spaces/BetaAnnounceCell.swift @@ -0,0 +1,94 @@ +// +// 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 +import Reusable + +@objc protocol BetaAnnounceCellDelegate: class { + func betaAnnounceCellDidTapCloseButton(_ cell: BetaAnnounceCell) +} + +/// BetaAnnounceCell enables to show coming beta feature +final class BetaAnnounceCell: UITableViewCell, Themable { + + // MARK: - Constants + + private enum Constants { + static let cardBackgroundViewCornersRadius: CGFloat = 8.0 + } + + // MARK: - Properties + + @IBOutlet private weak var cardBackgroundView: UIView! + @IBOutlet private weak var closeButton: CloseButton! + @IBOutlet private weak var badgeView: UIView! + @IBOutlet private weak var badgeLabel: UILabel! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var subtitleLabel: UILabel! + @IBOutlet private weak var informationLabel: UILabel! + + @objc weak var delegate: BetaAnnounceCellDelegate? + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + + self.badgeLabel.text = VectorL10n.spaceBetaAnnounceBadge + self.titleLabel.text = VectorL10n.spaceBetaAnnounceTitle + self.subtitleLabel.text = VectorL10n.spaceBetaAnnounceSubtitle + self.informationLabel.text = VectorL10n.spaceBetaAnnounceInformation + + self.badgeView.layer.masksToBounds = true + self.cardBackgroundView.layer.masksToBounds = true + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.cardBackgroundView.layer.cornerRadius = Constants.cardBackgroundViewCornersRadius + self.badgeView.layer.cornerRadius = self.badgeView.frame.height/2 + } + + // MARK: - Public + + func update(theme: Theme) { + self.closeButton.update(theme: theme) + self.titleLabel.textColor = theme.textPrimaryColor + self.subtitleLabel.textColor = theme.textPrimaryColor + self.informationLabel.textColor = theme.textSecondaryColor + self.cardBackgroundView.backgroundColor = theme.baseColor + self.contentView.backgroundColor = theme.backgroundColor + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.delegate?.betaAnnounceCellDidTapCloseButton(self) + } +} + +// Copy past from NibReusable in order to expose these methods to ObjC +extension BetaAnnounceCell { + @objc static var reuseIdentifier: String { + return String(describing: self) + } + + @objc static var nib: UINib { + return UINib(nibName: String(describing: self), bundle: Bundle(for: self)) + } +} diff --git a/Riot/Modules/Spaces/BetaAnnounceCell.xib b/Riot/Modules/Spaces/BetaAnnounceCell.xib new file mode 100644 index 000000000..1ecc88ccf --- /dev/null +++ b/Riot/Modules/Spaces/BetaAnnounceCell.xib @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.storyboard b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.storyboard new file mode 100644 index 000000000..129474cf7 --- /dev/null +++ b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.storyboard @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.swift b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.swift new file mode 100644 index 000000000..64dbda5eb --- /dev/null +++ b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.swift @@ -0,0 +1,97 @@ +// +// 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 + +final class SpaceFeatureUnaivableViewController: UIViewController { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var artworkImageView: UIImageView! + @IBOutlet private weak var subtitleLabel: UILabel! + @IBOutlet private weak var informationLabel: UILabel! + + // MARK: Private + + private var theme: Theme! + + // MARK: - Setup + + class func instantiate() -> SpaceFeatureUnaivableViewController { + let viewController = StoryboardScene.SpaceFeatureUnaivableViewController.initialScene.instantiate() + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + self.setupViews() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + self.title = VectorL10n.spaceFeatureUnavailableTitle + + self.subtitleLabel.text = VectorL10n.spaceFeatureUnavailableSubtitle + self.informationLabel.text = VectorL10n.spaceFeatureUnavailableInformation + } + + // MARK: - Public + + func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.backgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.subtitleLabel.textColor = theme.textPrimaryColor + self.informationLabel.textColor = theme.textSecondaryColor + + // Artwork image view + + let artworkImage = ThemeService.shared().isCurrentThemeDark() ? Asset.Images.featureUnavaibleArtworkDark.image : Asset.Images.featureUnavaibleArtwork.image + + self.artworkImageView.image = artworkImage + } + + func fill(informationText: String, shareLink: URL) { + self.subtitleLabel.text = informationText + } +} diff --git a/Riot/Modules/Spaces/SpaceFeatureUnavailablePresenter.swift b/Riot/Modules/Spaces/SpaceFeatureUnavailablePresenter.swift new file mode 100644 index 000000000..c7633f06a --- /dev/null +++ b/Riot/Modules/Spaces/SpaceFeatureUnavailablePresenter.swift @@ -0,0 +1,47 @@ +// +// 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 + +/// SpaceFeatureUnavailablePresenter enables to present modals for unavailable space features +@objcMembers +final class SpaceFeatureUnavailablePresenter: NSObject { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Private + + private weak var presentingViewController: UIViewController? + + // MARK: - Public + + func presentUnavailableFeature(from presentingViewController: UIViewController, + animated: Bool) { + + let spaceFeatureUnavailableVC = SpaceFeatureUnaivableViewController.instantiate() + + let navigationVC = RiotNavigationController(rootViewController: spaceFeatureUnavailableVC) + + spaceFeatureUnavailableVC.navigationItem.rightBarButtonItem = MXKBarButtonItem(title: Bundle.mxk_localizedString(forKey: "ok"), style: .plain, action: { [weak navigationVC] in + navigationVC?.dismiss(animated: true) + }) + + navigationVC.modalPresentationStyle = .formSheet + presentingViewController.present(navigationVC, animated: animated, completion: nil) + } +} diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index f0038508c..11603bf59 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -365,6 +365,11 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm"; self.encryptedMessagesTextFont = [UIFont italicSystemFontOfSize:15]; self.emojiOnlyTextFont = [UIFont systemFontOfSize:48]; self.editionMentionTextFont = [UIFont systemFontOfSize:12]; + + // Handle space room type, enables to show space in room list + defaultRoomSummaryUpdater.showRoomTypeStrings = @[ + MXRoomTypeStringSpace + ]; } return self; }