mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-29 04:36:58 +02:00
Remove all types related to Groups
This commit is contained in:
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "MXKViewController.h"
|
||||
#import "MXKSessionGroupsDataSource.h"
|
||||
|
||||
@class MXKGroupListViewController;
|
||||
|
||||
/**
|
||||
`MXKGroupListViewController` delegate.
|
||||
*/
|
||||
@protocol MXKGroupListViewControllerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
Tells the delegate that the user selected a group.
|
||||
|
||||
@param groupListViewController the `MXKGroupListViewController` instance.
|
||||
@param group the selected group.
|
||||
@param mxSession the matrix session in which the group is defined.
|
||||
*/
|
||||
- (void)groupListViewController:(MXKGroupListViewController *)groupListViewController didSelectGroup:(MXGroup*)group inMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
This view controller displays a group list.
|
||||
*/
|
||||
@interface MXKGroupListViewController : MXKViewController <MXKDataSourceDelegate, UITableViewDelegate, UISearchBarDelegate>
|
||||
{
|
||||
@protected
|
||||
|
||||
/**
|
||||
The fake top view displayed in case of vertical bounce.
|
||||
*/
|
||||
__weak UIView *topview;
|
||||
}
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UISearchBar *groupsSearchBar;
|
||||
@property (weak, nonatomic) IBOutlet UITableView *groupsTableView;
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsSearchBarTopConstraint;
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsSearchBarHeightConstraint;
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *groupsTableViewBottomConstraint;
|
||||
|
||||
/**
|
||||
The current data source associated to the view controller.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXKSessionGroupsDataSource *dataSource;
|
||||
|
||||
/**
|
||||
The delegate for the view controller.
|
||||
*/
|
||||
@property (nonatomic, weak) id<MXKGroupListViewControllerDelegate> delegate;
|
||||
|
||||
/**
|
||||
Enable the search option by adding a navigation item in the navigation bar (YES by default).
|
||||
Set NO this property to disable this option and hide the related bar button.
|
||||
*/
|
||||
@property (nonatomic) BOOL enableBarButtonSearch;
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
/**
|
||||
Returns the `UINib` object initialized for a `MXKGroupListViewController`.
|
||||
|
||||
@return The initialized `UINib` object or `nil` if there were errors during initialization
|
||||
or the nib file could not be located.
|
||||
|
||||
@discussion You may override this method to provide a customized nib. If you do,
|
||||
you should also override `groupListViewController` to return your
|
||||
view controller loaded from your custom nib.
|
||||
*/
|
||||
+ (UINib *)nib;
|
||||
|
||||
/**
|
||||
Creates and returns a new `MXKGroupListViewController` object.
|
||||
|
||||
@discussion This is the designated initializer for programmatic instantiation.
|
||||
@return An initialized `MXKGroupListViewController` object if successful, `nil` otherwise.
|
||||
*/
|
||||
+ (instancetype)groupListViewController;
|
||||
|
||||
/**
|
||||
Display the groups described in the provided data source.
|
||||
|
||||
Note: The provided data source will replace the current data source if any. The caller
|
||||
should dispose properly this data source if it is not used anymore.
|
||||
|
||||
@param listDataSource the data source providing the groups list.
|
||||
*/
|
||||
- (void)displayList:(MXKSessionGroupsDataSource*)listDataSource;
|
||||
|
||||
/**
|
||||
Refresh the groups table display.
|
||||
*/
|
||||
- (void)refreshGroupsTable;
|
||||
|
||||
/**
|
||||
Hide/show the search bar at the top of the groups table view.
|
||||
*/
|
||||
- (void)hideSearchBar:(BOOL)hidden;
|
||||
|
||||
@end
|
||||
@@ -1,614 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKGroupListViewController.h"
|
||||
|
||||
#import "MXKGroupTableViewCell.h"
|
||||
#import "MXKTableViewHeaderFooterWithLabel.h"
|
||||
|
||||
@interface MXKGroupListViewController ()
|
||||
{
|
||||
/**
|
||||
The data source providing UITableViewCells
|
||||
*/
|
||||
MXKSessionGroupsDataSource *dataSource;
|
||||
|
||||
/**
|
||||
Search handling
|
||||
*/
|
||||
UIBarButtonItem *searchButton;
|
||||
BOOL ignoreSearchRequest;
|
||||
|
||||
/**
|
||||
The reconnection animated view.
|
||||
*/
|
||||
UIView* reconnectingView;
|
||||
|
||||
/**
|
||||
The current table view header if any.
|
||||
*/
|
||||
UIView* tableViewHeaderView;
|
||||
|
||||
/**
|
||||
The latest server sync date
|
||||
*/
|
||||
NSDate* latestServerSync;
|
||||
|
||||
/**
|
||||
The restart the event connnection
|
||||
*/
|
||||
BOOL restartConnection;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKGroupListViewController
|
||||
@synthesize dataSource;
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
+ (UINib *)nib
|
||||
{
|
||||
return [UINib nibWithNibName:NSStringFromClass([MXKGroupListViewController class])
|
||||
bundle:[NSBundle bundleForClass:[MXKGroupListViewController class]]];
|
||||
}
|
||||
|
||||
+ (instancetype)groupListViewController
|
||||
{
|
||||
return [[[self class] alloc] initWithNibName:NSStringFromClass([MXKGroupListViewController class])
|
||||
bundle:[NSBundle bundleForClass:[MXKGroupListViewController class]]];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)finalizeInit
|
||||
{
|
||||
[super finalizeInit];
|
||||
|
||||
_enableBarButtonSearch = YES;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
// Check whether the view controller has been pushed via storyboard
|
||||
if (!_groupsTableView)
|
||||
{
|
||||
// Instantiate view controller objects
|
||||
[[[self class] nib] instantiateWithOwner:self options:nil];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
// Adjust search bar Top constraint to take into account potential navBar.
|
||||
if (_groupsSearchBarTopConstraint)
|
||||
{
|
||||
[NSLayoutConstraint deactivateConstraints:@[_groupsSearchBarTopConstraint]];
|
||||
|
||||
_groupsSearchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.groupsSearchBar
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0f
|
||||
constant:0.0f];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[_groupsSearchBarTopConstraint]];
|
||||
}
|
||||
|
||||
// Adjust table view Bottom constraint to take into account tabBar.
|
||||
if (_groupsTableViewBottomConstraint)
|
||||
{
|
||||
[NSLayoutConstraint deactivateConstraints:@[_groupsTableViewBottomConstraint]];
|
||||
|
||||
_groupsTableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self.groupsTableView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0f
|
||||
constant:0.0f];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[_groupsTableViewBottomConstraint]];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// Hide search bar by default
|
||||
[self hideSearchBar:YES];
|
||||
|
||||
// Apply search option in navigation bar
|
||||
self.enableBarButtonSearch = _enableBarButtonSearch;
|
||||
|
||||
// Add an accessory view to the search bar in order to retrieve keyboard view.
|
||||
self.groupsSearchBar.inputAccessoryView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
|
||||
// Finalize table view configuration
|
||||
// Note: self-sizing cells and self-sizing section headers are enabled from the nib file.
|
||||
self.groupsTableView.delegate = self;
|
||||
self.groupsTableView.dataSource = dataSource; // Note: dataSource may be nil here
|
||||
self.groupsTableView.estimatedSectionHeaderHeight = 30; // The value set in the nib seems not available for iOS version < 10.
|
||||
|
||||
// Set up classes to use for the cells and the section headers.
|
||||
[self.groupsTableView registerNib:MXKGroupTableViewCell.nib forCellReuseIdentifier:MXKGroupTableViewCell.defaultReuseIdentifier];
|
||||
[self.groupsTableView registerNib:MXKTableViewHeaderFooterWithLabel.nib forHeaderFooterViewReuseIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier];
|
||||
|
||||
// Add a top view which will be displayed in case of vertical bounce.
|
||||
CGFloat height = self.groupsTableView.frame.size.height;
|
||||
UIView *topview = [[UIView alloc] initWithFrame:CGRectMake(0,-height,self.groupsTableView.frame.size.width,height)];
|
||||
topview.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
topview.backgroundColor = [UIColor groupTableViewBackgroundColor];
|
||||
[self.groupsTableView addSubview:topview];
|
||||
self->topview = topview;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// Restore search mechanism (if enabled)
|
||||
ignoreSearchRequest = NO;
|
||||
|
||||
// Observe the server sync
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncNotification) name:kMXSessionDidSyncNotification object:nil];
|
||||
|
||||
// Do a full reload
|
||||
[self refreshGroupsTable];
|
||||
|
||||
// Refresh all groups summary
|
||||
[self.dataSource refreshGroupsSummary:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
// The user may still press search button whereas the view disappears
|
||||
ignoreSearchRequest = YES;
|
||||
|
||||
// Leave potential search session
|
||||
if (!self.groupsSearchBar.isHidden)
|
||||
{
|
||||
[self searchBarCancelButtonClicked:self.groupsSearchBar];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidSyncNotification object:nil];
|
||||
|
||||
[self removeReconnectingView];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
self.groupsSearchBar.inputAccessoryView = nil;
|
||||
|
||||
searchButton = nil;
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
#pragma mark - Override MXKViewController
|
||||
|
||||
- (void)onKeyboardShowAnimationComplete
|
||||
{
|
||||
// Report the keyboard view in order to track keyboard frame changes
|
||||
self.keyboardView = _groupsSearchBar.inputAccessoryView.superview;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
- (void)setKeyboardHeight:(CGFloat)keyboardHeight
|
||||
{
|
||||
// Deduce the bottom constraint for the table view (Don't forget the potential tabBar)
|
||||
CGFloat tableViewBottomConst = keyboardHeight - self.bottomLayoutGuide.length;
|
||||
// Check whether the keyboard is over the tabBar
|
||||
if (tableViewBottomConst < 0)
|
||||
{
|
||||
tableViewBottomConst = 0;
|
||||
}
|
||||
|
||||
// Update constraints
|
||||
_groupsTableViewBottomConstraint.constant = tableViewBottomConst;
|
||||
|
||||
// Force layout immediately to take into account new constraint
|
||||
[self.view layoutIfNeeded];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
self.groupsTableView.dataSource = nil;
|
||||
self.groupsTableView.delegate = nil;
|
||||
self.groupsTableView = nil;
|
||||
|
||||
dataSource.delegate = nil;
|
||||
dataSource = nil;
|
||||
|
||||
_delegate = nil;
|
||||
|
||||
[topview removeFromSuperview];
|
||||
topview = nil;
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setEnableBarButtonSearch:(BOOL)enableBarButtonSearch
|
||||
{
|
||||
_enableBarButtonSearch = enableBarButtonSearch;
|
||||
|
||||
if (enableBarButtonSearch)
|
||||
{
|
||||
if (!searchButton)
|
||||
{
|
||||
searchButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(search:)];
|
||||
}
|
||||
|
||||
// Add it in right bar items
|
||||
NSArray *rightBarButtonItems = self.navigationItem.rightBarButtonItems;
|
||||
self.navigationItem.rightBarButtonItems = rightBarButtonItems ? [rightBarButtonItems arrayByAddingObject:searchButton] : @[searchButton];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableArray *rightBarButtonItems = [NSMutableArray arrayWithArray: self.navigationItem.rightBarButtonItems];
|
||||
[rightBarButtonItems removeObject:searchButton];
|
||||
self.navigationItem.rightBarButtonItems = rightBarButtonItems;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)displayList:(MXKSessionGroupsDataSource *)listDataSource
|
||||
{
|
||||
// Cancel registration on existing dataSource if any
|
||||
if (dataSource)
|
||||
{
|
||||
dataSource.delegate = nil;
|
||||
|
||||
// Remove associated matrix sessions
|
||||
NSArray *mxSessions = self.mxSessions;
|
||||
for (MXSession *mxSession in mxSessions)
|
||||
{
|
||||
[self removeMatrixSession:mxSession];
|
||||
}
|
||||
}
|
||||
|
||||
dataSource = listDataSource;
|
||||
dataSource.delegate = self;
|
||||
|
||||
// Report the matrix session at view controller level to update UI according to session state
|
||||
[self addMatrixSession:listDataSource.mxSession];
|
||||
|
||||
if (self.groupsTableView)
|
||||
{
|
||||
// Set up table data source
|
||||
self.groupsTableView.dataSource = dataSource;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshGroupsTable
|
||||
{
|
||||
// For now, do a simple full reload
|
||||
[self.groupsTableView reloadData];
|
||||
}
|
||||
|
||||
- (void)hideSearchBar:(BOOL)hidden
|
||||
{
|
||||
self.groupsSearchBar.hidden = hidden;
|
||||
self.groupsSearchBarHeightConstraint.constant = hidden ? 0 : 44;
|
||||
[self.view setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - Action
|
||||
|
||||
- (IBAction)search:(id)sender
|
||||
{
|
||||
// The user may have pressed search button whereas the view controller was disappearing
|
||||
if (ignoreSearchRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.groupsSearchBar.isHidden)
|
||||
{
|
||||
// Check whether there are data in which search
|
||||
if ([self.dataSource numberOfSectionsInTableView:self.groupsTableView])
|
||||
{
|
||||
[self hideSearchBar:NO];
|
||||
|
||||
// Create search bar
|
||||
[self.groupsSearchBar becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self searchBarCancelButtonClicked: self.groupsSearchBar];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSourceDelegate
|
||||
|
||||
- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData
|
||||
{
|
||||
// Return the default group table view cell
|
||||
return MXKGroupTableViewCell.class;
|
||||
}
|
||||
|
||||
- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
|
||||
{
|
||||
// Return the default group table view cell
|
||||
return MXKGroupTableViewCell.defaultReuseIdentifier;
|
||||
}
|
||||
|
||||
- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
|
||||
{
|
||||
// For now, do a simple full reload
|
||||
[self refreshGroupsTable];
|
||||
}
|
||||
|
||||
- (void)dataSource:(MXKDataSource *)dataSource didAddMatrixSession:(MXSession *)mxSession
|
||||
{
|
||||
[self addMatrixSession:mxSession];
|
||||
}
|
||||
|
||||
- (void)dataSource:(MXKDataSource *)dataSource didRemoveMatrixSession:(MXSession *)mxSession
|
||||
{
|
||||
[self removeMatrixSession:mxSession];
|
||||
}
|
||||
|
||||
#pragma mark - UITableView delegate
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return tableView.estimatedRowHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
if (tableView.numberOfSections > 1)
|
||||
{
|
||||
return tableView.estimatedSectionHeaderHeight;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Refresh here the estimated row height
|
||||
tableView.estimatedRowHeight = cell.frame.size.height;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section
|
||||
{
|
||||
// Refresh here the estimated header height
|
||||
tableView.estimatedSectionHeaderHeight = view.frame.size.height;
|
||||
}
|
||||
|
||||
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
MXKTableViewHeaderFooterWithLabel *sectionHeader;
|
||||
|
||||
if (tableView.numberOfSections > 1)
|
||||
{
|
||||
sectionHeader = [tableView dequeueReusableHeaderFooterViewWithIdentifier:MXKTableViewHeaderFooterWithLabel.defaultReuseIdentifier];
|
||||
|
||||
sectionHeader.mxkLabel.text = [self.dataSource tableView:tableView titleForHeaderInSection:section];
|
||||
}
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (_delegate)
|
||||
{
|
||||
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
|
||||
if ([selectedCell conformsToProtocol:@protocol(MXKCellRendering)])
|
||||
{
|
||||
id<MXKCellRendering> cell = (id<MXKCellRendering>)selectedCell;
|
||||
|
||||
if ([cell respondsToSelector:@selector(renderedCellData)])
|
||||
{
|
||||
MXKCellData *cellData = cell.renderedCellData;
|
||||
if ([cellData conformsToProtocol:@protocol(MXKGroupCellDataStoring)])
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupCellData = (id<MXKGroupCellDataStoring>)cellData;
|
||||
[_delegate groupListViewController:self didSelectGroup:groupCellData.group inMatrixSession:self.mainSession];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the keyboard when user select a room
|
||||
// do not hide the searchBar until the view controller disappear
|
||||
// on tablets / iphone 6+, the user could expect to search again while looking at a room
|
||||
[self.groupsSearchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
|
||||
{
|
||||
// Release here resources, and restore reusable cells
|
||||
if ([cell respondsToSelector:@selector(didEndDisplay)])
|
||||
{
|
||||
[(id<MXKCellRendering>)cell didEndDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
||||
{
|
||||
// Detect vertical bounce at the top of the tableview to trigger reconnection.
|
||||
if (scrollView == _groupsTableView)
|
||||
{
|
||||
[self detectPullToKick:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
||||
{
|
||||
if (scrollView == _groupsTableView)
|
||||
{
|
||||
[self managePullToKick:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
if (scrollView == _groupsTableView)
|
||||
{
|
||||
if (scrollView.contentOffset.y + scrollView.adjustedContentInset.top == 0)
|
||||
{
|
||||
[self managePullToKick:scrollView];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
// Apply filter
|
||||
if (searchText.length)
|
||||
{
|
||||
[self.dataSource searchWithPatterns:@[searchText]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.dataSource searchWithPatterns:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
// "Done" key has been pressed
|
||||
[searchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
// Leave search
|
||||
[searchBar resignFirstResponder];
|
||||
|
||||
[self hideSearchBar:YES];
|
||||
|
||||
self.groupsSearchBar.text = nil;
|
||||
|
||||
// Refresh display
|
||||
[self.dataSource searchWithPatterns:nil];
|
||||
}
|
||||
|
||||
#pragma mark - resync management
|
||||
|
||||
- (void)onSyncNotification
|
||||
{
|
||||
latestServerSync = [NSDate date];
|
||||
|
||||
MXWeakify(self);
|
||||
|
||||
// Refresh all groups summary
|
||||
[self.dataSource refreshGroupsSummary:^{
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[self removeReconnectingView];
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)canReconnect
|
||||
{
|
||||
// avoid restarting connection if some data has been received within 1 second (1000 : latestServerSync is null)
|
||||
NSTimeInterval interval = latestServerSync ? [[NSDate date] timeIntervalSinceDate:latestServerSync] : 1000;
|
||||
return (interval > 1) && [self.mainSession reconnect];
|
||||
}
|
||||
|
||||
- (void)addReconnectingView
|
||||
{
|
||||
if (!reconnectingView)
|
||||
{
|
||||
UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
||||
spinner.transform = CGAffineTransformMakeScale(0.75f, 0.75f);
|
||||
CGRect frame = spinner.frame;
|
||||
frame.size.height = 80; // 80 * 0.75 = 60
|
||||
spinner.bounds = frame;
|
||||
spinner.color = [UIColor darkGrayColor];
|
||||
spinner.hidesWhenStopped = NO;
|
||||
spinner.backgroundColor = _groupsTableView.backgroundColor;
|
||||
[spinner startAnimating];
|
||||
|
||||
// no need to manage constraints here, IOS defines them.
|
||||
tableViewHeaderView = _groupsTableView.tableHeaderView;
|
||||
_groupsTableView.tableHeaderView = reconnectingView = spinner;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeReconnectingView
|
||||
{
|
||||
if (reconnectingView && !restartConnection)
|
||||
{
|
||||
_groupsTableView.tableHeaderView = tableViewHeaderView;
|
||||
reconnectingView = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Detect if the current connection must be restarted.
|
||||
The spinner is displayed until the overscroll ends (and scrollViewDidEndDecelerating is called).
|
||||
*/
|
||||
- (void)detectPullToKick:(UIScrollView *)scrollView
|
||||
{
|
||||
if (!reconnectingView)
|
||||
{
|
||||
// detect if the user scrolls over the tableview top
|
||||
restartConnection = (scrollView.contentOffset.y + scrollView.adjustedContentInset.top < -128);
|
||||
|
||||
if (restartConnection)
|
||||
{
|
||||
// wait that list decelerate to display / hide it
|
||||
[self addReconnectingView];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Restarts the current connection if it is required.
|
||||
The 0.3s delay is added to avoid flickering if the connection does not require to be restarted.
|
||||
*/
|
||||
- (void)managePullToKick:(UIScrollView *)scrollView
|
||||
{
|
||||
// the current connection must be restarted
|
||||
if (restartConnection)
|
||||
{
|
||||
// display at least 0.3s the spinner to show to the user that something is pending
|
||||
// else the UI is flickering
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
self->restartConnection = NO;
|
||||
|
||||
if (![self canReconnect])
|
||||
{
|
||||
// if the event stream has not been restarted
|
||||
// hide the spinner
|
||||
[self removeReconnectingView];
|
||||
}
|
||||
// else wait that onSyncNotification is called.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MXKGroupListViewController">
|
||||
<connections>
|
||||
<outlet property="groupsSearchBar" destination="Zbr-9e-VZh" id="lBZ-hu-yJo"/>
|
||||
<outlet property="groupsSearchBarHeightConstraint" destination="jCU-cq-2OA" id="Nl4-Wn-L98"/>
|
||||
<outlet property="groupsSearchBarTopConstraint" destination="aRg-Nz-enq" id="mDZ-YB-gRX"/>
|
||||
<outlet property="groupsTableView" destination="orV-HH-88x" id="pZt-dT-XPy"/>
|
||||
<outlet property="groupsTableViewBottomConstraint" destination="m4x-32-odR" id="4hx-dQ-8aj"/>
|
||||
<outlet property="view" destination="iN0-l3-epB" id="NUQ-LI-M61"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<searchBar contentMode="redraw" showsCancelButton="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zbr-9e-VZh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="jCU-cq-2OA"/>
|
||||
</constraints>
|
||||
<textInputTraits key="textInputTraits" returnKeyType="done"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-1" id="ejZ-kN-jKv"/>
|
||||
</connections>
|
||||
</searchBar>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="70" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="30" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="orV-HH-88x">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="orV-HH-88x" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="NaR-eJ-WMj"/>
|
||||
<constraint firstItem="Zbr-9e-VZh" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="aRg-Nz-enq"/>
|
||||
<constraint firstItem="Zbr-9e-VZh" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="fty-XB-tTr"/>
|
||||
<constraint firstItem="orV-HH-88x" firstAttribute="top" secondItem="Zbr-9e-VZh" secondAttribute="bottom" id="lBa-P2-Vnx"/>
|
||||
<constraint firstAttribute="bottom" secondItem="orV-HH-88x" secondAttribute="bottom" id="m4x-32-odR"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Zbr-9e-VZh" secondAttribute="trailing" id="rKb-TZ-sap"/>
|
||||
<constraint firstAttribute="trailing" secondItem="orV-HH-88x" secondAttribute="trailing" id="yBp-63-kZi"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKGroupCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKGroupCellData` modelised the data for a `MXKGroupTableViewCell` cell.
|
||||
*/
|
||||
@interface MXKGroupCellData : MXKCellData <MXKGroupCellDataStoring>
|
||||
|
||||
@end
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKGroupCellData.h"
|
||||
|
||||
#import "MXKSessionGroupsDataSource.h"
|
||||
|
||||
@implementation MXKGroupCellData
|
||||
@synthesize group, groupsDataSource, groupDisplayname, sortingDisplayname;
|
||||
|
||||
- (instancetype)initWithGroup:(MXGroup*)theGroup andGroupsDataSource:(MXKSessionGroupsDataSource*)theGroupsDataSource
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
groupsDataSource = theGroupsDataSource;
|
||||
[self updateWithGroup:theGroup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateWithGroup:(MXGroup*)theGroup
|
||||
{
|
||||
group = theGroup;
|
||||
|
||||
groupDisplayname = sortingDisplayname = group.profile.name;
|
||||
|
||||
if (!groupDisplayname.length)
|
||||
{
|
||||
groupDisplayname = group.groupId;
|
||||
// Ignore the prefix '+' of the group id during sorting.
|
||||
sortingDisplayname = [groupDisplayname substringFromIndex:1];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
#import "MXKCellData.h"
|
||||
|
||||
@class MXKSessionGroupsDataSource;
|
||||
|
||||
/**
|
||||
`MXKGroupCellDataStoring` defines a protocol a class must conform in order to store group cell data
|
||||
managed by `MXKSessionGroupsDataSource`.
|
||||
*/
|
||||
@protocol MXKGroupCellDataStoring <NSObject>
|
||||
|
||||
@property (nonatomic, weak, readonly) MXKSessionGroupsDataSource *groupsDataSource;
|
||||
|
||||
@property (nonatomic, readonly) MXGroup *group;
|
||||
|
||||
@property (nonatomic, readonly) NSString *groupDisplayname;
|
||||
@property (nonatomic, readonly) NSString *sortingDisplayname;
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Create a new `MXKCellData` object for a new group cell.
|
||||
|
||||
@param group the `MXGroup` object that has data about the group.
|
||||
@param groupsDataSource the `MXKSessionGroupsDataSource` object that will use this instance.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithGroup:(MXGroup*)group andGroupsDataSource:(MXKSessionGroupsDataSource*)groupsDataSource;
|
||||
|
||||
/**
|
||||
The `MXKSessionGroupsDataSource` object calls this method when the group data has been updated.
|
||||
|
||||
@param group the updated group.
|
||||
*/
|
||||
- (void)updateWithGroup:(MXGroup*)group;
|
||||
|
||||
@end
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKDataSource.h"
|
||||
#import "MXKGroupCellData.h"
|
||||
|
||||
/**
|
||||
Identifier to use for cells that display a group.
|
||||
*/
|
||||
extern NSString *const kMXKGroupCellIdentifier;
|
||||
|
||||
/**
|
||||
'MXKSessionGroupsDataSource' is a base class to handle the groups of a matrix session.
|
||||
A 'MXKSessionGroupsDataSource' instance provides the data source for `MXKGroupListViewController`.
|
||||
|
||||
A section is created to handle the invitations to a group, the first one if any.
|
||||
*/
|
||||
@interface MXKSessionGroupsDataSource : MXKDataSource <UITableViewDataSource>
|
||||
{
|
||||
@protected
|
||||
|
||||
/**
|
||||
The current list of the group invitations (sorted in the alphabetic order).
|
||||
This list takes into account potential filter defined by`patternsList`.
|
||||
*/
|
||||
NSMutableArray<MXKGroupCellData*> *groupsInviteCellDataArray;
|
||||
|
||||
/**
|
||||
The current displayed list of the joined groups (sorted in the alphabetic order).
|
||||
This list takes into account potential filter defined by`patternsList`.
|
||||
*/
|
||||
NSMutableArray<MXKGroupCellData*> *groupsCellDataArray;
|
||||
}
|
||||
|
||||
@property (nonatomic) NSInteger groupInvitesSection;
|
||||
@property (nonatomic) NSInteger joinedGroupsSection;
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
/**
|
||||
Refresh all the groups summary.
|
||||
The group data are not synced with the server, use this method to refresh them according to your needs.
|
||||
|
||||
@param completion the block to execute when a request has been done for each group (whatever the result of the requests).
|
||||
You may specify nil for this parameter.
|
||||
*/
|
||||
- (void)refreshGroupsSummary:(void (^)(void))completion;
|
||||
|
||||
/**
|
||||
Filter the current groups list according to the provided patterns.
|
||||
When patterns are not empty, the search result is stored in `filteredGroupsCellDataArray`,
|
||||
this array provides then data for the cells served by `MXKSessionGroupsDataSource`.
|
||||
|
||||
@param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList;
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell in the table
|
||||
@return the cell data
|
||||
*/
|
||||
- (id<MXKGroupCellDataStoring>)cellDataAtIndex:(NSIndexPath*)indexPath;
|
||||
|
||||
/**
|
||||
Get the index path of the cell related to the provided groupId.
|
||||
|
||||
@param groupId the group identifier.
|
||||
@return indexPath the index of the cell (nil if not found).
|
||||
*/
|
||||
- (NSIndexPath*)cellIndexPathWithGroupId:(NSString*)groupId;
|
||||
|
||||
/**
|
||||
Leave the group displayed at the provided path.
|
||||
|
||||
@param indexPath the index of the group cell in the table
|
||||
*/
|
||||
- (void)leaveGroupAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
@@ -1,611 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKSessionGroupsDataSource.h"
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
#import "MXKConstants.h"
|
||||
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
#pragma mark - Constant definitions
|
||||
NSString *const kMXKGroupCellIdentifier = @"kMXKGroupCellIdentifier";
|
||||
|
||||
|
||||
@interface MXKSessionGroupsDataSource ()
|
||||
{
|
||||
/**
|
||||
Internal array used to regulate change notifications.
|
||||
Cell data changes are stored instantly in this array.
|
||||
We wait at least for 500 ms between two notifications of the delegate.
|
||||
*/
|
||||
NSMutableArray *internalCellDataArray;
|
||||
|
||||
/*
|
||||
Timer to not notify the delegate on every changes.
|
||||
*/
|
||||
NSTimer *timer;
|
||||
|
||||
/*
|
||||
Tells whether some changes must be notified.
|
||||
*/
|
||||
BOOL isDataChangePending;
|
||||
|
||||
/**
|
||||
Store the current search patterns list.
|
||||
*/
|
||||
NSArray* searchPatternsList;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKSessionGroupsDataSource
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
self = [super initWithMatrixSession:matrixSession];
|
||||
if (self)
|
||||
{
|
||||
internalCellDataArray = [NSMutableArray array];
|
||||
groupsCellDataArray = [NSMutableArray array];
|
||||
groupsInviteCellDataArray = [NSMutableArray array];
|
||||
|
||||
isDataChangePending = NO;
|
||||
|
||||
// Set default data and view classes
|
||||
[self registerCellDataClass:MXKGroupCellData.class forCellIdentifier:kMXKGroupCellIdentifier];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
groupsCellDataArray = nil;
|
||||
groupsInviteCellDataArray = nil;
|
||||
internalCellDataArray = nil;
|
||||
|
||||
searchPatternsList = nil;
|
||||
|
||||
[timer invalidate];
|
||||
timer = nil;
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
- (void)didMXSessionStateChange
|
||||
{
|
||||
if (MXSessionStateRunning <= self.mxSession.state)
|
||||
{
|
||||
// Check whether some data have been already load
|
||||
if (0 == internalCellDataArray.count)
|
||||
{
|
||||
[self loadData];
|
||||
}
|
||||
else if (self.mxSession.state == MXSessionStateRunning)
|
||||
{
|
||||
// Refresh the group data
|
||||
[self refreshGroupsSummary:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)refreshGroupsSummary:(void (^)(void))completion
|
||||
{
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] refreshGroupsSummary");
|
||||
|
||||
__block NSUInteger count = internalCellDataArray.count;
|
||||
|
||||
if (count)
|
||||
{
|
||||
for (id<MXKGroupCellDataStoring> groupData in internalCellDataArray)
|
||||
{
|
||||
// Force the matrix session to refresh the group summary.
|
||||
[self.mxSession updateGroupSummary:groupData.group success:^{
|
||||
|
||||
if (completion && !(--count))
|
||||
{
|
||||
// All the requests have been done.
|
||||
completion ();
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] refreshGroupsSummary: group summary update failed %@", groupData.group.groupId);
|
||||
|
||||
if (completion && !(--count))
|
||||
{
|
||||
// All the requests have been done.
|
||||
completion ();
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
else if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList
|
||||
{
|
||||
if (patternsList.count)
|
||||
{
|
||||
searchPatternsList = patternsList;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchPatternsList = nil;
|
||||
}
|
||||
|
||||
[self onCellDataChange];
|
||||
}
|
||||
|
||||
- (id<MXKGroupCellDataStoring>)cellDataAtIndex:(NSIndexPath*)indexPath
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData;
|
||||
|
||||
if (indexPath.section == _groupInvitesSection)
|
||||
{
|
||||
if (indexPath.row < groupsInviteCellDataArray.count)
|
||||
{
|
||||
groupData = groupsInviteCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
else if (indexPath.section == _joinedGroupsSection)
|
||||
{
|
||||
if (indexPath.row < groupsCellDataArray.count)
|
||||
{
|
||||
groupData = groupsCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
return groupData;
|
||||
}
|
||||
|
||||
- (NSIndexPath*)cellIndexPathWithGroupId:(NSString*)groupId
|
||||
{
|
||||
// Look for the cell
|
||||
if (_groupInvitesSection != -1)
|
||||
{
|
||||
for (NSInteger index = 0; index < groupsInviteCellDataArray.count; index ++)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = groupsInviteCellDataArray[index];
|
||||
if ([groupId isEqualToString:groupData.group.groupId])
|
||||
{
|
||||
// Got it
|
||||
return [NSIndexPath indexPathForRow:index inSection:_groupInvitesSection];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_joinedGroupsSection != -1)
|
||||
{
|
||||
for (NSInteger index = 0; index < groupsCellDataArray.count; index ++)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = groupsCellDataArray[index];
|
||||
if ([groupId isEqualToString:groupData.group.groupId])
|
||||
{
|
||||
// Got it
|
||||
return [NSIndexPath indexPathForRow:index inSection:_joinedGroupsSection];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Groups processing
|
||||
|
||||
- (void)loadData
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewGroupInviteNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidJoinGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupSummaryNotification object:self.mxSession];
|
||||
|
||||
// Reset the table
|
||||
[internalCellDataArray removeAllObjects];
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier];
|
||||
NSAssert([class conformsToProtocol:@protocol(MXKGroupCellDataStoring)], @"MXKSessionGroupsDataSource only manages MXKCellData that conforms to MXKGroupCellDataStoring protocol");
|
||||
|
||||
// Listen to MXSession groups changes
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNewGroupInvite:) name:kMXSessionNewGroupInviteNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didJoinGroup:) name:kMXSessionDidJoinGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didLeaveGroup:) name:kMXSessionDidLeaveGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroup:) name:kMXSessionDidUpdateGroupSummaryNotification object:self.mxSession];
|
||||
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
NSArray *groups = self.mxSession.groups;
|
||||
for (MXGroup *group in groups)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
|
||||
// Force the matrix session to refresh the group summary.
|
||||
[self.mxSession updateGroupSummary:group success:nil failure:^(NSError *error) {
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] loadData: group summary update failed %@", group.groupId);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] Loaded %tu groups in %.3fms", groups.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
|
||||
- (void)didUpdateGroup:(NSNotification *)notif
|
||||
{
|
||||
MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey];
|
||||
if (group)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:group.groupId];
|
||||
if (groupData)
|
||||
{
|
||||
[groupData updateWithGroup:group];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] didUpdateGroup: Cannot find the changed group for %@ (%@). It is probably not managed by this group data source", group.groupId, group);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
|
||||
- (void)onNewGroupInvite:(NSNotification *)notif
|
||||
{
|
||||
MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey];
|
||||
if (group)
|
||||
{
|
||||
// Add the group if there is not yet a cell for it
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:group.groupId];
|
||||
if (nil == groupData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Add new group invite: %@", group.groupId);
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier];
|
||||
|
||||
id<MXKGroupCellDataStoring> cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didJoinGroup:(NSNotification *)notif
|
||||
{
|
||||
MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey];
|
||||
if (group)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:group.groupId];
|
||||
if (groupData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Update joined room: %@", group.groupId);
|
||||
[groupData updateWithGroup:group];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Add new joined invite: %@", group.groupId);
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier];
|
||||
|
||||
id<MXKGroupCellDataStoring> cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didLeaveGroup:(NSNotification *)notif
|
||||
{
|
||||
NSString *groupId = notif.userInfo[kMXSessionNotificationGroupIdKey];
|
||||
if (groupId)
|
||||
{
|
||||
[self removeGroup:groupId];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeGroup:(NSString*)groupId
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:groupId];
|
||||
if (groupData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Remove left group: %@", groupId);
|
||||
|
||||
[internalCellDataArray removeObject:groupData];
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onCellDataChange
|
||||
{
|
||||
isDataChangePending = NO;
|
||||
|
||||
// Check no notification was done recently.
|
||||
// Note: do not wait in case of search
|
||||
if (timer == nil || searchPatternsList)
|
||||
{
|
||||
[timer invalidate];
|
||||
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(checkPendingUpdate:) userInfo:nil repeats:NO];
|
||||
|
||||
// Prepare cell data array, and notify the delegate.
|
||||
[self prepareCellDataAndNotifyChanges];
|
||||
}
|
||||
else
|
||||
{
|
||||
isDataChangePending = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)checkPendingUpdate:(id)sender
|
||||
{
|
||||
[timer invalidate];
|
||||
timer = nil;
|
||||
|
||||
if (isDataChangePending)
|
||||
{
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sortCellData
|
||||
{
|
||||
// Order alphabetically the groups
|
||||
[internalCellDataArray sortUsingComparator:^NSComparisonResult(id<MXKGroupCellDataStoring> cellData1, id<MXKGroupCellDataStoring> cellData2)
|
||||
{
|
||||
if (cellData1.sortingDisplayname.length && cellData2.sortingDisplayname.length)
|
||||
{
|
||||
return [cellData1.sortingDisplayname compare:cellData2.sortingDisplayname options:NSCaseInsensitiveSearch];
|
||||
}
|
||||
else if (cellData1.sortingDisplayname.length)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
else if (cellData2.sortingDisplayname.length)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
return NSOrderedSame;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)prepareCellDataAndNotifyChanges
|
||||
{
|
||||
// Prepare the cell data arrays by considering the potential filter.
|
||||
[groupsInviteCellDataArray removeAllObjects];
|
||||
[groupsCellDataArray removeAllObjects];
|
||||
for (id<MXKGroupCellDataStoring> groupData in internalCellDataArray)
|
||||
{
|
||||
BOOL isKept = !searchPatternsList;
|
||||
|
||||
for (NSString* pattern in searchPatternsList)
|
||||
{
|
||||
if (groupData.groupDisplayname && [groupData.groupDisplayname rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
{
|
||||
isKept = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isKept)
|
||||
{
|
||||
if (groupData.group.membership == MXMembershipInvite)
|
||||
{
|
||||
[groupsInviteCellDataArray addObject:groupData];
|
||||
}
|
||||
else
|
||||
{
|
||||
[groupsCellDataArray addObject:groupData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update here data source state
|
||||
if (state != MXKDataSourceStateReady)
|
||||
{
|
||||
state = MXKDataSourceStateReady;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:state];
|
||||
}
|
||||
}
|
||||
|
||||
// And inform the delegate about the update
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
|
||||
// Find the cell data that stores information about the given group id
|
||||
- (id<MXKGroupCellDataStoring>)cellDataWithGroupId:(NSString*)groupId
|
||||
{
|
||||
id<MXKGroupCellDataStoring> theGroupData;
|
||||
for (id<MXKGroupCellDataStoring> groupData in internalCellDataArray)
|
||||
{
|
||||
if ([groupData.group.groupId isEqualToString:groupId])
|
||||
{
|
||||
theGroupData = groupData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return theGroupData;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
NSInteger count = 0;
|
||||
_groupInvitesSection = _joinedGroupsSection = -1;
|
||||
|
||||
// Check whether all data sources are ready before rendering groups.
|
||||
if (self.state == MXKDataSourceStateReady)
|
||||
{
|
||||
if (groupsInviteCellDataArray.count)
|
||||
{
|
||||
_groupInvitesSection = count++;
|
||||
}
|
||||
if (groupsCellDataArray.count)
|
||||
{
|
||||
_joinedGroupsSection = count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (section == _groupInvitesSection)
|
||||
{
|
||||
return groupsInviteCellDataArray.count;
|
||||
}
|
||||
else if (section == _joinedGroupsSection)
|
||||
{
|
||||
return groupsCellDataArray.count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
NSString* sectionTitle = nil;
|
||||
|
||||
if (section == _groupInvitesSection)
|
||||
{
|
||||
sectionTitle = [VectorL10n groupInviteSection];
|
||||
}
|
||||
else if (section == _joinedGroupsSection)
|
||||
{
|
||||
sectionTitle = [VectorL10n groupSection];
|
||||
}
|
||||
|
||||
return sectionTitle;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData;
|
||||
|
||||
if (indexPath.section == _groupInvitesSection)
|
||||
{
|
||||
if (indexPath.row < groupsInviteCellDataArray.count)
|
||||
{
|
||||
groupData = groupsInviteCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
else if (indexPath.section == _joinedGroupsSection)
|
||||
{
|
||||
if (indexPath.row < groupsCellDataArray.count)
|
||||
{
|
||||
groupData = groupsCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
if (groupData)
|
||||
{
|
||||
NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:groupData];
|
||||
if (cellIdentifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
|
||||
|
||||
// Make sure we listen to user actions on the cell
|
||||
cell.delegate = self;
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:groupData];
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Return NO if you do not want the specified item to be editable.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete)
|
||||
{
|
||||
[self leaveGroupAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)leaveGroupAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKGroupCellDataStoring> cellData = [self cellDataAtIndex:indexPath];
|
||||
|
||||
if (cellData.group)
|
||||
{
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
[self.mxSession leaveGroup:cellData.group.groupId success:^{
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
// Refresh the table content
|
||||
typeof(self) self = weakSelf;
|
||||
[self removeGroup:cellData.group.groupId];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] Failed to leave group (%@)", cellData.group.groupId);
|
||||
|
||||
// Notify MatrixKit user
|
||||
NSString *myUserId = self.mxSession.myUser.userId;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKTableViewCell.h"
|
||||
|
||||
#import "MXKCellRendering.h"
|
||||
|
||||
#import "MXKGroupCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKGroupTableViewCell` instances display a group.
|
||||
*/
|
||||
@interface MXKGroupTableViewCell : MXKTableViewCell <MXKCellRendering>
|
||||
{
|
||||
@protected
|
||||
/**
|
||||
The current cell data displayed by the table view cell
|
||||
*/
|
||||
id<MXKGroupCellDataStoring> groupCellData;
|
||||
}
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UILabel *groupName;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *groupDescription;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *memberCount;
|
||||
|
||||
@end
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKGroupTableViewCell.h"
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@implementation MXKGroupTableViewCell
|
||||
@synthesize delegate;
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
- (void)render:(MXKCellData *)cellData
|
||||
{
|
||||
groupCellData = (id<MXKGroupCellDataStoring>)cellData;
|
||||
if (groupCellData)
|
||||
{
|
||||
// Render the current group values.
|
||||
_groupName.text = groupCellData.groupDisplayname;
|
||||
_groupDescription.text = groupCellData.group.profile.shortDescription;
|
||||
|
||||
if (_groupDescription.text.length)
|
||||
{
|
||||
_groupDescription.hidden = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide and fill the label with a fake description to harmonize the height of all the cells.
|
||||
// This is a drawback of the self-sizing cell.
|
||||
_groupDescription.hidden = YES;
|
||||
_groupDescription.text = @"No description";
|
||||
}
|
||||
|
||||
if (_memberCount)
|
||||
{
|
||||
if (groupCellData.group.summary.usersSection.totalUserCountEstimate > 1)
|
||||
{
|
||||
_memberCount.text = [VectorL10n numMembersOther:@(groupCellData.group.summary.usersSection.totalUserCountEstimate).stringValue];
|
||||
}
|
||||
else if (groupCellData.group.summary.usersSection.totalUserCountEstimate == 1)
|
||||
{
|
||||
_memberCount.text = [VectorL10n numMembersOne:@(1).stringValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
_memberCount.text = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_groupName.text = nil;
|
||||
_groupDescription.text = nil;
|
||||
_memberCount.text = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (MXKCellData*)renderedCellData
|
||||
{
|
||||
return groupCellData;
|
||||
}
|
||||
|
||||
+ (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth
|
||||
{
|
||||
// The height is fixed
|
||||
//@TODO: change this to handle dynamic font
|
||||
return 70;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
groupCellData = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="L2L-l5-wPx" customClass="MXKGroupTableViewCell">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="70"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="L2L-l5-wPx" id="aXz-IR-jj5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="69.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="RoomTitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Lg1-xQ-AGn">
|
||||
<rect key="frame" x="8" y="8" width="506" height="23"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="19"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LastEventDescription" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dQt-mN-T6b">
|
||||
<rect key="frame" x="8" y="42" width="584" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="xx users" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="360-Go-RcG">
|
||||
<rect key="frame" x="520" y="8" width="72" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="72" id="uOj-6w-G8q"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="8" id="3qc-BC-FYt"/>
|
||||
<constraint firstItem="360-Go-RcG" firstAttribute="leading" secondItem="Lg1-xQ-AGn" secondAttribute="trailing" constant="6" id="8ff-tS-Ulq"/>
|
||||
<constraint firstItem="dQt-mN-T6b" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="8" id="UYQ-HG-YP1"/>
|
||||
<constraint firstItem="360-Go-RcG" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="8" id="XyO-tl-6SX"/>
|
||||
<constraint firstAttribute="trailing" secondItem="360-Go-RcG" secondAttribute="trailing" constant="8" id="YqC-WC-Wqe"/>
|
||||
<constraint firstAttribute="bottom" secondItem="dQt-mN-T6b" secondAttribute="bottom" constant="9" id="sLb-Q4-AFW"/>
|
||||
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="8" id="uCT-KE-X09"/>
|
||||
<constraint firstAttribute="trailing" secondItem="dQt-mN-T6b" secondAttribute="trailing" constant="8" id="ygG-1B-fu3"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<outlet property="groupDescription" destination="dQt-mN-T6b" id="meF-te-1lf"/>
|
||||
<outlet property="groupName" destination="Lg1-xQ-AGn" id="ABQ-44-bUs"/>
|
||||
<outlet property="memberCount" destination="360-Go-RcG" id="oYv-dg-CoR"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
Reference in New Issue
Block a user