566 lines
17 KiB
Objective-C
566 lines
17 KiB
Objective-C
/*
|
|
Copyright 2018-2024 New Vector Ltd.
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "MXKTableViewController.h"
|
|
|
|
#import "UIViewController+MatrixKit.h"
|
|
#import "MXSession+MatrixKit.h"
|
|
|
|
@interface MXKTableViewController ()
|
|
{
|
|
/**
|
|
Array of `MXSession` instances.
|
|
*/
|
|
NSMutableArray *mxSessionArray;
|
|
|
|
/**
|
|
Keep reference on the pushed view controllers to release them correctly
|
|
*/
|
|
NSMutableArray *childViewControllers;
|
|
}
|
|
@end
|
|
|
|
@implementation MXKTableViewController
|
|
@synthesize defaultBarTintColor, enableBarTintColorStatusChange;
|
|
@synthesize barTitleColor;
|
|
@synthesize mainSession;
|
|
@synthesize activityIndicator, rageShakeManager;
|
|
@synthesize childViewControllers;
|
|
|
|
#pragma mark -
|
|
|
|
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
|
|
{
|
|
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
|
if (self)
|
|
{
|
|
[self finalizeInit];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
|
|
{
|
|
self = [super initWithCoder:aDecoder];
|
|
if (self)
|
|
{
|
|
[self finalizeInit];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)finalizeInit
|
|
{
|
|
// Set default properties values
|
|
defaultBarTintColor = nil;
|
|
barTitleColor = nil;
|
|
enableBarTintColorStatusChange = YES;
|
|
rageShakeManager = nil;
|
|
|
|
mxSessionArray = [NSMutableArray array];
|
|
childViewControllers = [NSMutableArray array];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
|
|
// Add default activity indicator
|
|
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
|
activityIndicator.backgroundColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:1.0];
|
|
activityIndicator.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
|
|
|
|
CGRect frame = activityIndicator.frame;
|
|
frame.size.width += 30;
|
|
frame.size.height += 30;
|
|
activityIndicator.bounds = frame;
|
|
[activityIndicator.layer setCornerRadius:5];
|
|
|
|
activityIndicator.center = self.view.center;
|
|
[self.view addSubview:activityIndicator];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if (activityIndicator)
|
|
{
|
|
[activityIndicator removeFromSuperview];
|
|
activityIndicator = nil;
|
|
}
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
|
|
if (self.rageShakeManager)
|
|
{
|
|
[self.rageShakeManager cancel:self];
|
|
}
|
|
|
|
// Update UI according to mxSession state, and add observer (if need)
|
|
if (mxSessionArray.count)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMatrixSessionStateDidChange:) name:kMXSessionStateDidChangeNotification object:nil];
|
|
}
|
|
[self onMatrixSessionChange];
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
[super viewWillDisappear:animated];
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionStateDidChangeNotification object:nil];
|
|
|
|
[activityIndicator stopAnimating];
|
|
|
|
if (self.rageShakeManager)
|
|
{
|
|
[self.rageShakeManager cancel:self];
|
|
}
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated
|
|
{
|
|
[super viewDidAppear:animated];
|
|
|
|
MXLogDebug(@"[MXKTableViewController] %@ viewDidAppear", self.class);
|
|
|
|
// Release properly pushed and/or presented view controller
|
|
if (childViewControllers.count)
|
|
{
|
|
for (id viewController in childViewControllers)
|
|
{
|
|
if ([viewController isKindOfClass:[UINavigationController class]])
|
|
{
|
|
UINavigationController *navigationController = (UINavigationController*)viewController;
|
|
for (id subViewController in navigationController.viewControllers)
|
|
{
|
|
if ([subViewController respondsToSelector:@selector(destroy)])
|
|
{
|
|
[subViewController destroy];
|
|
}
|
|
}
|
|
}
|
|
else if ([viewController respondsToSelector:@selector(destroy)])
|
|
{
|
|
[viewController destroy];
|
|
}
|
|
}
|
|
|
|
[childViewControllers removeAllObjects];
|
|
}
|
|
}
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated
|
|
{
|
|
[super viewDidDisappear:animated];
|
|
|
|
MXLogDebug(@"[MXKTableViewController] %@ viewDidDisappear", self.class);
|
|
}
|
|
|
|
- (void)setEnableBarTintColorStatusChange:(BOOL)enable
|
|
{
|
|
if (enableBarTintColorStatusChange != enable)
|
|
{
|
|
enableBarTintColorStatusChange = enable;
|
|
|
|
[self onMatrixSessionChange];
|
|
}
|
|
}
|
|
|
|
- (void)setDefaultBarTintColor:(UIColor *)barTintColor
|
|
{
|
|
defaultBarTintColor = barTintColor;
|
|
|
|
if (enableBarTintColorStatusChange)
|
|
{
|
|
// Force update by taking into account the matrix session state.
|
|
[self onMatrixSessionChange];
|
|
}
|
|
else
|
|
{
|
|
// Set default tintColor
|
|
self.navigationController.navigationBar.barTintColor = defaultBarTintColor;
|
|
self.mxk_mainNavigationController.navigationBar.barTintColor = defaultBarTintColor;
|
|
}
|
|
}
|
|
|
|
- (void)setBarTitleColor:(UIColor *)titleColor
|
|
{
|
|
barTitleColor = titleColor;
|
|
|
|
// Retrieve the main navigation controller if the current view controller is embedded inside a split view controller.
|
|
UINavigationController *mainNavigationController = self.mxk_mainNavigationController;
|
|
|
|
// Set navigation bar title color
|
|
NSDictionary<NSString *,id> *titleTextAttributes = self.navigationController.navigationBar.titleTextAttributes;
|
|
if (titleTextAttributes)
|
|
{
|
|
NSMutableDictionary *textAttributes = [NSMutableDictionary dictionaryWithDictionary:titleTextAttributes];
|
|
textAttributes[NSForegroundColorAttributeName] = barTitleColor;
|
|
self.navigationController.navigationBar.titleTextAttributes = textAttributes;
|
|
}
|
|
else if (barTitleColor)
|
|
{
|
|
self.navigationController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: barTitleColor};
|
|
}
|
|
|
|
if (mainNavigationController)
|
|
{
|
|
titleTextAttributes = mainNavigationController.navigationBar.titleTextAttributes;
|
|
if (titleTextAttributes)
|
|
{
|
|
NSMutableDictionary *textAttributes = [NSMutableDictionary dictionaryWithDictionary:titleTextAttributes];
|
|
textAttributes[NSForegroundColorAttributeName] = barTitleColor;
|
|
mainNavigationController.navigationBar.titleTextAttributes = textAttributes;
|
|
}
|
|
else if (barTitleColor)
|
|
{
|
|
mainNavigationController.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: barTitleColor};
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setView:(UIView *)view
|
|
{
|
|
[super setView:view];
|
|
|
|
// Keep the activity indicator (if any)
|
|
if (view && activityIndicator)
|
|
{
|
|
[self.view addSubview:activityIndicator];
|
|
}
|
|
}
|
|
|
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
|
|
{
|
|
// Keep ref on destinationViewController
|
|
[childViewControllers addObject:segue.destinationViewController];
|
|
}
|
|
|
|
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
|
|
{
|
|
// Keep ref on presented view controller
|
|
[childViewControllers addObject:viewControllerToPresent];
|
|
|
|
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)addMatrixSession:(MXSession*)mxSession
|
|
{
|
|
if (!mxSession || mxSession.state == MXSessionStateClosed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!mxSessionArray.count)
|
|
{
|
|
[mxSessionArray addObject:mxSession];
|
|
|
|
// Add matrix sessions observer on first added session
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMatrixSessionStateDidChange:) name:kMXSessionStateDidChangeNotification object:nil];
|
|
}
|
|
else if ([mxSessionArray indexOfObject:mxSession] == NSNotFound)
|
|
{
|
|
[mxSessionArray addObject:mxSession];
|
|
}
|
|
|
|
// Force update
|
|
[self onMatrixSessionChange];
|
|
}
|
|
|
|
- (void)removeMatrixSession:(MXSession*)mxSession
|
|
{
|
|
if (!mxSession)
|
|
{
|
|
return;
|
|
}
|
|
|
|
NSUInteger index = [mxSessionArray indexOfObject:mxSession];
|
|
if (index != NSNotFound)
|
|
{
|
|
[mxSessionArray removeObjectAtIndex:index];
|
|
|
|
if (!mxSessionArray.count)
|
|
{
|
|
// Remove matrix sessions observer
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionStateDidChangeNotification object:nil];
|
|
}
|
|
}
|
|
|
|
// Force update
|
|
[self onMatrixSessionChange];
|
|
}
|
|
|
|
- (NSArray*)mxSessions
|
|
{
|
|
return [NSArray arrayWithArray:mxSessionArray];
|
|
}
|
|
|
|
- (MXSession*)mainSession
|
|
{
|
|
// We consider the first added session as the main one.
|
|
if (mxSessionArray.count)
|
|
{
|
|
return [mxSessionArray firstObject];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion
|
|
{
|
|
// Check whether the view controller is embedded inside a navigation controller.
|
|
if (self.navigationController)
|
|
{
|
|
[self popViewController:self navigationController:self.navigationController animated:animated completion:completion];
|
|
}
|
|
else
|
|
{
|
|
// Suppose here the view controller has been presented modally. We dismiss it
|
|
[self dismissViewControllerAnimated:animated completion:completion];
|
|
}
|
|
}
|
|
|
|
- (void)popViewController:(UIViewController*)viewController navigationController:(UINavigationController*)navigationController animated:(BOOL)animated completion:(void (^)(void))completion
|
|
{
|
|
// We pop the view controller (except if it is the root view controller).
|
|
NSUInteger index = [navigationController.viewControllers indexOfObject:viewController];
|
|
if (index != NSNotFound)
|
|
{
|
|
if (index > 0)
|
|
{
|
|
UIViewController *previousViewController = [navigationController.viewControllers objectAtIndex:(index - 1)];
|
|
[navigationController popToViewController:previousViewController animated:animated];
|
|
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check whether the navigation controller is embedded inside a navigation controller, to pop it.
|
|
if (navigationController.navigationController)
|
|
{
|
|
[self popViewController:navigationController navigationController:navigationController.navigationController animated:animated completion:completion];
|
|
}
|
|
else
|
|
{
|
|
// Remove the root view controller
|
|
navigationController.viewControllers = @[];
|
|
// Suppose here the navigation controller has been presented modally. We dismiss it
|
|
[navigationController dismissViewControllerAnimated:animated completion:completion];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)destroy
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
mxSessionArray = nil;
|
|
childViewControllers = nil;
|
|
}
|
|
|
|
#pragma mark - Sessions handling
|
|
|
|
- (void)onMatrixSessionStateDidChange:(NSNotification *)notif
|
|
{
|
|
MXSession *mxSession = notif.object;
|
|
|
|
NSUInteger index = [mxSessionArray indexOfObject:mxSession];
|
|
if (index != NSNotFound)
|
|
{
|
|
if (mxSession.state == MXSessionStateClosed)
|
|
{
|
|
// Call here the dedicated method which may be overridden
|
|
[self removeMatrixSession:mxSession];
|
|
}
|
|
else
|
|
{
|
|
[self onMatrixSessionChange];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)onMatrixSessionChange
|
|
{
|
|
// This method is called to refresh view controller appearance on session state change,
|
|
// It is called when the view will appear to update session array by removing closed sessions.
|
|
// Indeed 'kMXSessionStateDidChangeNotification' are observed only when the view controller is visible.
|
|
|
|
// Retrieve the main navigation controller if the current view controller is embedded inside a split view controller.
|
|
UINavigationController *mainNavigationController = self.mxk_mainNavigationController;
|
|
|
|
if (mxSessionArray.count)
|
|
{
|
|
// Check each session state
|
|
UIColor *barTintColor = defaultBarTintColor;
|
|
BOOL allHomeserverNotReachable = YES;
|
|
BOOL isActivityInProgress = NO;
|
|
for (NSUInteger index = 0; index < mxSessionArray.count;)
|
|
{
|
|
MXSession *mxSession = mxSessionArray[index];
|
|
|
|
// Remove here closed sessions
|
|
if (mxSession.state == MXSessionStateClosed)
|
|
{
|
|
// Call here the dedicated method which may be overridden.
|
|
// This method will call again [onMatrixSessionChange] when session is removed.
|
|
[self removeMatrixSession:mxSession];
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (mxSession.state == MXSessionStateHomeserverNotReachable)
|
|
{
|
|
barTintColor = [UIColor orangeColor];
|
|
}
|
|
else
|
|
{
|
|
allHomeserverNotReachable = NO;
|
|
isActivityInProgress = mxSession.shouldShowActivityIndicator;
|
|
}
|
|
|
|
index ++;
|
|
}
|
|
}
|
|
|
|
// Check whether the navigation bar color depends on homeserver reachability.
|
|
if (enableBarTintColorStatusChange)
|
|
{
|
|
// The navigation bar tintColor reflects the matrix homeserver reachability status.
|
|
if (allHomeserverNotReachable)
|
|
{
|
|
self.navigationController.navigationBar.barTintColor = [UIColor redColor];
|
|
if (mainNavigationController)
|
|
{
|
|
mainNavigationController.navigationBar.barTintColor = [UIColor redColor];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self.navigationController.navigationBar.barTintColor = barTintColor;
|
|
if (mainNavigationController)
|
|
{
|
|
mainNavigationController.navigationBar.barTintColor = barTintColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run activity indicator if need
|
|
if (isActivityInProgress)
|
|
{
|
|
[self startActivityIndicator];
|
|
}
|
|
else
|
|
{
|
|
[self stopActivityIndicator];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hide potential activity indicator
|
|
[self stopActivityIndicator];
|
|
|
|
// Check whether the navigation bar color depends on homeserver reachability.
|
|
if (enableBarTintColorStatusChange)
|
|
{
|
|
// Restore default tintColor
|
|
self.navigationController.navigationBar.barTintColor = defaultBarTintColor;
|
|
if (mainNavigationController)
|
|
{
|
|
mainNavigationController.navigationBar.barTintColor = defaultBarTintColor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - Activity indicator
|
|
|
|
- (void)startActivityIndicator
|
|
{
|
|
if (activityIndicator)
|
|
{
|
|
// Keep centering the loading wheel
|
|
CGPoint center = self.view.center;
|
|
center.y += self.tableView.contentOffset.y - self.tableView.adjustedContentInset.top;
|
|
activityIndicator.center = center;
|
|
[self.view bringSubviewToFront:activityIndicator];
|
|
|
|
[activityIndicator startAnimating];
|
|
|
|
// Show the loading wheel after a delay so that if the caller calls stopActivityIndicator
|
|
// in a short future, the loading wheel will not be displayed to the end user.
|
|
activityIndicator.alpha = 0;
|
|
[UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
|
self->activityIndicator.alpha = 1;
|
|
} completion:^(BOOL finished)
|
|
{
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)stopActivityIndicator
|
|
{
|
|
// Check whether all conditions are satisfied before stopping loading wheel
|
|
BOOL isActivityInProgress = NO;
|
|
for (MXSession *mxSession in mxSessionArray)
|
|
{
|
|
if (mxSession.shouldShowActivityIndicator)
|
|
{
|
|
isActivityInProgress = YES;
|
|
}
|
|
}
|
|
if (!isActivityInProgress)
|
|
{
|
|
[activityIndicator stopAnimating];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Shake handling
|
|
|
|
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
|
{
|
|
if (motion == UIEventSubtypeMotionShake && self.rageShakeManager)
|
|
{
|
|
[self.rageShakeManager startShaking:self];
|
|
}
|
|
}
|
|
|
|
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
|
{
|
|
[self motionEnded:motion withEvent:event];
|
|
}
|
|
|
|
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
|
{
|
|
if (self.rageShakeManager)
|
|
{
|
|
[self.rageShakeManager stopShaking:self];
|
|
}
|
|
}
|
|
|
|
- (BOOL)canBecomeFirstResponder
|
|
{
|
|
return (self.rageShakeManager != nil);
|
|
}
|
|
|
|
|
|
@end
|