mirror of https://github.com/oxen-io/session-ios
Merge branch 'charlesmchen/bundleDocumentTypes'
commit
8c1c38b305
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "file-thin-black-w-shadow-large.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
@class TSThread;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol SelectThreadViewControllerDelegate <NSObject>
|
||||
|
||||
- (void)threadWasSelected:(TSThread *)thread;
|
||||
|
||||
- (BOOL)canSelectBlockedContact;
|
||||
|
||||
- (nullable UIView *)createHeaderWithSearchBar:(UISearchBar *)searchBar;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface SelectThreadViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) id<SelectThreadViewControllerDelegate> delegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,416 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SelectThreadViewController.h"
|
||||
#import "BlockListUIUtils.h"
|
||||
#import "ContactTableViewCell.h"
|
||||
#import "Environment.h"
|
||||
#import "InboxTableViewCell.h"
|
||||
#import "OWSContactsManager.h"
|
||||
#import "OWSContactsSearcher.h"
|
||||
#import "OWSTableViewController.h"
|
||||
#import "ThreadViewHelper.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import "UIFont+OWS.h"
|
||||
#import "UIView+OWS.h"
|
||||
#import <SignalServiceKit/OWSBlockingManager.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/TSContactThread.h>
|
||||
#import <SignalServiceKit/TSThread.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SelectThreadViewController () <OWSTableViewControllerDelegate, ThreadViewHelperDelegate, UISearchBarDelegate>
|
||||
|
||||
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
|
||||
@property (nonatomic) NSSet<NSString *> *blockedPhoneNumberSet;
|
||||
|
||||
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
|
||||
@property (nonatomic) NSArray<Contact *> *contacts;
|
||||
|
||||
@property (nonatomic, readonly) ThreadViewHelper *threadViewHelper;
|
||||
|
||||
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
|
||||
@property (nonatomic, readonly) UISearchBar *searchBar;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation SelectThreadViewController
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
self.navigationItem.leftBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
|
||||
target:self
|
||||
action:@selector(dismissPressed:)];
|
||||
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
_blockingManager = [OWSBlockingManager sharedManager];
|
||||
_blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]];
|
||||
_contactsManager = [Environment getCurrent].contactsManager;
|
||||
self.contacts = [self filteredContacts];
|
||||
_threadViewHelper = [ThreadViewHelper new];
|
||||
_threadViewHelper.delegate = self;
|
||||
|
||||
[self createViews];
|
||||
|
||||
[self addNotificationListeners];
|
||||
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
[self.navigationController.navigationBar setTranslucent:NO];
|
||||
}
|
||||
|
||||
- (void)addNotificationListeners
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(blockedPhoneNumbersDidChange:)
|
||||
name:kNSNotificationName_BlockedPhoneNumbersDidChange
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(signalRecipientsDidChange:)
|
||||
name:OWSContactsManagerSignalRecipientsDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)createViews
|
||||
{
|
||||
OWSAssert(self.delegate);
|
||||
|
||||
// Search
|
||||
UISearchBar *searchBar = [UISearchBar new];
|
||||
_searchBar = searchBar;
|
||||
searchBar.searchBarStyle = UISearchBarStyleMinimal;
|
||||
searchBar.delegate = self;
|
||||
searchBar.placeholder = NSLocalizedString(@"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT", @"");
|
||||
searchBar.backgroundColor = [UIColor whiteColor];
|
||||
[searchBar sizeToFit];
|
||||
|
||||
UIView *header = [self.delegate createHeaderWithSearchBar:searchBar];
|
||||
|
||||
// Table
|
||||
_tableViewController = [OWSTableViewController new];
|
||||
_tableViewController.delegate = self;
|
||||
_tableViewController.contents = [OWSTableContents new];
|
||||
[self.view addSubview:self.tableViewController.view];
|
||||
[_tableViewController.view autoPinWidthToSuperview];
|
||||
[_tableViewController.view autoPinToTopLayoutGuideOfViewController:self withInset:0];
|
||||
if (header) {
|
||||
_tableViewController.tableView.tableHeaderView = header;
|
||||
} else {
|
||||
_tableViewController.tableView.tableHeaderView = searchBar;
|
||||
}
|
||||
[_tableViewController.view autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
}
|
||||
|
||||
#pragma mark - UISearchBarDelegate
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar
|
||||
{
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
|
||||
{
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)updateTableContents
|
||||
{
|
||||
__weak SelectThreadViewController *weakSelf = self;
|
||||
OWSTableContents *contents = [OWSTableContents new];
|
||||
OWSTableSection *section = [OWSTableSection new];
|
||||
|
||||
// Threads
|
||||
for (TSThread *thread in [self filteredThreadsWithSearchText]) {
|
||||
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||
SelectThreadViewController *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return (ContactTableViewCell *)nil;
|
||||
}
|
||||
|
||||
// To be consistent with the threads (above), we use ContactTableViewCell
|
||||
// instead of InboxTableViewCell to present contacts and threads.
|
||||
ContactTableViewCell *cell = [ContactTableViewCell new];
|
||||
[cell configureWithThread:thread contactsManager:strongSelf.contactsManager];
|
||||
return cell;
|
||||
}
|
||||
customRowHeight:[ContactTableViewCell rowHeight]
|
||||
actionBlock:^{
|
||||
[weakSelf.delegate threadWasSelected:thread];
|
||||
}]];
|
||||
}
|
||||
|
||||
// Contacts
|
||||
NSArray<Contact *> *filteredContacts = [self filteredContactsWithSearchText];
|
||||
for (Contact *contact in filteredContacts) {
|
||||
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||
SelectThreadViewController *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return (ContactTableViewCell *)nil;
|
||||
}
|
||||
|
||||
ContactTableViewCell *cell = [ContactTableViewCell new];
|
||||
BOOL isBlocked = [strongSelf isContactBlocked:contact];
|
||||
if (isBlocked) {
|
||||
cell.accessoryMessage
|
||||
= NSLocalizedString(@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
|
||||
} else {
|
||||
OWSAssert(cell.accessoryMessage == nil);
|
||||
}
|
||||
[cell configureWithContact:contact contactsManager:strongSelf.contactsManager];
|
||||
return cell;
|
||||
}
|
||||
customRowHeight:[ContactTableViewCell rowHeight]
|
||||
actionBlock:^{
|
||||
[weakSelf contactWasSelected:contact];
|
||||
}]];
|
||||
}
|
||||
|
||||
if (section.itemCount < 1) {
|
||||
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
|
||||
UITableViewCell *cell = [UITableViewCell new];
|
||||
cell.textLabel.text = NSLocalizedString(
|
||||
@"SETTINGS_BLOCK_LIST_NO_CONTACTS", @"A label that indicates the user has no Signal contacts.");
|
||||
cell.textLabel.font = [UIFont ows_regularFontWithSize:15.f];
|
||||
cell.textLabel.textColor = [UIColor colorWithWhite:0.5f alpha:1.f];
|
||||
cell.textLabel.textAlignment = NSTextAlignmentCenter;
|
||||
return cell;
|
||||
}
|
||||
actionBlock:nil]];
|
||||
}
|
||||
[contents addSection:section];
|
||||
|
||||
self.tableViewController.contents = contents;
|
||||
}
|
||||
|
||||
- (void)contactWasSelected:(Contact *)contact
|
||||
{
|
||||
OWSAssert(contact);
|
||||
OWSAssert(self.delegate);
|
||||
|
||||
// TODO: Use ContactAccount.
|
||||
NSString *recipientId = contact.textSecureIdentifiers.firstObject;
|
||||
|
||||
if ([self isRecipientIdBlocked:recipientId] &&
|
||||
![self.delegate canSelectBlockedContact]) {
|
||||
|
||||
__weak SelectThreadViewController *weakSelf = self;
|
||||
[BlockListUIUtils showUnblockContactActionSheet:contact
|
||||
fromViewController:self
|
||||
blockingManager:self.blockingManager
|
||||
contactsManager:self.contactsManager
|
||||
completionBlock:^(BOOL isBlocked) {
|
||||
if (!isBlocked) {
|
||||
[weakSelf contactWasSelected:contact];
|
||||
}
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
__block TSThread *thread = nil;
|
||||
[[TSStorageManager sharedManager].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction];
|
||||
}];
|
||||
OWSAssert(thread);
|
||||
|
||||
[self.delegate threadWasSelected:thread];
|
||||
}
|
||||
|
||||
#pragma mark - Filter
|
||||
|
||||
- (NSArray<TSThread *> *)filteredThreadsWithSearchText
|
||||
{
|
||||
NSArray<TSThread *> *threads = self.threadViewHelper.threads;
|
||||
|
||||
NSString *searchTerm =
|
||||
[[self.searchBar text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
||||
if ([searchTerm isEqualToString:@""]) {
|
||||
return threads;
|
||||
}
|
||||
|
||||
NSString *formattedNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
|
||||
|
||||
NSMutableArray *result = [NSMutableArray new];
|
||||
for (TSThread *thread in threads) {
|
||||
if ([thread.name containsString:searchTerm]) {
|
||||
[result addObject:thread];
|
||||
} else if ([thread isKindOfClass:[TSContactThread class]]) {
|
||||
TSContactThread *contactThread = (TSContactThread *)thread;
|
||||
if (formattedNumber.length > 0 && [contactThread.contactIdentifier containsString:formattedNumber]) {
|
||||
[result addObject:thread];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Move this to contacts view helper.
|
||||
- (NSArray<Contact *> *)filteredContactsWithSearchText
|
||||
{
|
||||
// We don't want to show a 1:1 thread with Alice and Alice's contact,
|
||||
// so we de-duplicate by recipientId.
|
||||
NSArray<TSThread *> *threads = self.threadViewHelper.threads;
|
||||
NSMutableSet *contactIdsToIgnore = [NSMutableSet new];
|
||||
for (TSThread *thread in threads) {
|
||||
if ([thread isKindOfClass:[TSContactThread class]]) {
|
||||
TSContactThread *contactThread = (TSContactThread *)thread;
|
||||
[contactIdsToIgnore addObject:contactThread.contactIdentifier];
|
||||
}
|
||||
}
|
||||
|
||||
NSString *searchString = [self.searchBar text];
|
||||
|
||||
NSArray *nonRedundantContacts =
|
||||
[self.contacts filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Contact *contact,
|
||||
NSDictionary<NSString *, id> *_Nullable bindings) {
|
||||
return ![contactIdsToIgnore containsObject:contact.textSecureIdentifiers.firstObject];
|
||||
}]];
|
||||
|
||||
// TODO: Move this to contacts view helper.
|
||||
OWSContactsSearcher *contactsSearcher = [[OWSContactsSearcher alloc] initWithContacts:nonRedundantContacts];
|
||||
NSArray<Contact *> *filteredContacts = [contactsSearcher filterWithString:searchString];
|
||||
|
||||
return filteredContacts;
|
||||
}
|
||||
|
||||
#pragma mark - Contacts and Blocking
|
||||
|
||||
- (void)blockedPhoneNumbersDidChange:(id)notification
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_blockedPhoneNumberSet = [NSSet setWithArray:[_blockingManager blockedPhoneNumbers]];
|
||||
|
||||
[self updateContacts];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)signalRecipientsDidChange:(NSNotification *)notification
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updateContacts];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateContacts
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
self.contacts = [self filteredContacts];
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
- (BOOL)isContactBlocked:(Contact *)contact
|
||||
{
|
||||
if (contact.parsedPhoneNumbers.count < 1) {
|
||||
// Hide contacts without any valid phone numbers.
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
|
||||
if ([_blockedPhoneNumberSet containsObject:phoneNumber.toE164]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isRecipientIdBlocked:(NSString *)recipientId
|
||||
{
|
||||
OWSAssert(recipientId.length > 0);
|
||||
|
||||
return [_blockedPhoneNumberSet containsObject:recipientId];
|
||||
}
|
||||
|
||||
- (BOOL)isContactHidden:(Contact *)contact
|
||||
{
|
||||
if (contact.parsedPhoneNumbers.count < 1) {
|
||||
// Hide contacts without any valid phone numbers.
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSArray<Contact *> *_Nonnull)filteredContacts
|
||||
{
|
||||
NSMutableArray<Contact *> *result = [NSMutableArray new];
|
||||
for (Contact *contact in self.contactsManager.signalContacts) {
|
||||
if (![self isContactHidden:contact]) {
|
||||
[result addObject:contact];
|
||||
}
|
||||
}
|
||||
return [result copy];
|
||||
}
|
||||
|
||||
#pragma mark - Events
|
||||
|
||||
- (void)dismissPressed:(id)sender
|
||||
{
|
||||
[self.searchBar resignFirstResponder];
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - OWSTableViewControllerDelegate
|
||||
|
||||
- (void)tableViewDidScroll
|
||||
{
|
||||
[self.searchBar resignFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - ThreadViewHelperDelegate
|
||||
|
||||
- (void)threadListDidChange
|
||||
{
|
||||
[self updateTableContents];
|
||||
}
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
+ (NSString *)tag
|
||||
{
|
||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||
}
|
||||
|
||||
- (NSString *)tag
|
||||
{
|
||||
return self.class.tag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,17 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SelectThreadViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SignalAttachment;
|
||||
|
||||
@interface SendExternalFileViewController : SelectThreadViewController
|
||||
|
||||
@property (nonatomic) SignalAttachment *attachment;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,158 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SendExternalFileViewController.h"
|
||||
#import "Environment.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import "ThreadUtil.h"
|
||||
#import "UIColor+OWS.h"
|
||||
#import "UIFont+OWS.h"
|
||||
#import <SignalServiceKit/OWSMessageSender.h>
|
||||
#import <SignalServiceKit/TSThread.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SendExternalFileViewController () <SelectThreadViewControllerDelegate>
|
||||
|
||||
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation SendExternalFileViewController
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
self.delegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
_messageSender = [Environment getCurrent].messageSender;
|
||||
|
||||
self.title = NSLocalizedString(@"SEND_EXTERNAL_FILE_VIEW_TITLE", @"Title for the 'send external file' view.");
|
||||
}
|
||||
|
||||
#pragma mark - SelectThreadViewControllerDelegate
|
||||
|
||||
- (void)threadWasSelected:(TSThread *)thread
|
||||
{
|
||||
OWSAssert(self.attachment);
|
||||
OWSAssert(thread);
|
||||
|
||||
[ThreadUtil sendMessageWithAttachment:self.attachment inThread:thread messageSender:self.messageSender];
|
||||
|
||||
[Environment messageThreadId:thread.uniqueId];
|
||||
}
|
||||
|
||||
- (BOOL)canSelectBlockedContact
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (nullable UIView *)createHeaderWithSearchBar:(UISearchBar *)searchBar
|
||||
{
|
||||
OWSAssert(searchBar)
|
||||
|
||||
const CGFloat imageSize
|
||||
= ScaleFromIPhone5To7Plus(40, 50);
|
||||
const CGFloat imageLabelSpacing = ScaleFromIPhone5To7Plus(5, 8);
|
||||
const CGFloat titleVSpacing = ScaleFromIPhone5To7Plus(10, 15);
|
||||
const CGFloat contentVMargin = 20;
|
||||
|
||||
UIView *header = [UIView new];
|
||||
header.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
UIView *titleLabel = [self createTitleLabel];
|
||||
[titleLabel sizeToFit];
|
||||
[header addSubview:titleLabel];
|
||||
[titleLabel autoHCenterInSuperview];
|
||||
[titleLabel autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:contentVMargin];
|
||||
|
||||
UIView *fileView = [UIView new];
|
||||
[header addSubview:fileView];
|
||||
[fileView autoHCenterInSuperview];
|
||||
[fileView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:titleLabel withOffset:titleVSpacing];
|
||||
|
||||
UIImage *image = [UIImage imageNamed:@"file-thin-black-filled-large"];
|
||||
OWSAssert(image);
|
||||
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
|
||||
imageView.layer.minificationFilter = kCAFilterTrilinear;
|
||||
imageView.layer.magnificationFilter = kCAFilterTrilinear;
|
||||
imageView.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
imageView.layer.shadowRadius = 2.f;
|
||||
imageView.layer.shadowOpacity = 0.2f;
|
||||
imageView.layer.shadowOffset = CGSizeMake(0.75f, 0.75f);
|
||||
[fileView addSubview:imageView];
|
||||
[imageView autoSetDimension:ALDimensionWidth toSize:imageSize];
|
||||
[imageView autoSetDimension:ALDimensionHeight toSize:imageSize];
|
||||
[imageView autoPinEdgeToSuperviewEdge:ALEdgeLeft];
|
||||
[imageView autoPinEdgeToSuperviewEdge:ALEdgeTop];
|
||||
[imageView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
|
||||
UIView *fileNameLabel = [self createFileNameLabel];
|
||||
[fileView addSubview:fileNameLabel];
|
||||
[fileNameLabel autoAlignAxis:ALAxisHorizontal toSameAxisOfView:imageView];
|
||||
[fileNameLabel autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:imageView withOffset:imageLabelSpacing];
|
||||
[fileNameLabel autoPinEdgeToSuperviewEdge:ALEdgeRight];
|
||||
|
||||
[header addSubview:searchBar];
|
||||
[searchBar autoPinWidthToSuperview];
|
||||
[searchBar autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:fileView withOffset:contentVMargin];
|
||||
[searchBar autoPinEdgeToSuperviewEdge:ALEdgeBottom];
|
||||
|
||||
// UITableViewController.tableHeaderView must have its height set.
|
||||
header.frame = CGRectMake(0,
|
||||
0,
|
||||
0,
|
||||
(contentVMargin * 2 + titleLabel.frame.size.height + titleVSpacing + imageSize + searchBar.frame.size.height));
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
- (NSString *)formattedFileName
|
||||
{
|
||||
OWSAssert(self.attachment) NSString *filename =
|
||||
[self.attachment.filename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
OWSAssert(filename.length > 0);
|
||||
const NSUInteger kMaxFilenameLength = 50;
|
||||
if (filename.length > kMaxFilenameLength) {
|
||||
// Truncate the filename if necessary.
|
||||
//
|
||||
// TODO: Use l10n-safe truncation.
|
||||
filename = [[[filename substringToIndex:kMaxFilenameLength / 2] stringByAppendingString:@"…"]
|
||||
stringByAppendingString:[filename substringFromIndex:filename.length - kMaxFilenameLength / 2]];
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
- (UIView *)createFileNameLabel
|
||||
{
|
||||
UILabel *label = [UILabel new];
|
||||
label.text = [self formattedFileName];
|
||||
label.textColor = [UIColor ows_materialBlueColor];
|
||||
label.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(16.f, 20.f)];
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
- (UIView *)createTitleLabel
|
||||
{
|
||||
UILabel *label = [UILabel new];
|
||||
label.text
|
||||
= NSLocalizedString(@"SEND_EXTERNAL_FILE_HEADER_TITLE", @"Header title for the 'send external file' view.");
|
||||
label.textColor = [UIColor blackColor];
|
||||
label.font = [UIFont ows_mediumFontWithSize:ScaleFromIPhone5To7Plus(18.f, 20.f)];
|
||||
return label;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ThreadViewHelperDelegate <NSObject>
|
||||
|
||||
- (void)threadListDidChange;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@class TSThread;
|
||||
|
||||
// A helper class for views that want to present the list of threads
|
||||
// that show up in home view, and in the same order.
|
||||
//
|
||||
// It observes changes to the threads & their ordering and informs
|
||||
// its delegate when they happen.
|
||||
@interface ThreadViewHelper : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<ThreadViewHelperDelegate> delegate;
|
||||
|
||||
@property (nonatomic, readonly) NSMutableArray<TSThread *> *threads;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,140 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ThreadViewHelper.h"
|
||||
#import <SignalServiceKit/TSDatabaseView.h>
|
||||
#import <SignalServiceKit/TSStorageManager.h>
|
||||
#import <SignalServiceKit/TSThread.h>
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
#import <YapDatabase/YapDatabaseViewChange.h>
|
||||
#import <YapDatabase/YapDatabaseViewConnection.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ThreadViewHelper ()
|
||||
|
||||
@property (nonatomic) YapDatabaseConnection *uiDatabaseConnection;
|
||||
@property (nonatomic) YapDatabaseViewMappings *threadMappings;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ThreadViewHelper
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(yapDatabaseModified:)
|
||||
name:TSUIDatabaseConnectionDidUpdateNotification
|
||||
object:nil];
|
||||
[self initializeMapping];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)initializeMapping
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
NSString *grouping = TSInboxGroup;
|
||||
|
||||
self.threadMappings =
|
||||
[[YapDatabaseViewMappings alloc] initWithGroups:@[ grouping ] view:TSThreadDatabaseViewExtensionName];
|
||||
[self.threadMappings setIsReversed:YES forGroup:grouping];
|
||||
|
||||
__weak ThreadViewHelper *weakSelf = self;
|
||||
[self.uiDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
[self.threadMappings updateWithTransaction:transaction];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateThreads];
|
||||
[weakSelf.delegate threadListDidChange];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Database
|
||||
|
||||
- (YapDatabaseConnection *)uiDatabaseConnection
|
||||
{
|
||||
NSAssert([NSThread isMainThread], @"Must access uiDatabaseConnection on main thread!");
|
||||
if (!_uiDatabaseConnection) {
|
||||
YapDatabase *database = TSStorageManager.sharedManager.database;
|
||||
_uiDatabaseConnection = [database newConnection];
|
||||
[_uiDatabaseConnection beginLongLivedReadTransaction];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(yapDatabaseModified:)
|
||||
name:YapDatabaseModifiedNotification
|
||||
object:database];
|
||||
}
|
||||
return _uiDatabaseConnection;
|
||||
}
|
||||
|
||||
- (void)yapDatabaseModified:(NSNotification *)notification
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
|
||||
NSArray *sectionChanges = nil;
|
||||
NSArray *rowChanges = nil;
|
||||
[[self.uiDatabaseConnection ext:TSThreadDatabaseViewExtensionName] getSectionChanges:§ionChanges
|
||||
rowChanges:&rowChanges
|
||||
forNotifications:notifications
|
||||
withMappings:self.threadMappings];
|
||||
if (sectionChanges.count == 0 && rowChanges.count == 0) {
|
||||
// Ignore irrelevant modifications.
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateThreads];
|
||||
|
||||
[self.delegate threadListDidChange];
|
||||
}
|
||||
|
||||
- (void)updateThreads
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
NSMutableArray<TSThread *> *threads = [NSMutableArray new];
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
NSUInteger numberOfSections = [self.threadMappings numberOfSections];
|
||||
OWSAssert(numberOfSections == 1);
|
||||
for (NSUInteger section = 0; section < numberOfSections; section++) {
|
||||
NSUInteger numberOfItems = [self.threadMappings numberOfItemsInSection:section];
|
||||
for (NSUInteger item = 0; item < numberOfItems; item++) {
|
||||
TSThread *thread = [[transaction extension:TSThreadDatabaseViewExtensionName]
|
||||
objectAtIndexPath:[NSIndexPath indexPathForItem:(NSInteger)item inSection:(NSInteger)section]
|
||||
withMappings:self.threadMappings];
|
||||
[threads addObject:thread];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
_threads = [threads copy];
|
||||
}
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
+ (NSString *)tag
|
||||
{
|
||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||
}
|
||||
|
||||
- (NSString *)tag
|
||||
{
|
||||
return self.class.tag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
Loading…
Reference in New Issue