MESSENGER-2638 new layout feature banner

This commit is contained in:
JanNiklas Grabowski
2023-03-28 10:38:04 +00:00
parent 92099d3813
commit bba325bbe9
20 changed files with 341 additions and 242 deletions
@@ -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<RecentsListServiceProtocol>)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
@@ -32,4 +32,5 @@ import Foundation
case searchedRoom
case allChats
case unknown
case featureBanner
}
@@ -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
@@ -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
@@ -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;
@@ -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<FeatureBannerView>.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<FeatureBannerView> 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
+2 -2
View File
@@ -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 <UITableViewDataSource, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@interface HomeViewController : RecentsViewController <UITableViewDataSource, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
+ (instancetype)instantiate;
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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;
@@ -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;