1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-02 09:27:55 +01:00
Telegram/Telegraph/TGSecretModernConversationCompanion.m
2016-02-25 01:03:51 +01:00

676 lines
24 KiB
Objective-C

#import "TGSecretModernConversationCompanion.h"
#import "ActionStage.h"
#import "SGraphObjectNode.h"
#import "TGInterfaceManager.h"
#import "TGTelegraph.h"
#import "TGActionSheet.h"
#import "TGAlertView.h"
#import "TGNavigationBar.h"
#import "TGSecretChatUserInfoController.h"
#import "TGMessageModernConversationItem.h"
#import "TGMessage.h"
#import "TGModernConversationController.h"
#import "TGModernConversationTitleIcon.h"
#import "TGSecretConversationHandshakeStatusPanel.h"
#import "TGModernConversationActionInputPanel.h"
#import "TGSecretConversationEmptyListView.h"
#import "TGSecretModernConversationAccessoryTimerView.h"
#import "TGDatabase.h"
#import "TGAppDelegate.h"
#import "TGDialogListController.h"
#import "TGTelegraphDialogListCompanion.h"
#import "TGPopoverController.h"
#import "TGModernGalleryController.h"
#import "TGSecretTimerValueController.h"
#import "TGStringUtils.h"
#import "TGModernConversationUpgradeStateTitlePanel.h"
#import "TGModernSendSecretMessageActor.h"
#import "TGPickerSheet.h"
@interface TGSecretModernConversationCompanion () <TGSecretModernConversationAccessoryTimerViewDelegate>
{
int64_t _encryptedConversationId;
int64_t _accessHash;
bool _encryptedConversationIsIncoming;
NSString *_encryptedConversationUserName;
NSTimeInterval _lastTypingActivity;
TGConversation *_conversation; // Main Thread
int _encryptionState; // Main Thread
TGSecretModernConversationAccessoryTimerView *_selfDestructTimerView;
int _selfDestructTimer; // Main Thread
TGPickerSheet *_pickerSheet;
}
@end
@implementation TGSecretModernConversationCompanion
- (instancetype)initWithEncryptedConversationId:(int64_t)encryptedConversationId accessHash:(int64_t)accessHash conversationId:(int64_t)conversationId uid:(int)uid activity:(NSString *)activity mayHaveUnreadMessages:(bool)mayHaveUnreadMessages
{
self = [super initWithConversationId:conversationId uid:uid activity:activity mayHaveUnreadMessages:mayHaveUnreadMessages];
if (self != nil)
{
_encryptedConversationId = encryptedConversationId;
_accessHash = accessHash;
_conversation = [TGDatabaseInstance() loadConversationWithIdCached:_conversationId];
_encryptedConversationIsIncoming = _conversation.chatParticipants.chatAdminId != TGTelegraphInstance.clientUserId;
_encryptedConversationUserName = [TGDatabaseInstance() loadUser:uid].displayFirstName;
}
return self;
}
- (void)dealloc
{
UIView *selfDestructTimerView = _selfDestructTimerView;
TGPickerSheet *pickerSheet = _pickerSheet;
dispatch_async(dispatch_get_main_queue(), ^
{
[selfDestructTimerView alpha];
[pickerSheet dismiss];
});
}
#pragma mark -
- (void)loadInitialState
{
_selfDestructTimer = [TGDatabaseInstance() messageLifetimeForPeerId:_conversationId];
_selfDestructTimerView.timerValue = _selfDestructTimer;
[self updateLayer:[TGDatabaseInstance() peerLayer:_conversationId]];\
[super loadInitialState];
TGModernConversationTitleIcon *lockIcon = [[TGModernConversationTitleIcon alloc] init];
lockIcon.bounds = CGRectMake(0.0f, 0.0f, 16, 16);
lockIcon.offsetWeight = 0.5f;
lockIcon.imageOffset = CGPointMake(3.0f, 5.0f);
static UIImage *lockImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
lockImage = [UIImage imageNamed:@"ModernConversationTitleIconLock.png"];
});
lockIcon.image = lockImage;
lockIcon.iconPosition = TGModernConversationTitleIconPositionBeforeTitle;
[self setAdditionalTitleIcons:@[lockIcon]];
[self _updateEncryptionState:_conversation.encryptedData.handshakeState];
}
#pragma mark -
- (bool)controllerShouldStoreCapturedAssets
{
return false;
}
- (bool)controllerShouldCacheServerAssets
{
return false;
}
- (bool)controllerShouldLiveUploadVideo
{
return false;
}
- (bool)allowMessageForwarding
{
return false;
}
- (bool)allowReplies {
return [self layer] >= 45;
}
- (bool)allowMessageEntities {
return [self layer] >= 45;
}
- (bool)allowContactSharing
{
return false;
}
- (bool)allowVenueSharing
{
return [self layer] >= 45;
}
- (bool)allowCaptionedMedia
{
return [self layer] >= 45;
}
- (bool)encryptUploads
{
return true;
}
- (void)_controllerAvatarPressed
{
TGModernConversationController *controller = self.controller;
if (controller.currentSizeClass == UIUserInterfaceSizeClassCompact) {
[[TGInterfaceManager instance] navigateToProfileOfUser:_uid encryptedConversationId:_encryptedConversationId];
} else {
if (controller != nil)
{
TGSecretChatUserInfoController *secretChatInfoController = [[TGSecretChatUserInfoController alloc] initWithUid:_uid encryptedConversationId:_encryptedConversationId];
TGNavigationController *navigationController = [TGNavigationController navigationControllerWithControllers:@[secretChatInfoController] navigationBarClass:[TGWhiteNavigationBar class]];
navigationController.presentationStyle = TGNavigationControllerPresentationStyleRootInPopover;
TGPopoverController *popoverController = [[TGPopoverController alloc] initWithContentViewController:navigationController];
navigationController.parentPopoverController = popoverController;
navigationController.detachFromPresentingControllerInCompactMode = true;
[popoverController setContentSize:CGSizeMake(320.0f, 528.0f)];
controller.associatedPopoverController = popoverController;
[popoverController presentPopoverFromBarButtonItem:controller.navigationItem.rightBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:true];
secretChatInfoController.collectionView.contentOffset = CGPointMake(0.0f, -secretChatInfoController.collectionView.contentInset.top);
}
}
}
- (TGModernConversationEmptyListPlaceholderView *)_conversationEmptyListPlaceholder
{
TGSecretConversationEmptyListView *placeholder = [[TGSecretConversationEmptyListView alloc] initWithIncoming:_encryptedConversationIsIncoming userName:_encryptedConversationUserName];
return placeholder;
}
- (UIView *)_controllerInputTextPanelAccessoryView
{
if (_selfDestructTimerView == nil)
{
_selfDestructTimerView = [[TGSecretModernConversationAccessoryTimerView alloc] init];
_selfDestructTimerView.delegate = self;
_selfDestructTimerView.timerValue = _selfDestructTimer;
}
return _selfDestructTimerView;
}
- (void)accessoryTimerViewPressed:(TGSecretModernConversationAccessoryTimerView *)__unused accessoryTimerView
{
NSMutableArray *timerValues = [[NSMutableArray alloc] init];
[timerValues addObject:@(0)];
for (int i = 1; i < 16; i++)
{
[timerValues addObject:@(i)];
}
[timerValues addObject:@(30)];
[timerValues addObject:@(1 * 60)];
[timerValues addObject:@(1 * 60 * 60)];
[timerValues addObject:@(1 * 60 * 60 * 24)];
[timerValues addObject:@(1 * 60 * 60 * 24 * 7)];
NSUInteger selectedIndex = 5;
if (_selfDestructTimer != 0)
{
NSInteger closestMatchIndex = 5;
NSInteger index = -1;
for (NSNumber *nValue in timerValues)
{
index++;
if ([nValue intValue] != 0 && ABS([nValue intValue] - _selfDestructTimer) < ABS([timerValues[closestMatchIndex] intValue] - _selfDestructTimer))
{
closestMatchIndex = index;
}
}
selectedIndex = closestMatchIndex;
}
__weak TGSecretModernConversationCompanion *weakSelf = self;
_pickerSheet = [[TGPickerSheet alloc] initWithItems:timerValues selectedIndex:selectedIndex action:^(NSNumber *timerValue)
{
__strong TGSecretModernConversationCompanion *strongSelf = weakSelf;
[strongSelf _commitSetSelfDestructTimer:[timerValue intValue]];
}];
TGModernConversationController *controller = self.controller;
if (TGAppDelegateInstance.rootController.currentSizeClass == UIUserInterfaceSizeClassRegular)
{
CGRect windowRect = [_selfDestructTimerView convertRect:_selfDestructTimerView.bounds toView:controller.view];
[_pickerSheet showFromRect:windowRect inView:controller.view];
}
else
[_pickerSheet show];
}
- (void)_commitSetSelfDestructTimer:(int)value
{
if (value != _selfDestructTimer)
{
_selfDestructTimer = value;
_selfDestructTimerView.timerValue = _selfDestructTimer;
if (_selfDestructTimer > 0 && _selfDestructTimer <= 60 && [self layer] < 17)
{
TGUser *user = [TGDatabaseInstance() loadUser:_uid];
TGDispatchOnMainThread(^
{
NSString *text = [[NSString alloc] initWithFormat:TGLocalized(@"Compatibility.SecretMediaVersionTooLow"), user.displayFirstName, user.displayFirstName];
[[[TGAlertView alloc] initWithTitle:text message:nil cancelButtonTitle:TGLocalized(@"Common.OK") okButtonTitle:nil completionBlock:nil] show];
});
}
[TGDatabaseInstance() setMessageLifetimeForPeerId:_conversationId encryptedConversationId:_encryptedConversationId messageLifetime:value writeToActionQueue:true];
[ActionStageInstance() requestActor:@"/tg/service/synchronizeserviceactions/(settings)" options:nil watcher:TGTelegraphInstance];
}
}
- (bool)shouldDisplayContactLinkPanel
{
return false;
}
#pragma mark -
- (void)_updateEncryptionState:(int)encryptionState
{
if (_encryptionState != encryptionState)
{
_encryptionState = encryptionState;
TGModernConversationInputPanel *panel = nil;
if (encryptionState == 1 || encryptionState == 2)
{
TGSecretConversationHandshakeStatusPanel *statusPanel = [[TGSecretConversationHandshakeStatusPanel alloc] init];
statusPanel.delegate = self.controller;
panel = statusPanel;
if (encryptionState == 1) // awaiting
{
NSString *formatText = TGLocalized(@"Conversation.EncryptionWaiting");
NSString *baseText = [[NSString alloc] initWithFormat:formatText, [TGDatabaseInstance() loadUser:_uid].displayFirstName];
[statusPanel setText:baseText];
}
else
[statusPanel setText:TGLocalized(@"Conversation.EncryptionProcessing")];
}
else if (encryptionState == 3) // cancelled
{
TGModernConversationActionInputPanel *deleteAndExitPanel = [[TGModernConversationActionInputPanel alloc] init];
[deleteAndExitPanel setActionWithTitle:TGLocalized(@"ConversationProfile.LeaveDeleteAndExit") action:@"deleteAndExit"];
deleteAndExitPanel.delegate = self.controller;
deleteAndExitPanel.companionHandle = self.actionHandle;
panel = deleteAndExitPanel;
}
TGModernConversationController *controller = self.controller;
[controller setCustomInputPanel:panel];
}
}
- (void)updateLayer:(NSUInteger)layer
{
self.layer = layer;
[self updateDebugPanel];
}
- (void)updateDebugPanel
{
/*#ifdef INTERNAL_RELEASE
TGDispatchOnMainThread(^
{
TGModernConversationController *controller = self.controller;
TGModernConversationUpgradeStateTitlePanel *panel = [controller.secondaryTitlePanel isKindOfClass:[TGModernConversationUpgradeStateTitlePanel class]] ? (TGModernConversationUpgradeStateTitlePanel *)controller.secondaryTitlePanel : nil;
if (panel == nil)
{
panel = [[TGModernConversationUpgradeStateTitlePanel alloc] init];
__weak TGSecretModernConversationCompanion *weakSelf = self;
panel.rekey = ^
{
__strong TGSecretModernConversationCompanion *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf _maybeRekey];
}
};
}
int64_t keyId = 0;
[TGDatabaseInstance() encryptionKeyForConversationId:_conversationId requestedKeyFingerprint:0 outKeyFingerprint:&keyId];
[panel setCurrentLayer:self.layer keyId:keyId rekeySessionId:_conversation.encryptedData.currentRekeyExchangeId canRekey:self.layer >= 20 && _conversation.encryptedData.keyFingerprint != 0 && _conversation.encryptedData.currentRekeyExchangeId == 0];
[controller setSecondaryTitlePanel:panel animated:false];
});
#endif*/
}
- (NSDictionary *)userActivityData
{
return nil;
}
#pragma mark -
- (void)_maybeRekey
{
[TGModernSendSecretMessageActor maybeRekeyPeerId:_conversationId];
}
#pragma mark -
- (void)controllerDidUpdateTypingActivity
{
[ActionStageInstance() dispatchOnStageQueue:^
{
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
if (ABS(currentTime - _lastTypingActivity) >= 4.0)
{
_lastTypingActivity = currentTime;
[ActionStageInstance() requestActor:[NSString stringWithFormat:@"/tg/conversation/(%lld)/activity/(typing)", _conversationId] options:[self _optionsForMessageActions] watcher:self];
}
}];
}
- (void)controllerDidCancelTypingActivity
{
}
#pragma mark -
- (bool)imageDownloadsShouldAutosavePhotos
{
return false;
}
- (bool)_shouldCacheRemoteAssetUris
{
return false;
}
- (int)messageLifetime
{
return [TGDatabaseInstance() messageLifetimeForPeerId:_conversationId];
}
- (NSString *)_sendMessagePathForMessageId:(int32_t)mid
{
return [[NSString alloc] initWithFormat:@"/tg/sendSecretMessage/(%@)/(%d)", [self _conversationIdPathComponent], mid];
}
- (NSString *)_sendMessagePathPrefix
{
return [[NSString alloc] initWithFormat:@"/tg/sendSecretMessage/(%@)/", [self _conversationIdPathComponent]];
}
- (void)subscribeToUpdates
{
[ActionStageInstance() watchForPaths:@[
[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/conversation", _conversationId],
[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/readByDateMessages", _conversationId],
[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/messageFlagChanges", _conversationId],
[[NSString alloc] initWithFormat:@"/tg/conversation/messageViewDateChanges"],
[[NSString alloc] initWithFormat:@"/tg/encrypted/messageLifetime/(%" PRId64 ")", _conversationId],
[[NSString alloc] initWithFormat:@"/tg/peerLayerUpdates/(%" PRId64 ")", _conversationId]
] watcher:self];
[super subscribeToUpdates];
}
- (NSDictionary *)_optionsForMessageActions
{
return @{
@"conversationId": @(_conversationId),
@"encryptedConversationId": @(_encryptedConversationId),
@"accessHash": @(_accessHash),
@"isEncrypted": @(true)
};
}
- (bool)_messagesNeedRandomId
{
return true;
}
- (void)serviceNotificationsForMessageIds:(NSArray *)messageIds
{
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
NSMutableDictionary *messageFlagChanges = [[NSMutableDictionary alloc] init];
NSMutableArray *randomIds = [[NSMutableArray alloc] init];
for (NSNumber *nMid in messageIds)
{
int32_t messageId = [nMid intValue];
int messageFlags = [TGDatabaseInstance() secretMessageFlags:messageId];
//if ((messageFlags & TGSecretMessageFlagScreenshot) == 0)
{
messageFlags |= TGSecretMessageFlagScreenshot;
TGMessage *message = [TGDatabaseInstance() loadMessageWithMid:messageId peerId:_conversationId];
if (message != nil)
{
messageFlagChanges[@(messageId)] = @(messageFlags);
int64_t randomId = [TGDatabaseInstance() randomIdForMessageId:messageId];
if (randomId != 0)
[randomIds addObject:@(randomId)];
}
}
}
int64_t encryptedConversationId = [TGDatabaseInstance() encryptedConversationIdForPeerId:_conversationId];
if (encryptedConversationId != 0 && randomIds.count != 0)
{
int64_t actionRandomId = 0;
arc4random_buf(&actionRandomId, 8);
NSUInteger peerLayer = [TGDatabaseInstance() peerLayer:_conversationId];
NSData *messageData = [TGModernSendSecretMessageActor decryptedServiceMessageActionWithLayer:MIN(peerLayer, [TGModernSendSecretMessageActor currentLayer]) screenshotMessagesWithRandomIds:randomIds randomId:actionRandomId];
if (messageData != nil)
{
[TGModernSendSecretMessageActor enqueueOutgoingServiceMessageForPeerId:_conversationId layer:MIN(peerLayer, [TGModernSendSecretMessageActor currentLayer]) keyId:0 randomId:actionRandomId messageData:messageData];
}
[ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/tg/conversation/(%" PRId64 ")/messageFlagChanges", _conversationId] resource:messageFlagChanges];
}
}];
}
- (void)markMessagesAsViewed:(NSArray *)messageIds
{
[TGDatabaseInstance() dispatchOnDatabaseThread:^
{
NSMutableArray *readMesageIds = [[NSMutableArray alloc] init];
for (NSNumber *nMessageId in messageIds)
{
TGMessage *message = [TGDatabaseInstance() loadMessageWithMid:[nMessageId intValue] peerId:_conversationId];
if (!message.outgoing && message.messageLifetime > 0 && message.messageLifetime <= 60 && message.layer >= 17)
{
bool initiatedCountdown = false;
[TGDatabaseInstance() messageCountdownLocalTime:[nMessageId intValue] enqueueIfNotQueued:true initiatedCountdown:&initiatedCountdown];
if (initiatedCountdown)
[readMesageIds addObject:nMessageId];
}
}
if (readMesageIds.count != 0)
{
[ActionStageInstance() requestActor:@"/tg/service/synchronizeserviceactions/(settings)" options:nil watcher:TGTelegraphInstance];
}
} synchronous:false];
}
#pragma mark -
- (TGMessageModernConversationItem *)_updateMediaStatusData:(TGMessageModernConversationItem *)item
{
if (item->_message.mediaAttachments.count != 0)
{
bool canBeRead = false;
for (TGMediaAttachment *attachment in item->_message.mediaAttachments)
{
switch (attachment.type)
{
case TGImageMediaAttachmentType:
case TGVideoMediaAttachmentType:
canBeRead = item->_message.messageLifetime > 0 && item->_message.messageLifetime <= 60;
break;
case TGAudioMediaAttachmentType:
canBeRead = true;
break;
case TGDocumentMediaAttachmentType:
for (id attribute in ((TGDocumentMediaAttachment *)attachment).attributes) {
if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) {
canBeRead = ((TGDocumentAttributeAudio *)attribute).isVoice;
break;
}
}
break;
default:
break;
}
}
if (canBeRead) {
int flags = [TGDatabaseInstance() secretMessageFlags:item->_message.mid];
NSTimeInterval viewDate = [TGDatabaseInstance() messageCountdownLocalTime:item->_message.mid enqueueIfNotQueued:false initiatedCountdown:NULL];
if (flags != 0 || ABS(viewDate - DBL_EPSILON) > 0.0)
[self _setMessageFlagsAndViewDate:item->_message.mid flags:flags viewDate:viewDate];
}
}
return [super _updateMediaStatusData:item];
}
#pragma mark -
- (void)actionStageActionRequested:(NSString *)action options:(id)options
{
if ([action isEqualToString:@"actionPanelAction"])
{
NSString *panelAction = options[@"action"];
if ([panelAction isEqualToString:@"deleteAndExit"])
{
TGModernConversationController *controller = self.controller;
UINavigationController *navigationController = controller.navigationController;
NSUInteger index = [navigationController.viewControllers indexOfObject:controller];
if (index != NSNotFound)
{
[TGAppDelegateInstance.rootController.dialogListController.dialogListCompanion deleteItem:[[TGConversation alloc] initWithConversationId:_conversationId unreadCount:0 serviceUnreadCount:0] animated:false];
[self _dismissController];
}
}
}
[super actionStageActionRequested:action options:options];
}
- (void)actionStageResourceDispatched:(NSString *)path resource:(id)resource arguments:(id)arguments
{
if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/readByDateMessages", _conversationId]])
{
int maxDate = [resource[@"maxDate"] intValue];
[TGModernConversationCompanion dispatchOnMessageQueue:^
{
NSMutableArray *messageIds = [[NSMutableArray alloc] init];
for (TGMessageModernConversationItem *messageItem in _items)
{
if (messageItem->_message.deliveryState == TGMessageDeliveryStateDelivered)
{
if (messageItem->_additionalDate != 0)
{
if (messageItem->_additionalDate <= maxDate)
[messageIds addObject:[[NSNumber alloc] initWithInt:messageItem->_message.mid]];
}
else if (messageItem->_message.date <= maxDate)
[messageIds addObject:[[NSNumber alloc] initWithInt:messageItem->_message.mid]];
}
}
if (messageIds.count != 0)
[self _updateMessagesRead:messageIds];
}];
}
else if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/conversation", _conversationId]])
{
TGDispatchOnMainThread(^
{
_conversation = ((SGraphObjectNode *)resource).object;
[self _updateEncryptionState:_conversation.encryptedData.handshakeState];
[self updateDebugPanel];
});
}
else if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/messageFlagChanges", _conversationId]])
{
TGDispatchOnMainThread(^
{
[(NSMutableDictionary *)resource enumerateKeysAndObjectsUsingBlock:^(NSNumber *nMessageId, NSNumber *nFlags, __unused BOOL *stop)
{
[self _setMessageFlags:(int32_t)[nMessageId intValue] flags:[nFlags intValue]];
}];
});
}
else if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/conversation/messageViewDateChanges"]])
{
TGDispatchOnMainThread(^
{
[(NSMutableDictionary *)resource enumerateKeysAndObjectsUsingBlock:^(NSNumber *nMessageId, NSNumber *nViewDate, __unused BOOL *stop)
{
[self _setMessageViewDate:(int32_t)[nMessageId intValue] viewDate:[nViewDate doubleValue]];
}];
});
}
else if ([path hasPrefix:@"/tg/encrypted/messageLifetime/"])
{
TGDispatchOnMainThread(^
{
_selfDestructTimer = [resource intValue];
_selfDestructTimerView.timerValue = _selfDestructTimer;
});
}
else if ([path isEqualToString:[[NSString alloc] initWithFormat:@"/tg/peerLayerUpdates/(%" PRId64 ")", _conversationId]])
{
[self updateLayer:[resource[@"layer"] unsignedIntegerValue]];
}
[super actionStageResourceDispatched:path resource:resource arguments:arguments];
}
- (void)controllerDidChangeInputText:(NSString *)inputText
{
if (self.layer >= 23)
[super controllerDidChangeInputText:inputText];
}
- (bool)allowExternalContent {
return true;
}
@end