diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index 5342efe13..65078b069 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -23,7 +23,7 @@ extension BWIBuildSettings { secondaryAppName = "BundesMessenger" settingsScreenShowLabSettings = true authScreenShowRegister = true - showTopBanner = false + showTopBanner = true bwiShowDeveloperSettings = true bwiPersonalState = true bwiEnableErrorTracking = true diff --git a/Riot/Assets/Images.xcassets/BWI/new_features.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/new_features.imageset/Contents.json new file mode 100644 index 000000000..fff4ea669 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/new_features.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon_new-releases.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/new_features.imageset/icon_new-releases.svg b/Riot/Assets/Images.xcassets/BWI/new_features.imageset/icon_new-releases.svg new file mode 100644 index 000000000..68d70cc4c --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/new_features.imageset/icon_new-releases.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index e97ff8c5a..b0d62ec38 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -517,6 +517,9 @@ "bwi_settings_new_features_header" = "Neue Funktionen"; "bwi_settings_new_features_show_features" = "Neue Funktionen anzeigen"; +"bwi_feature_banner_header" = "Neue Funktionen"; +"bwi_feature_banner_show_more_button" = "Erfahre mehr"; +"bwi_feature_banner_advertisement_text" = "Du kannst jetzt aktive und vergangene Umfragen gesammelt in den Raumdetails einsehen (erreichbar unter Raumdetails, im Bereich \"Umfrageverlauf\")."; // MARK: - Onboarding "onboarding_splash_login_button_title" = "Loslegen"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index f7c082123..598f4ddc2 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -397,6 +397,9 @@ "bwi_settings_new_features_header" = "New Features"; "bwi_settings_new_features_show_features" = "Show new features"; +"bwi_feature_banner_header" = "New Features"; +"bwi_feature_banner_show_more_button" = "Learn more"; +"bwi_feature_banner_advertisement_text" = "You can now see a poll history in the room details."; // MARK: - Onboarding "onboarding_splash_login_button_title" = "Let's go"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 015e512bb..66650cf78 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -203,6 +203,18 @@ public class BWIL10n: NSObject { public static func bwiErrorInviteGeneral(_ p1: String) -> String { return BWIL10n.tr("Bwi", "bwi_error_invite_general", p1) } + /// Du kannst jetzt aktive und vergangene Umfragen gesammelt in den Raumdetails einsehen (erreichbar unter Raumdetails, im Bereich "Umfrageverlauf"). + public static var bwiFeatureBannerAdvertisementText: String { + return BWIL10n.tr("Bwi", "bwi_feature_banner_advertisement_text") + } + /// Neue Funktionen + public static var bwiFeatureBannerHeader: String { + return BWIL10n.tr("Bwi", "bwi_feature_banner_header") + } + /// Erfahre mehr + public static var bwiFeatureBannerShowMoreButton: String { + return BWIL10n.tr("Bwi", "bwi_feature_banner_show_more_button") + } /// Der angegebene Server ist nicht für die Nutzung mit dem %@ vorgesehen public static func bwiLoginProtectionErrorMessage(_ p1: String) -> String { return BWIL10n.tr("Bwi", "bwi_login_protection_error_message", p1) diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 4cf81539f..3a16e898b 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -48,6 +48,7 @@ internal class Asset: NSObject { internal static let birthdayCake = ImageAsset(name: "birthday_cake") internal static let fileAttachmentIcon = ImageAsset(name: "file_attachment_icon") internal static let fileScanInfected = ImageAsset(name: "file_scan_infected") + internal static let newFeatures = ImageAsset(name: "new_features") internal static let qrcodeViewfinder = ImageAsset(name: "qrcode_viewfinder") internal static let welcomeExperience1 = ImageAsset(name: "welcome_experience_1") internal static let welcomeExperience2 = ImageAsset(name: "welcome_experience_2") diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 2cf3969ea..282507835 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -56,6 +56,9 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou // Timer to not refresh publicRoomsDirectoryDataSource on every keystroke. NSTimer *publicRoomsTriggerTimer; + + // bwi: new feature banner + bool shouldHideFeatureBanner; } @property (nonatomic, strong, readwrite) RecentsDataSourceSections *sections; @@ -77,6 +80,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou - (instancetype)initWithMatrixSession:(MXSession *)mxSession recentsListService:(id)theRecentsListService { + // bwi: new feature banner + shouldHideFeatureBanner = TRUE; if (self = [super initWithMatrixSession:mxSession]) { processingQueue = dispatch_queue_create("RecentsDataSource", DISPATCH_QUEUE_SERIAL); @@ -101,6 +106,9 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou [self registerAllChatsSettingsUpdateNotification]; self.allChatsFilterOptions = [AllChatsFilterOptions new]; + // bwi: new feature banner + [self registerFeatureBannerReadStatusChangedNotification]; + [self shouldShowFeatureBanner]; } return self; } @@ -109,6 +117,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou { [self unregisterSpaceServiceDidBuildGraphNotification]; [self unregisterAllChatsSettingsUpdateNotification]; + // bwi: new feature banner + [self unregisterFeatureBannerNotification]; } #pragma mark - Properties @@ -221,6 +231,11 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou - (RecentsDataSourceSections *)makeDataSourceSections { NSMutableArray *types = [NSMutableArray array]; + // bwi: add new feature banner + if (!shouldHideFeatureBanner && BWIBuildSettings.shared.showTopBanner){ + [types addObject:@(RecentsDataSourceSectionTypeFeatureBanner)]; + } + if (self.recentsDataSourceMode == RecentsDataSourceModeRoomInvites) { [types addObject:@(RecentsDataSourceSectionTypeInvites)]; @@ -643,6 +658,10 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou NSUInteger count = 0; RecentsDataSourceSectionType sectionType = [self.sections sectionTypeForSectionIndex:section]; + // bwi: new feature banner + if (sectionType == RecentsDataSourceSectionTypeFeatureBanner) { + count = 1; + } if (sectionType == RecentsDataSourceSectionTypeCrossSigningBanner && self.crossSigningBannerDisplay != CrossSigningBannerDisplayNone) { count = 1; @@ -725,7 +744,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou (sectionType == RecentsDataSourceSectionTypeInvites && self.recentsDataSourceMode == RecentsDataSourceModeAllChats) || (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsFilterOptions.optionsCount) || (sectionType == RecentsDataSourceSectionTypeAllChats && self.currentSpace != nil && self.currentSpace.childRoomIds.count == 0) || - sectionType == RecentsDataSourceSectionTypePersonalNotes ) + sectionType == RecentsDataSourceSectionTypePersonalNotes || sectionType == RecentsDataSourceSectionTypeFeatureBanner) { return 0.0; } @@ -1911,4 +1930,63 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return NO; } +#pragma mark - bwi new feature banner + +- (void) shouldShowFeatureBanner +{ + if (BWIBuildSettings.shared.showTopBanner){ + MXSession* session = [self mxSession]; + if(!session) + return; + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + FeatureBannerVisibilityService *featureBannerService = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; + [featureBannerService isUnreadWithVersion:version completion:^(BOOL unread) { + if (unread) { + // this notification will be called either if the user clicked on the banner or wants to hide it + [[NSNotificationCenter defaultCenter] addObserverForName:@"de.bwi.messenger.hide_top_banner" object:NULL queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { + [self markFeatureBannerAsRead]; + }]; + + // this notification will be called either if the user clicked on the banner or wants to hide it using a swipe gesture + [[NSNotificationCenter defaultCenter] addObserverForName:@"de.bwi.messenger.mark_top_banner_as_read" object:NULL queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { + [self markFeatureBannerAsRead]; + }]; + + self->shouldHideFeatureBanner = FALSE; + } else { + self->shouldHideFeatureBanner = TRUE; + } + [self.delegate dataSource:self didCellChange:nil]; + }]; + } else { + self->shouldHideFeatureBanner = TRUE; + } +} + +- (void) registerFeatureBannerReadStatusChangedNotification +{ + [[NSNotificationCenter defaultCenter] addObserverForName:@"de.bwi.messenger.mark_top_banner_as_unread" object:NULL queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { + [self shouldShowFeatureBanner]; + }]; +} + +- (void) markFeatureBannerAsRead +{ + MXSession* session = [self mxSession]; + if(!session) + return; + NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + FeatureBannerVisibilityService *featureBannerService = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; + [featureBannerService markAsReadWithVersion:version]; + self->shouldHideFeatureBanner = TRUE; + [self.delegate dataSource:self didCellChange:nil]; +} + +- (void)unregisterFeatureBannerNotification +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"de.bwi.messenger.mark_top_banner_as_read" object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"de.bwi.messenger.hide_top_banner" object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:@"de.bwi.messenger.mark_top_banner_as_unread" object:nil]; +} + @end diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift b/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift index ec11a5471..d0a3aa9f1 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift @@ -32,4 +32,5 @@ import Foundation case searchedRoom case allChats case unknown + case featureBanner } diff --git a/Riot/Modules/Common/Recents/RecentsBannerViewController.h b/Riot/Modules/Common/Recents/RecentsBannerViewController.h deleted file mode 100644 index 031b45985..000000000 --- a/Riot/Modules/Common/Recents/RecentsBannerViewController.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021 BWI GmbH - * - * 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 "RecentsViewController.h" - -@interface RecentsBannerViewController : RecentsViewController - -+ (bool)isBannerHidden; - -- (void)setupTopBanner; - -@end diff --git a/Riot/Modules/Common/Recents/RecentsBannerViewController.m b/Riot/Modules/Common/Recents/RecentsBannerViewController.m deleted file mode 100644 index a8259a972..000000000 --- a/Riot/Modules/Common/Recents/RecentsBannerViewController.m +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2021 BWI GmbH - * - * 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 "RecentsBannerViewController.h" -#import "GeneratedInterface-Swift.h" - -@interface RecentsBannerViewController () -{ - TopBannerViewController *topBannerViewController; - bool bannersInitialized; - bool isBannerVisible; - bool shouldHideBanner; -} -- (void)setTopBannerViewConstraints; -- (void)showTopBanner; -- (void)hideTopBanner; -@end - -@implementation RecentsBannerViewController - -+ (bool)isBannerHidden { - NSDictionary *info = [[NSBundle mainBundle] infoDictionary]; - NSString *version = [info objectForKey:@"CFBundleShortVersionString"]; - if( [version isEqual:[[NSUserDefaults standardUserDefaults] objectForKey:@"de.bwi.messenger.top_banner_confirmation"]] ) - return TRUE; - else - return FALSE; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - bannersInitialized = FALSE; - isBannerVisible = FALSE; - shouldHideBanner = FALSE; -} - -- (void)onMatrixSessionChange -{ - [super onMatrixSessionChange]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - if( [BWIBuildSettings.shared showTopBanner] ) { - [self showTopBanner]; - } -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - if( bannersInitialized && [BWIBuildSettings.shared showTopBanner] ) { - [self hideTopBanner]; - } -} - -- (void)setTopBannerViewConstraints -{ - // get the offset in the y-coordiante so that the banner appears below the navigation bar - UIWindow *window = UIApplication.sharedApplication.windows.firstObject; - CGFloat statusBarHeight = window.safeAreaInsets.top; - CGFloat navigationBarHeight = self.navigationController.navigationBar.frame.size.height; - - NSLayoutConstraint *alignTopConstraint = [NSLayoutConstraint - constraintWithItem:topBannerViewController.view - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.view.safeAreaLayoutGuide - attribute:NSLayoutAttributeTop - multiplier:1.0 - constant:0]; - [[self view] addConstraint:alignTopConstraint]; - - NSLayoutConstraint *alignLeadingConstraint = [NSLayoutConstraint - constraintWithItem:topBannerViewController.view - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeLeading - multiplier:1.0 - constant:0]; - [[self view] addConstraint:alignLeadingConstraint]; - - NSLayoutConstraint *alignTrailingConstraint = [NSLayoutConstraint - constraintWithItem:topBannerViewController.view - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeTrailing - multiplier:1.0 - constant:0]; - [[self view] addConstraint:alignTrailingConstraint]; - - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint - constraintWithItem:topBannerViewController.view - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:NULL - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:130]; - [[self view] addConstraint:heightConstraint]; -} - -- (void)setupTopBanner -{ - NSMutableArray *bannerControllers = [NSMutableArray array]; - - MXSession* session = [self mainSession]; - if( !session || bannersInitialized) - return; - - NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; - - FeatureBannerVisibilityService *service = [[FeatureBannerVisibilityService alloc] initWithMxSession:session]; - [service isUnreadWithVersion:version completion:^(BOOL unread) { - if (unread) { - // [bannerControllers addObject:[[FeatureBannerViewController alloc] initWithSession:session version:version]]; - - self->topBannerViewController = [[TopBannerViewController alloc] initWithControllers: bannerControllers]; - - // this notification will be called either if the user clicked on the banner or wants to hide it using a swipe gesture - [[NSNotificationCenter defaultCenter] addObserverForName:@"de.bwi.messenger.hide_top_banner" object:NULL queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { - [self hideTopBanner]; - [self->topBannerViewController removeAdvertiseViewControllers]; - }]; - - // this notification will be called either if the user clicked on the banner or wants to hide it using a swipe gesture - [[NSNotificationCenter defaultCenter] addObserverForName:@"de.bwi.messenger.mark_top_banner_as_read" object:NULL queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { - self->shouldHideBanner = TRUE; - }]; - - self->bannersInitialized = TRUE; - - if( [BWIBuildSettings.shared showTopBanner] ) { - [self showTopBanner]; - } - } - }]; - -} - -- (void)showTopBanner -{ - if( isBannerVisible || shouldHideBanner) { - return; // nothing to do because the banner is already visible for the user - } - - if( [topBannerViewController.advertiseViewControllers count] > 0 ) - { - // add child view controller - [self addChildViewController:topBannerViewController]; - [[topBannerViewController view] setTranslatesAutoresizingMaskIntoConstraints:FALSE]; - [[topBannerViewController view] setBounds:CGRectMake(0, 0, self.view.bounds.size.width, 100)]; - [[self view] addSubview:[topBannerViewController view]]; - [topBannerViewController didMoveToParentViewController:self]; - - [self setTopBannerViewConstraints]; - - isBannerVisible = TRUE; - } -} - -- (void)hideTopBanner -{ - if( [[topBannerViewController advertiseViewControllers] count] > 0 ) - { - // remove child view controller - [topBannerViewController willMoveToParentViewController:nil]; - [topBannerViewController.view removeFromSuperview]; - [topBannerViewController removeFromParentViewController]; - } - - isBannerVisible = FALSE; -} - -@end diff --git a/Riot/Modules/Favorites/FavouritesViewController.h b/Riot/Modules/Favorites/FavouritesViewController.h index a9a68a4b2..3be388271 100644 --- a/Riot/Modules/Favorites/FavouritesViewController.h +++ b/Riot/Modules/Favorites/FavouritesViewController.h @@ -15,12 +15,12 @@ limitations under the License. */ -#import "RecentsBannerViewController.h" +#import "RecentsViewController.h" /** The `FavouritesViewController` screen is the view controller displayed when `Favourites` tab is selected. */ -@interface FavouritesViewController : RecentsBannerViewController +@interface FavouritesViewController : RecentsViewController + (instancetype)instantiate; diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index ecfda48b7..1b69b5497 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -61,6 +61,9 @@ class AllChatsViewController: HomeViewController { private let reviewSessionAlertSnoozeController = ReviewSessionAlertSnoozeController() + // bwi: new feature banner height + private var featureBannerViewHeight: CGFloat = 0 + private var bannerView: UIView? { didSet { bannerView?.translatesAutoresizingMaskIntoConstraints = false @@ -123,6 +126,7 @@ class AllChatsViewController: HomeViewController { recentsTableView.register(RecentEmptySectionTableViewCell.nib, forCellReuseIdentifier: RecentEmptySectionTableViewCell.reuseIdentifier) recentsTableView.register(RecentEmptySpaceSectionTableViewCell.nib, forCellReuseIdentifier: RecentEmptySpaceSectionTableViewCell.reuseIdentifier) recentsTableView.register(RecentsInvitesTableViewCell.nib, forCellReuseIdentifier: RecentsInvitesTableViewCell.reuseIdentifier) + recentsTableView.register(FeatureBannerViewCell.self, forCellReuseIdentifier: "featureBanner") recentsTableView.contentInsetAdjustmentBehavior = .automatic toolbarHeight = toolbar.frame.height @@ -284,7 +288,8 @@ class AllChatsViewController: HomeViewController { RecentsDataSourceSectionType.serverNotice.rawValue, RecentsDataSourceSectionType.suggestedRooms.rawValue, RecentsDataSourceSectionType.breadcrumbs.rawValue, - RecentsDataSourceSectionType.personalNotes.rawValue + RecentsDataSourceSectionType.personalNotes.rawValue, + RecentsDataSourceSectionType.featureBanner.rawValue ] } @@ -348,15 +353,27 @@ class AllChatsViewController: HomeViewController { guard let sectionType = sectionType(forSectionAt: section), sectionType == .invites else { return super.tableView(tableView, numberOfRowsInSection: section) } + if sectionType == .featureBanner { + return 1 + } return dataSource?.tableView(tableView, numberOfRowsInSection: section) ?? 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites else { + guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites || sectionType == .featureBanner else { return super.tableView(tableView, cellForRowAt: indexPath) } - + // bwi: feature banner cell + if sectionType == .featureBanner { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "featureBanner", for: indexPath) as? FeatureBannerViewCell else { + return UITableViewCell() + } + cell.selectionStyle = .none + cell.setupView(parent: self, rootView: FeatureBannerView(delegate: cell)) + featureBannerViewHeight = cell.calculateHeight() + return cell + } guard let dataSource = dataSource else { MXLog.failure("Missing data source") return UITableViewCell() @@ -367,20 +384,27 @@ class AllChatsViewController: HomeViewController { // MARK: - UITableViewDelegate override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites else { + guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites || sectionType == .featureBanner else { return super.tableView(tableView, heightForRowAt: indexPath) } - - return dataSource?.cellHeight(at: indexPath) ?? 0 + if sectionType == .featureBanner { + return featureBannerViewHeight + } else { + return dataSource?.cellHeight(at: indexPath) ?? 0 + } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites else { + guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites || sectionType == .featureBanner else { super.tableView(tableView, didSelectRowAt: indexPath) return } - - showRoomInviteList() + if sectionType == .invites { + showRoomInviteList() + } else { + // bwi: feature banner is not selectable + return + } } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { @@ -1035,7 +1059,7 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol { func bwiOnUnlockedByPin() { // bwi specific - self.setupTopBanner() + } // MARK: - Private diff --git a/Riot/Modules/Home/HomeViewController.h b/Riot/Modules/Home/HomeViewController.h index 0343e397c..842b79eb9 100644 --- a/Riot/Modules/Home/HomeViewController.h +++ b/Riot/Modules/Home/HomeViewController.h @@ -15,13 +15,13 @@ limitations under the License. */ -#import "RecentsBannerViewController.h" +#import "RecentsViewController.h" #import "RecentsDataSource.h" /** The `HomeViewController` screen is the main app screen. */ -@interface HomeViewController : RecentsBannerViewController +@interface HomeViewController : RecentsViewController + (instancetype)instantiate; diff --git a/Riot/Modules/People/PeopleViewController.h b/Riot/Modules/People/PeopleViewController.h index 514333769..7e03c6313 100644 --- a/Riot/Modules/People/PeopleViewController.h +++ b/Riot/Modules/People/PeopleViewController.h @@ -15,13 +15,13 @@ limitations under the License. */ -#import "RecentsBannerViewController.h" +#import "RecentsViewController.h" #import "ContactsDataSource.h" /** 'PeopleViewController' instance is used to display/filter the direct rooms and a list of contacts. */ -@interface PeopleViewController : RecentsBannerViewController +@interface PeopleViewController : RecentsViewController + (instancetype)instantiate; diff --git a/Riot/Modules/Rooms/RoomsViewController.h b/Riot/Modules/Rooms/RoomsViewController.h index 68e79ea69..2f665ef65 100644 --- a/Riot/Modules/Rooms/RoomsViewController.h +++ b/Riot/Modules/Rooms/RoomsViewController.h @@ -15,12 +15,12 @@ limitations under the License. */ -#import "RecentsBannerViewController.h" +#import "RecentsViewController.h" /** The `RoomsViewController` screen is the view controller displayed when `Rooms` tab is selected. */ -@interface RoomsViewController : RecentsBannerViewController +@interface RoomsViewController : RecentsViewController + (instancetype)instantiate; diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index ac9b24e3c..ddb60c185 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -1036,14 +1036,11 @@ switch (tabItemTag) { case TABBAR_FAVOURITES_INDEX: - [self.favouritesViewController setupTopBanner]; [self.favouritesViewController checkAppVersionOutdated]; break; case TABBAR_PEOPLE_INDEX: - [self.peopleViewController setupTopBanner]; break; case TABBAR_ROOMS_INDEX: - [self.roomsViewController setupTopBanner]; break; default: break; diff --git a/bwi/FeatureBanner/FeatureBannerView.swift b/bwi/FeatureBanner/FeatureBannerView.swift new file mode 100644 index 000000000..38b3556a4 --- /dev/null +++ b/bwi/FeatureBanner/FeatureBannerView.swift @@ -0,0 +1,169 @@ +// +/* + * Copyright (c) 2023 BWI GmbH + * + * 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 +import UIKit + + +protocol FeatureBannerDelegate { + func didPressClose() + func didPressShowDetails() +} + +@objcMembers class FeatureBannerViewCell: UITableViewCell, FeatureBannerDelegate { + private var parentViewController: UIViewController? + private let hostingController = UIHostingController(rootView: nil) + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + hostingController.view.backgroundColor = UIColor.clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func setupView(parent: UIViewController, rootView: Content) { + self.parentViewController = parent + hostingController.rootView = rootView + + let shouldControllerMove = hostingController.parent != parent + if shouldControllerMove { + removeController() + parent.addChild(hostingController) + } + + if !self.contentView.subviews.contains(hostingController.view) { + self.contentView.addSubview(hostingController.view) + self.contentView.vc_addSubViewMatchingParentSafeArea(hostingController.view) + } + + if shouldControllerMove { + hostingController.didMove(toParent: parent) + } + } + + func calculateHeight() -> CGFloat { + hostingController.view.setNeedsLayout() + hostingController.view.layoutIfNeeded() + let result = hostingController.view.intrinsicContentSize.height + + return result + } + + private func removeController() { + hostingController.willMove(toParent: nil) + hostingController.view.removeFromSuperview() + hostingController.removeFromParent() + parentViewController = nil + } + + deinit { + removeController() + } + + + // MARK: user interaction + + func hideTopBanner() { + removeController() + NotificationCenter.default.post(name: .bwiHideTopBanner, object: self, userInfo: ["type" : "feature_banner"]) + } + + func didPressClose() { + hideTopBanner() + } + + func didPressShowDetails() { + let htmlFile = Bundle.main.path(forResource: BWIBuildSettings.shared.newFeaturesHTML, ofType: "html") + if let webviewController = WebViewViewController(localHTMLFile: htmlFile) { + webviewController.title = BWIL10n.bwiSettingsNewFeaturesHeader + + hostingController.parent?.present(webviewController, animated: true, completion: { () -> Void in + self.hideTopBanner() + }) + } + } + +} + + +struct FeatureBannerView: View { + var delegate: FeatureBannerDelegate? + + var body: some View { + VStack(alignment: .center) { + closeButton + header + advertisementText + showMoreButton + } + .background(Color(ThemeService.shared().theme.tintColor)) + .cornerRadius(12) + .padding(16) + } + + var header: some View { + HStack() { + Image(Asset.Images.newFeatures.name) + .renderingMode(.template) + .foregroundColor(.white) + Text(BWIL10n.bwiFeatureBannerHeader) + .font(.system(size: 20).bold()) + .foregroundColor(.white) + } + } + + var advertisementText: some View { + Text(BWIL10n.bwiFeatureBannerAdvertisementText) + .font(.system(size: 15)) + .multilineTextAlignment(.center) + .lineLimit(nil) + .foregroundColor(.white) + .padding(5) + } + + var closeButton: some View { + HStack() { + Spacer() + Button { + delegate?.didPressClose() + } label: { + Image(Asset.Images.closeButton.name) + .renderingMode(.template) + .foregroundColor(.white) + .padding(10) + } + } + } + + var showMoreButton: some View { + Button { + delegate?.didPressShowDetails() + } label: { + Text(BWIL10n.bwiFeatureBannerShowMoreButton) + .font(.system(size: 15)) + .padding(10) + .foregroundColor(.white) + .overlay(RoundedRectangle(cornerRadius: 12) + .stroke(Color.white, lineWidth: 2)) + } + .padding(.top, 10) + .padding(.bottom, 25) + } + +} diff --git a/bwi/TopBanner/FeatureBannerVisibilityService.swift b/bwi/TopBanner/FeatureBannerVisibilityService.swift index 13a655bce..81d5b0d3e 100644 --- a/bwi/TopBanner/FeatureBannerVisibilityService.swift +++ b/bwi/TopBanner/FeatureBannerVisibilityService.swift @@ -50,7 +50,10 @@ import Foundation featureDict[version] = true - return session.setAccountData(featureDict, forType: AccountDataTypes.featureVisibility, success: nil, failure: nil) + return session.setAccountData(featureDict, forType: AccountDataTypes.featureVisibility, success: { + // bwi: update tableview + NotificationCenter.default.post(name: .bwiMarkTopBannerAsUnRead, object: nil, userInfo: ["type" : "feature_banner"]) + }, failure: nil) } func isUnread( version: String, completion: @escaping (_ unread : Bool) -> Void) { diff --git a/bwi/TopBanner/TopBannerViewController.swift b/bwi/TopBanner/TopBannerViewController.swift index d46593e91..f0571b8a6 100644 --- a/bwi/TopBanner/TopBannerViewController.swift +++ b/bwi/TopBanner/TopBannerViewController.swift @@ -85,4 +85,5 @@ extension TopBannerViewController: UIPageViewControllerDelegate { extension NSNotification.Name { static let bwiHideTopBanner = Notification.Name("de.bwi.messenger.hide_top_banner") static let bwiMarkTopBannerAsRead = Notification.Name("de.bwi.messenger.mark_top_banner_as_read") + static let bwiMarkTopBannerAsUnRead = Notification.Name("de.bwi.messenger.mark_top_banner_as_unread") }