session-ios/Signal/src/call/RecentCallManager.m

187 lines
6.9 KiB
Objective-C

#import "RecentCallManager.h"
#import "ContactsManager.h"
#import "FunctionalUtil.h"
#import "ObservableValue.h"
#import "PreferencesUtil.h"
#define RECENT_CALLS_DEFAULT_KEY @"RPRecentCallsDefaultKey"
typedef BOOL (^SearchTermConditionalBlock)(RecentCall*, NSUInteger, BOOL*);
@interface RecentCallManager () {
NSMutableArray *_allRecents;
}
@end
@implementation RecentCallManager
- (id)init {
if (self = [super init]) {
[self initRecentCallsObservable];
}
return self;
}
-(void) initRecentCallsObservable {
_allRecents = [self loadContactsFromDefaults];
observableRecentsController = [ObservableValueController observableValueControllerWithInitialValue:_allRecents];
}
- (ObservableValue *)getObservableRecentCalls {
return observableRecentsController;
}
-(void) watchForContactUpdatesFrom:(ContactsManager*) contactManager untillCancelled:(TOCCancelToken*) cancelToken{
[contactManager.getObservableWhisperUsers watchLatestValue:^(NSArray* latestUsers) {
for (RecentCall* recentCall in _allRecents) {
if (![contactManager latestContactWithRecordId:recentCall.contactRecordID]) {
Contact* contact = [contactManager latestContactForPhoneNumber:recentCall.phoneNumber];
if(contact){
[self updateRecentCall:recentCall withContactId:contact.recordID];
}
}
}
} onThread:NSThread.mainThread untilCancelled:cancelToken];
}
-(void) watchForCallsThrough:(PhoneManager*)phoneManager
untilCancelled:(TOCCancelToken*)untilCancelledToken {
require(phoneManager != nil);
[phoneManager.currentCallObservable watchLatestValue:^(CallState* latestCall) {
if (latestCall != nil && Environment.preferences.getHistoryLogEnabled) {
[self addCall:latestCall];
}
} onThread:NSThread.mainThread untilCancelled:untilCancelledToken];
}
-(void) addCall:(CallState*)call {
require(call != nil);
[call.futureCallLocallyAcceptedOrRejected finallyDo:^(TOCFuture* interactionCompletion) {
bool isOutgoingCall = call.initiatedLocally;
bool isMissedCall = interactionCompletion.hasFailed;
Contact* contact = [self tryGetContactForCall:call];
RPRecentCallType callType = isOutgoingCall ? RPRecentCallTypeOutgoing
: isMissedCall ? RPRecentCallTypeMissed
: RPRecentCallTypeIncoming;
[self addRecentCall:[RecentCall recentCallWithContactID:contact.recordID
andNumber:call.remoteNumber
andCallType:callType]];
}];
}
-(Contact*) tryGetContactForCall:(CallState*)call {
if (call.potentiallySpecifiedContact != nil) return call.potentiallySpecifiedContact;
return [self tryGetContactForNumber:call.remoteNumber];
}
-(Contact*) tryGetContactForNumber:(PhoneNumber*)number {
return [Environment.getCurrent.contactsManager latestContactForPhoneNumber:number];
}
- (void)addMissedCallDueToBusy:(ResponderSessionDescriptor*)incomingCallDescriptor {
require(incomingCallDescriptor != nil);
Contact* contact = [self tryGetContactForNumber:incomingCallDescriptor.initiatorNumber];
[self addRecentCall:[RecentCall recentCallWithContactID:contact.recordID
andNumber:incomingCallDescriptor.initiatorNumber
andCallType:RPRecentCallTypeMissed]];
}
-(void) updateRecentCall:(RecentCall*) recentCall withContactId:(ABRecordID) contactId {
[recentCall updateRecentCallWithContactId:contactId];
[observableRecentsController updateValue:_allRecents.copy];
[self saveContactsToDefaults];
}
- (void)addRecentCall:(RecentCall *)recentCall {
[_allRecents insertObject:recentCall atIndex:0];
[Environment.preferences setFreshInstallTutorialsEnabled:NO];
[observableRecentsController updateValue:_allRecents.copy];
[self saveContactsToDefaults];
}
- (void)removeRecentCall:(RecentCall *)recentCall {
[_allRecents removeObject:recentCall];
[observableRecentsController updateValue:_allRecents.copy];
[self saveContactsToDefaults];
}
- (void)archiveRecentCall:(RecentCall *)recentCall {
NSUInteger indexOfRecent = [_allRecents indexOfObject:recentCall];
recentCall.isArchived = YES;
_allRecents[indexOfRecent] = recentCall;
[self saveContactsToDefaults];
[observableRecentsController updateValue:_allRecents.copy];
}
- (void)clearRecentCalls {
[_allRecents removeAllObjects];
[observableRecentsController updateValue:_allRecents.copy];
[self saveContactsToDefaults];
}
- (void)saveContactsToDefaults {
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
NSData *saveData = [NSKeyedArchiver archivedDataWithRootObject:_allRecents.copy];
[defaults setObject:saveData forKey:RECENT_CALLS_DEFAULT_KEY];
[defaults synchronize];
}
- (NSMutableArray *)loadContactsFromDefaults {
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
NSData *encodedData = [defaults objectForKey:RECENT_CALLS_DEFAULT_KEY];
id data = [NSKeyedUnarchiver unarchiveObjectWithData:encodedData];
if(![data isKindOfClass:NSArray.class]) {
return [NSMutableArray array];
} else {
return [NSMutableArray arrayWithArray:data];
}
}
- (NSArray *)recentsForSearchString:(NSString *)optionalSearchString andExcludeArchived:(BOOL)excludeArchived {
ContactsManager *contactsManager = Environment.getCurrent.contactsManager;
SearchTermConditionalBlock searchBlock = ^BOOL(RecentCall *obj, NSUInteger idx, BOOL *stop) {
BOOL nameMatchesSearch = YES;
BOOL numberMatchesSearch = YES;
if (optionalSearchString) {
NSString *contactName = [contactsManager latestContactWithRecordId:obj.contactRecordID].fullName;
nameMatchesSearch = [ContactsManager name:contactName matchesQuery:optionalSearchString];
numberMatchesSearch = [ContactsManager phoneNumber:obj.phoneNumber matchesQuery:optionalSearchString];
}
if (excludeArchived) {
return !obj.isArchived && (nameMatchesSearch || numberMatchesSearch);
} else {
return (nameMatchesSearch || numberMatchesSearch);
}
};
NSIndexSet *newsFeedIndexes = [_allRecents indexesOfObjectsPassingTest:searchBlock];
return [_allRecents objectsAtIndexes:newsFeedIndexes];
}
- (NSUInteger)missedCallCount {
SearchTermConditionalBlock missedCallBlock = ^BOOL(RecentCall *recentCall, NSUInteger idx, BOOL *stop) {
return !recentCall.userNotified;
};
return [[_allRecents indexesOfObjectsPassingTest:missedCallBlock] count];
}
-(BOOL) isPhoneNumberPresentInRecentCalls:(PhoneNumber*) phoneNumber {
return [_allRecents any:^int(RecentCall* call) {
return [call.phoneNumber resolvesInternationallyTo:phoneNumber];
}];
}
@end