Group functionality

• create
• send/receive full functionality
• basic UI for group updating.

TODOS:
    -group avatars not supported
    -group update occurrence initiated by Android displayed in thread UI but not yet fully

Reviewed-by: @FredericJacobs
pull/1/head
Christine Corbett 10 years ago committed by Frederic Jacobs
parent c74899661c
commit 333c920e0b

@ -363,14 +363,12 @@
B6B096631A1D25ED008BFAA6 /* TSPreKeyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095F11A1D25ED008BFAA6 /* TSPreKeyManager.m */; }; B6B096631A1D25ED008BFAA6 /* TSPreKeyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095F11A1D25ED008BFAA6 /* TSPreKeyManager.m */; };
B6B096641A1D25ED008BFAA6 /* TSContactThread.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095F51A1D25ED008BFAA6 /* TSContactThread.m */; }; B6B096641A1D25ED008BFAA6 /* TSContactThread.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095F51A1D25ED008BFAA6 /* TSContactThread.m */; };
B6B096651A1D25ED008BFAA6 /* TSGroupThread.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095F71A1D25ED008BFAA6 /* TSGroupThread.m */; }; B6B096651A1D25ED008BFAA6 /* TSGroupThread.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095F71A1D25ED008BFAA6 /* TSGroupThread.m */; };
B6B096671A1D25ED008BFAA6 /* TSGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095FB1A1D25ED008BFAA6 /* TSGroup.m */; };
B6B096681A1D25ED008BFAA6 /* TSRecipient.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095FD1A1D25ED008BFAA6 /* TSRecipient.m */; }; B6B096681A1D25ED008BFAA6 /* TSRecipient.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095FD1A1D25ED008BFAA6 /* TSRecipient.m */; };
B6B096691A1D25ED008BFAA6 /* TSThread.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095FF1A1D25ED008BFAA6 /* TSThread.m */; }; B6B096691A1D25ED008BFAA6 /* TSThread.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B095FF1A1D25ED008BFAA6 /* TSThread.m */; };
B6B0966A1A1D25ED008BFAA6 /* IncomingPushMessageSignal.pb.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096021A1D25ED008BFAA6 /* IncomingPushMessageSignal.pb.m */; settings = {COMPILER_FLAGS = "-w"; }; }; B6B0966A1A1D25ED008BFAA6 /* IncomingPushMessageSignal.pb.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096021A1D25ED008BFAA6 /* IncomingPushMessageSignal.pb.m */; settings = {COMPILER_FLAGS = "-w"; }; };
B6B0966B1A1D25ED008BFAA6 /* TSAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096041A1D25ED008BFAA6 /* TSAttachment.m */; }; B6B0966B1A1D25ED008BFAA6 /* TSAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096041A1D25ED008BFAA6 /* TSAttachment.m */; };
B6B0966C1A1D25ED008BFAA6 /* TSCall.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096061A1D25ED008BFAA6 /* TSCall.m */; }; B6B0966C1A1D25ED008BFAA6 /* TSCall.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096061A1D25ED008BFAA6 /* TSCall.m */; };
B6B0966D1A1D25ED008BFAA6 /* TSErrorMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096081A1D25ED008BFAA6 /* TSErrorMessage.m */; }; B6B0966D1A1D25ED008BFAA6 /* TSErrorMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096081A1D25ED008BFAA6 /* TSErrorMessage.m */; };
B6B0966E1A1D25ED008BFAA6 /* TSGroupMessageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B0960A1A1D25ED008BFAA6 /* TSGroupMessageManager.m */; };
B6B0966F1A1D25ED008BFAA6 /* TSIncomingMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B0960C1A1D25ED008BFAA6 /* TSIncomingMessage.m */; }; B6B0966F1A1D25ED008BFAA6 /* TSIncomingMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B0960C1A1D25ED008BFAA6 /* TSIncomingMessage.m */; };
B6B096701A1D25ED008BFAA6 /* TSInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B0960E1A1D25ED008BFAA6 /* TSInfoMessage.m */; }; B6B096701A1D25ED008BFAA6 /* TSInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B0960E1A1D25ED008BFAA6 /* TSInfoMessage.m */; };
B6B096711A1D25ED008BFAA6 /* TSInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096101A1D25ED008BFAA6 /* TSInteraction.m */; }; B6B096711A1D25ED008BFAA6 /* TSInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B096101A1D25ED008BFAA6 /* TSInteraction.m */; };
@ -995,8 +993,6 @@
B6B095F51A1D25ED008BFAA6 /* TSContactThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSContactThread.m; sourceTree = "<group>"; }; B6B095F51A1D25ED008BFAA6 /* TSContactThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSContactThread.m; sourceTree = "<group>"; };
B6B095F61A1D25ED008BFAA6 /* TSGroupThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGroupThread.h; sourceTree = "<group>"; }; B6B095F61A1D25ED008BFAA6 /* TSGroupThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGroupThread.h; sourceTree = "<group>"; };
B6B095F71A1D25ED008BFAA6 /* TSGroupThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGroupThread.m; sourceTree = "<group>"; }; B6B095F71A1D25ED008BFAA6 /* TSGroupThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGroupThread.m; sourceTree = "<group>"; };
B6B095FA1A1D25ED008BFAA6 /* TSGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGroup.h; sourceTree = "<group>"; };
B6B095FB1A1D25ED008BFAA6 /* TSGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGroup.m; sourceTree = "<group>"; };
B6B095FC1A1D25ED008BFAA6 /* TSRecipient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSRecipient.h; sourceTree = "<group>"; }; B6B095FC1A1D25ED008BFAA6 /* TSRecipient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSRecipient.h; sourceTree = "<group>"; };
B6B095FD1A1D25ED008BFAA6 /* TSRecipient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSRecipient.m; sourceTree = "<group>"; }; B6B095FD1A1D25ED008BFAA6 /* TSRecipient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSRecipient.m; sourceTree = "<group>"; };
B6B095FE1A1D25ED008BFAA6 /* TSThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TSThread.h; path = ../TSThread.h; sourceTree = "<group>"; }; B6B095FE1A1D25ED008BFAA6 /* TSThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TSThread.h; path = ../TSThread.h; sourceTree = "<group>"; };
@ -1009,8 +1005,6 @@
B6B096061A1D25ED008BFAA6 /* TSCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSCall.m; sourceTree = "<group>"; }; B6B096061A1D25ED008BFAA6 /* TSCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSCall.m; sourceTree = "<group>"; };
B6B096071A1D25ED008BFAA6 /* TSErrorMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSErrorMessage.h; sourceTree = "<group>"; }; B6B096071A1D25ED008BFAA6 /* TSErrorMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSErrorMessage.h; sourceTree = "<group>"; };
B6B096081A1D25ED008BFAA6 /* TSErrorMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSErrorMessage.m; sourceTree = "<group>"; }; B6B096081A1D25ED008BFAA6 /* TSErrorMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSErrorMessage.m; sourceTree = "<group>"; };
B6B096091A1D25ED008BFAA6 /* TSGroupMessageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGroupMessageManager.h; sourceTree = "<group>"; };
B6B0960A1A1D25ED008BFAA6 /* TSGroupMessageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGroupMessageManager.m; sourceTree = "<group>"; };
B6B0960B1A1D25ED008BFAA6 /* TSIncomingMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSIncomingMessage.h; sourceTree = "<group>"; }; B6B0960B1A1D25ED008BFAA6 /* TSIncomingMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSIncomingMessage.h; sourceTree = "<group>"; };
B6B0960C1A1D25ED008BFAA6 /* TSIncomingMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSIncomingMessage.m; sourceTree = "<group>"; }; B6B0960C1A1D25ED008BFAA6 /* TSIncomingMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSIncomingMessage.m; sourceTree = "<group>"; };
B6B0960D1A1D25ED008BFAA6 /* TSInfoMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSInfoMessage.h; sourceTree = "<group>"; }; B6B0960D1A1D25ED008BFAA6 /* TSInfoMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSInfoMessage.h; sourceTree = "<group>"; };
@ -2335,8 +2329,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B6B095F31A1D25ED008BFAA6 /* Threads */, B6B095F31A1D25ED008BFAA6 /* Threads */,
B6B095FA1A1D25ED008BFAA6 /* TSGroup.h */,
B6B095FB1A1D25ED008BFAA6 /* TSGroup.m */,
B6B095FC1A1D25ED008BFAA6 /* TSRecipient.h */, B6B095FC1A1D25ED008BFAA6 /* TSRecipient.h */,
B6B095FD1A1D25ED008BFAA6 /* TSRecipient.m */, B6B095FD1A1D25ED008BFAA6 /* TSRecipient.m */,
); );
@ -2366,8 +2358,6 @@
B6B096061A1D25ED008BFAA6 /* TSCall.m */, B6B096061A1D25ED008BFAA6 /* TSCall.m */,
B6B096071A1D25ED008BFAA6 /* TSErrorMessage.h */, B6B096071A1D25ED008BFAA6 /* TSErrorMessage.h */,
B6B096081A1D25ED008BFAA6 /* TSErrorMessage.m */, B6B096081A1D25ED008BFAA6 /* TSErrorMessage.m */,
B6B096091A1D25ED008BFAA6 /* TSGroupMessageManager.h */,
B6B0960A1A1D25ED008BFAA6 /* TSGroupMessageManager.m */,
B6B0960B1A1D25ED008BFAA6 /* TSIncomingMessage.h */, B6B0960B1A1D25ED008BFAA6 /* TSIncomingMessage.h */,
B6B0960C1A1D25ED008BFAA6 /* TSIncomingMessage.m */, B6B0960C1A1D25ED008BFAA6 /* TSIncomingMessage.m */,
B6B0960D1A1D25ED008BFAA6 /* TSInfoMessage.h */, B6B0960D1A1D25ED008BFAA6 /* TSInfoMessage.h */,
@ -3107,7 +3097,6 @@
76EB061A18170B33006006FC /* DiscardingLog.m in Sources */, 76EB061A18170B33006006FC /* DiscardingLog.m in Sources */,
B63AF5C71A1F757900D01AAD /* TSContactsIntersectionRequest.m in Sources */, B63AF5C71A1F757900D01AAD /* TSContactsIntersectionRequest.m in Sources */,
B6B0968B1A1D25ED008BFAA6 /* TSStorageManager+SignedPreKeyStore.m in Sources */, B6B0968B1A1D25ED008BFAA6 /* TSStorageManager+SignedPreKeyStore.m in Sources */,
B6B0966E1A1D25ED008BFAA6 /* TSGroupMessageManager.m in Sources */,
76EB05AC18170B33006006FC /* SrtpSocket.m in Sources */, 76EB05AC18170B33006006FC /* SrtpSocket.m in Sources */,
B6CBF53F1A254BD1000D4184 /* ContactDetailCell.m in Sources */, B6CBF53F1A254BD1000D4184 /* ContactDetailCell.m in Sources */,
FCB11D931A12A4AA002F93FB /* FullImageViewController.m in Sources */, FCB11D931A12A4AA002F93FB /* FullImageViewController.m in Sources */,
@ -3150,7 +3139,6 @@
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */, 76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */,
76EB060A18170B33006006FC /* SignalUtil.m in Sources */, 76EB060A18170B33006006FC /* SignalUtil.m in Sources */,
76EB062818170B33006006FC /* BadArgument.m in Sources */, 76EB062818170B33006006FC /* BadArgument.m in Sources */,
B6B096671A1D25ED008BFAA6 /* TSGroup.m in Sources */,
76EB062E18170B33006006FC /* SecurityFailure.m in Sources */, 76EB062E18170B33006006FC /* SecurityFailure.m in Sources */,
76EB05F218170B33006006FC /* PhoneNumber.m in Sources */, 76EB05F218170B33006006FC /* PhoneNumber.m in Sources */,
E197B61718BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m in Sources */, E197B61718BBEC1A00F073E5 /* AnonymousAudioCallbackHandler.m in Sources */,

@ -120,6 +120,7 @@
</view> </view>
<connections> <connections>
<segue destination="urv-62-RsD" kind="presentation" identifier="fingerprintSegue" modalPresentationStyle="overCurrentContext" animates="NO" id="Zjl-QX-tHE"/> <segue destination="urv-62-RsD" kind="presentation" identifier="fingerprintSegue" modalPresentationStyle="overCurrentContext" animates="NO" id="Zjl-QX-tHE"/>
<segue destination="bDi-2Q-XOC" kind="show" identifier="updateGroupSegue" id="gZ1-lh-srF"/>
</connections> </connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="yXZ-iE-5va" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="yXZ-iE-5va" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -3404,7 +3405,7 @@ Licensed under the GPLv3</string>
<placeholder placeholderIdentifier="IBFirstResponder" id="fUD-iU-Cax" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="fUD-iU-Cax" userLabel="First Responder" sceneMemberID="firstResponder"/>
<searchDisplayController id="f1M-Dk-nMv"/> <searchDisplayController id="f1M-Dk-nMv"/>
</objects> </objects>
<point key="canvasLocation" x="3082.5" y="-906.75"/> <point key="canvasLocation" x="3184.5" y="-906.75"/>
</scene> </scene>
<!--New Group View Controller--> <!--New Group View Controller-->
<scene sceneID="mdV-ti-fPA"> <scene sceneID="mdV-ti-fPA">
@ -3612,7 +3613,7 @@ Licensed under the GPLv3</string>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="GsM-dR-L7j" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="GsM-dR-L7j" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="3930" y="-906.75"/> <point key="canvasLocation" x="4197" y="-899.25"/>
</scene> </scene>
<!--Navigation Controller--> <!--Navigation Controller-->
<scene sceneID="kfT-eG-hkf"> <scene sceneID="kfT-eG-hkf">
@ -3657,4 +3658,7 @@ Licensed under the GPLv3</string>
<image name="signal.png" width="50" height="50"/> <image name="signal.png" width="50" height="50"/>
<image name="signals_tab.png" width="24" height="24"/> <image name="signals_tab.png" width="24" height="24"/>
</resources> </resources>
<inferredMetricsTieBreakers>
<segue reference="gZ1-lh-srF"/>
</inferredMetricsTieBreakers>
</document> </document>

@ -3,7 +3,7 @@
#import "PropertyListPreferences.h" #import "PropertyListPreferences.h"
#import "PacketHandler.h" #import "PacketHandler.h"
#import "SecureEndPoint.h" #import "SecureEndPoint.h"
#import "GroupModel.h"
/** /**
* *
* Environment is a data and data accessor class. * Environment is a data and data accessor class.
@ -85,5 +85,7 @@ andCurrentRegionCodeForPhoneNumbers:(NSString*)currentRegionCodeForPhoneNumbers
- (void)setSignUpFlowNavigationController:(UINavigationController *)signUpFlowNavigationController; - (void)setSignUpFlowNavigationController:(UINavigationController *)signUpFlowNavigationController;
+ (void)messageIdentifier:(NSString*)identifier; + (void)messageIdentifier:(NSString*)identifier;
+ (void)groupModel:(GroupModel*)model;
@end @end

@ -189,6 +189,26 @@ phoneDirectoryManager;
} }
} }
+ (void)groupModel:(GroupModel*)model {
Environment *env = [self getCurrent];
SignalsViewController *vc = env.signalsViewController;
if (vc.presentedViewController) {
[vc.presentedViewController dismissViewControllerAnimated:YES completion:nil];
}
[vc.navigationController popToRootViewControllerAnimated:YES];
vc.groupFromCompose = model;
[vc performSegueWithIdentifier:@"showSegue" sender:nil];
UITabBarController *tabBarController = (UITabBarController*)vc.parentViewController.parentViewController;
if ([tabBarController respondsToSelector:@selector(selectedIndex)]) {
tabBarController.selectedIndex = 1;
}
}
+ (void)resetAppData{ + (void)resetAppData{
[SignalKeyingStorage wipeKeychain]; [SignalKeyingStorage wipeKeychain];
[Environment.preferences clear]; [Environment.preferences clear];

@ -1,27 +0,0 @@
//
// TSGroup.h
// TextSecureKit
//
// Created by Frederic Jacobs on 12/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TSAttachment.h"
#import "TSYapDatabaseObject.h"
@interface TSGroup : TSYapDatabaseObject
@property (nonatomic) NSString *name;// Name of the group
@property (nonatomic) TSAttachment *avatar;// Link to the attachment object (group picture)
@property (nonatomic) NSSet *members;// Each member of the discussion is a TSUser
- (NSData*)groupIdentifier;
+ (TSGroup*)groupWithId:(NSData*)id;
- (NSSet*)membersIdentifier;
@end

@ -1,17 +0,0 @@
//
// TSGroup.m
// TextSecureKit
//
// Created by Frederic Jacobs on 12/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import "TSGroup.h"
@implementation TSGroup
+ (NSString*)collection{
return @"TSGroup";
}
@end

@ -11,7 +11,6 @@
#import "ContactsManager.h" #import "ContactsManager.h"
#import "TSInteraction.h" #import "TSInteraction.h"
#import "TSStorageManager.h" #import "TSStorageManager.h"
#import "TSGroup.h"
#import "TSCall.h" #import "TSCall.h"
#import "TSOutgoingMessage.h" #import "TSOutgoingMessage.h"

@ -7,11 +7,13 @@
// //
#import "TSThread.h" #import "TSThread.h"
#import "GroupModel.h"
@interface TSGroupThread : TSThread @interface TSGroupThread : TSThread
@property (nonatomic,strong) GroupModel* groupModel;
+ (instancetype)threadWithGroupId:(NSData*)groupId; + (instancetype)threadWithGroupModel:(GroupModel *)groupModel transaction:(YapDatabaseReadWriteTransaction*)transaction;
- (NSData*)groupId; - (NSData*)groupId;
- (NSArray *)recipientsWithTransaction:(YapDatabaseReadTransaction*)transaction;
@end @end

@ -7,30 +7,34 @@
// //
#import "TSGroupThread.h" #import "TSGroupThread.h"
#import "TSRecipient.h"
#import "NSData+Base64.h" #import "NSData+Base64.h"
@implementation TSGroupThread @implementation TSGroupThread
#define TSGroupThreadPrefix @"g" #define TSGroupThreadPrefix @"g"
- (instancetype)initWithGroupId:(NSData*)groupId{ - (instancetype)initWithGroupModel:(GroupModel *)groupModel{
NSString *uniqueIdentifier = [[self class] threadIdFromGroupId:groupId]; NSString *uniqueIdentifier = [[self class] threadIdFromGroupId:groupModel.groupId];
self = [super initWithUniqueId:uniqueIdentifier]; self = [super initWithUniqueId:uniqueIdentifier];
_groupModel = groupModel;
return self; return self;
} }
+ (instancetype)threadWithGroupId:(NSData *)groupId{
+ (instancetype)threadWithGroupModel:(GroupModel *)groupModel transaction:(YapDatabaseReadWriteTransaction*)transaction{
TSGroupThread *thread = [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId]]; TSGroupThread *thread = [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupModel.groupId] transaction:transaction];
if (!thread) { if (!thread) {
thread = [[TSGroupThread alloc] initWithGroupId:groupId]; thread = [[TSGroupThread alloc] initWithGroupModel:groupModel];
[thread save]; [thread saveWithTransaction:transaction];
}
else if(![thread.groupModel isEqual:groupModel]) {
thread.groupModel = groupModel;
[thread saveWithTransaction:transaction];
} }
return thread; return thread;
} }
@ -42,6 +46,12 @@
return [[self class] groupIdFromThreadId:self.uniqueId]; return [[self class] groupIdFromThreadId:self.uniqueId];
} }
- (NSString*)name{
return self.groupModel.groupName;
}
+ (NSString*)threadIdFromGroupId:(NSData*)groupId{ + (NSString*)threadIdFromGroupId:(NSData*)groupId{
return [TSGroupThreadPrefix stringByAppendingString:[groupId base64EncodedString]]; return [TSGroupThreadPrefix stringByAppendingString:[groupId base64EncodedString]];
} }
@ -50,4 +60,20 @@
return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length-1)]]; return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length-1)]];
} }
- (NSArray *)recipientsWithTransaction:(YapDatabaseReadTransaction*)transaction{
NSMutableArray *recipients = [[NSMutableArray alloc] init];
for(NSString *recipientId in _groupModel.groupMemberIds) {
TSRecipient *recipient = [TSRecipient recipientWithTextSecureIdentifier:recipientId withTransaction:transaction];
if (!recipient){
recipient = [[TSRecipient alloc] initWithTextSecureIdentifier:recipientId relay:nil];
}
[recipients addObject:recipient];
}
return recipients;
}
@end @end

@ -1,17 +0,0 @@
//
// TSGroupMessageManager.h
// TextSecureKit
//
// Created by Frederic Jacobs on 15/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IncomingPushMessageSignal.pb.h"
@interface TSGroupMessageManager : NSObject
+ (void)processGroupMessage:(IncomingPushMessageSignal*)pushMessage content:(PushMessageContent*)content;
@end

@ -1,88 +0,0 @@
//
// TSGroupMessageManager.m
// TextSecureKit
//
// Created by Frederic Jacobs on 15/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import <CocoaLumberjack/DDLog.h>
#import "TSGroup.h"
#import "TSGroupMessageManager.h"
#define ddLogLevel LOG_LEVEL_VERBOSE
@implementation TSGroupMessageManager
+ (void)processGroupMessage:(IncomingPushMessageSignal*)pushMessage content:(PushMessageContent*)content{
if (!content.group.id) {
DDLogInfo(@"Received group message with no id! Ignoring...");
return;
}
PushMessageContentGroupContext *group = content.group;
NSData *id = group.id;
int type = group.type;
TSGroup *record = [TSGroup groupWithId:id];
if (record != nil && type == PushMessageContentGroupContextTypeUpdate) {
//TODO: [self handleGroupUpdate:pushMessage group:group record:record];
} else if (record == nil && type == PushMessageContentGroupContextTypeUpdate) {
[self handleGroupCreate:pushMessage group:group record:record];
} else if (record != nil && type == PushMessageContentGroupContextTypeQuit) {
[self handleGroupLeave:pushMessage group:group record:record];
} else if (type == PushMessageContentGroupContextTypeUnknown) {
DDLogInfo(@"Received unknown type, ignoring...");
}
}
+ (void)handleGroupCreate:(IncomingPushMessageSignal*)message group:(PushMessageContentGroupContext*)group record:(TSGroup*)record{
//TODO
}
+ (void)handleGroupLeave:(IncomingPushMessageSignal*)pushMessage group:(PushMessageContentGroupContext*)group record:(TSGroup*)record{
//TODO
}
//+ (void)handleGroupUpdate:(IncomingPushMessageSignal*)pushMessage group:(PushMessageContentGroupContext*)group record:(TSGroup*)record{
// NSData *identifier = group.id;
// NSArray *messageMembersIds = group.members;
//
// NSSet *recordMembers = record.membersIdentifier;
// NSSet *messageMembers = [NSSet setWithArray:messageMembersIds];
//
// NSMutableSet *addedMembers = [messageMembers mutableCopy];
// [addedMembers minusSet:recordMembers];
//
// NSMutableSet missingMembers = [recordMembers mutableCopy];
// [missingMembers minusSet:messageMembers];
//
// if (addedMembers.count > 0) {
// Set<String> unionMembers = new HashSet<String>(recordMembers);
// unionMembers.addAll(messageMembers);
// database.updateMembers(id, new LinkedList<String>(unionMembers));
//
// group = group.toBuilder().clearMembers().addAllMembers(addedMembers).build();
//
// } else {
// group = group.toBuilder().clearMembers().build();
// }
//
// if (missingMembers > 0) {
//
// }
//
// if (group.hasName || group.hasAvatar) {
// record.avatar = group.avatar;
// record.name = group.name;
// [record save];
// }
//
// // TO-DO: Implement
//
//}
@end

@ -13,13 +13,14 @@
typedef NS_ENUM(NSInteger, TSInfoMessageType){ typedef NS_ENUM(NSInteger, TSInfoMessageType){
TSInfoMessageTypeSessionDidEnd, TSInfoMessageTypeSessionDidEnd,
TSInfoMessageUserNotRegistered, TSInfoMessageUserNotRegistered,
TSInfoMessageTypeUnsupportedMessage TSInfoMessageTypeUnsupportedMessage,
TSInfoMessageTypeGroupUpdate
}; };
+ (instancetype)userNotRegisteredMessageInThread:(TSThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction; + (instancetype)userNotRegisteredMessageInThread:(TSThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction;
@property TSInfoMessageType messageType; @property TSInfoMessageType messageType;
- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSContactThread *)contact messageType:(TSInfoMessageType)infoMessage; - (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)contact messageType:(TSInfoMessageType)infoMessage;
@end @end

@ -11,8 +11,8 @@
@implementation TSInfoMessage @implementation TSInfoMessage
- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSContactThread *)contact messageType:(TSInfoMessageType)infoMessage{ - (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread messageType:(TSInfoMessageType)infoMessage{
self = [super initWithTimestamp:timestamp inThread:contact messageBody:nil attachments:nil]; self = [super initWithTimestamp:timestamp inThread:thread messageBody:nil attachments:nil];
if (self) { if (self) {
_messageType = infoMessage; _messageType = infoMessage;
@ -21,7 +21,7 @@
return self; return self;
} }
+ (instancetype)userNotRegisteredMessageInThread:(TSContactThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction{ + (instancetype)userNotRegisteredMessageInThread:(TSThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction{
return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread messageType:TSInfoMessageUserNotRegistered]; return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread messageType:TSInfoMessageUserNotRegistered];
} }
@ -34,6 +34,8 @@
return @"Media messages are currently not supported."; return @"Media messages are currently not supported.";
case TSInfoMessageUserNotRegistered: case TSInfoMessageUserNotRegistered:
return @"The user is not registered."; return @"The user is not registered.";
case TSInfoMessageTypeGroupUpdate:
return @"Updated the group";
default: default:
break; break;
} }

@ -13,10 +13,18 @@
* Abstract message class. Is instantiated by either * Abstract message class. Is instantiated by either
*/ */
typedef NS_ENUM(NSInteger, TSGroupMetaMessage){
TSGroupMessageNone,
TSGroupMessageNew,
TSGroupMessageUpdate,
TSGroupMessageDeliver,
TSGroupMessageQuit
};
@interface TSMessage : TSInteraction @interface TSMessage : TSInteraction
@property (nonatomic, readonly) NSMutableArray *attachments; @property (nonatomic, readonly) NSMutableArray *attachments;
@property (nonatomic, readonly) NSString *body; @property (nonatomic, readonly) NSString *body;
@property (nonatomic) TSGroupMetaMessage groupMetaMessage;
- (instancetype)initWithTimestamp:(uint64_t)timestamp - (instancetype)initWithTimestamp:(uint64_t)timestamp
inThread:(TSThread*)thread inThread:(TSThread*)thread

@ -21,6 +21,7 @@
#import "TSStorageManager+SignedPreKeyStore.h" #import "TSStorageManager+SignedPreKeyStore.h"
#import "PreKeyBundle+jsonDict.h" #import "PreKeyBundle+jsonDict.h"
#import "SignalKeyingStorage.h"
#import "TSAttachmentStream.h" #import "TSAttachmentStream.h"
#import "TSNetworkManager.h" #import "TSNetworkManager.h"
@ -53,12 +54,30 @@ dispatch_queue_t sendingQueue() {
} }
- (void)sendMessage:(TSOutgoingMessage*)message inThread:(TSThread*)thread{ - (void)sendMessage:(TSOutgoingMessage*)message inThread:(TSThread*)thread{
[self saveMessage:message withState:TSOutgoingMessageStateAttemptingOut];
dispatch_async(sendingQueue(), ^{ dispatch_async(sendingQueue(), ^{
if ([thread isKindOfClass:[TSGroupThread class]]) { if ([thread isKindOfClass:[TSGroupThread class]]) {
NSLog(@"Currently unsupported"); TSGroupThread* groupThread = (TSGroupThread*)thread;
} else if([thread isKindOfClass:[TSContactThread class]]){
[self saveGroupMessage:message inThread:thread];
__block NSArray* recipients;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
recipients = [groupThread recipientsWithTransaction:transaction];
}];
for(TSRecipient *rec in recipients){
// TODOGROUP hack so that we don't send group messages to ourselves; probably a more elegant way of doing this.
if( ![[rec uniqueId] isEqualToString:[SignalKeyingStorage.localNumber toE164]]){
[self sendMessage:message
toRecipient:rec
inThread:thread
withAttemps:3];
}
}
}
else if([thread isKindOfClass:[TSContactThread class]]){
[self saveMessage:message withState:TSOutgoingMessageStateDelivered];
TSContactThread *contactThread = (TSContactThread*)thread; TSContactThread *contactThread = (TSContactThread*)thread;
__block TSRecipient *recipient; __block TSRecipient *recipient;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
@ -81,7 +100,7 @@ dispatch_queue_t sendingQueue() {
if (remainingAttempts > 0) { if (remainingAttempts > 0) {
remainingAttempts -= 1; remainingAttempts -= 1;
[self outgoingMessages:message toRecipient:recipient completion:^(NSArray *messages) { [self outgoingMessages:message toRecipient:recipient inThread:thread completion:^(NSArray *messages) {
TSSubmitMessageRequest *request = [[TSSubmitMessageRequest alloc] initWithRecipient:recipient.uniqueId messages:messages relay:recipient.relay timeStamp:message.timeStamp]; TSSubmitMessageRequest *request = [[TSSubmitMessageRequest alloc] initWithRecipient:recipient.uniqueId messages:messages relay:recipient.relay timeStamp:message.timeStamp];
[[TSNetworkManager sharedManager] queueAuthenticatedRequest:request success:^(NSURLSessionDataTask *task, id responseObject) { [[TSNetworkManager sharedManager] queueAuthenticatedRequest:request success:^(NSURLSessionDataTask *task, id responseObject) {
@ -127,10 +146,10 @@ dispatch_queue_t sendingQueue() {
[self saveMessage:message withState:TSOutgoingMessageStateSent]; [self saveMessage:message withState:TSOutgoingMessageStateSent];
} }
- (void)outgoingMessages:(TSOutgoingMessage*)message toRecipient:(TSRecipient*)recipient completion:(messagesQueue)sendMessages{ - (void)outgoingMessages:(TSOutgoingMessage*)message toRecipient:(TSRecipient*)recipient inThread:(TSThread*)thread completion:(messagesQueue)sendMessages{
NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count]; NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count];
TSStorageManager *storage = [TSStorageManager sharedManager]; TSStorageManager *storage = [TSStorageManager sharedManager];
NSData *plainText = [self plainTextForMessage:message]; NSData *plainText = [self plainTextForMessage:message inThread:thread];
for (NSNumber *deviceNumber in recipient.devices) { for (NSNumber *deviceNumber in recipient.devices) {
@try { @try {
@ -209,16 +228,51 @@ dispatch_queue_t sendingQueue() {
} }
- (void)saveMessage:(TSOutgoingMessage*)message withState:(TSOutgoingMessageState)state{ - (void)saveMessage:(TSOutgoingMessage*)message withState:(TSOutgoingMessageState)state{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { if(message.groupMetaMessage == TSGroupMessageDeliver || message.groupMetaMessage == TSGroupMessageNone) {
[message setMessageState:state]; [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message saveWithTransaction:transaction]; [message setMessageState:state];
}]; [message saveWithTransaction:transaction];
}];
}
} }
- (NSData*)plainTextForMessage:(TSOutgoingMessage*)message{ - (void) saveGroupMessage:(TSOutgoingMessage*)message inThread:(TSThread*)thread{
if(message.groupMetaMessage==TSGroupMessageDeliver) {
[self saveMessage:message withState:message.messageState];
}
else {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[[TSInfoMessage alloc] initWithTimestamp:message.timeStamp inThread:thread messageType:TSInfoMessageTypeGroupUpdate] saveWithTransaction:transaction];
}];
}
}
- (NSData*)plainTextForMessage:(TSOutgoingMessage*)message inThread:(TSThread*)thread{
PushMessageContentBuilder *builder = [PushMessageContentBuilder new]; PushMessageContentBuilder *builder = [PushMessageContentBuilder new];
[builder setBody:message.body]; [builder setBody:message.body];
if([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *gThread = (TSGroupThread*)thread;
PushMessageContentGroupContextBuilder *groupBuilder = [PushMessageContentGroupContextBuilder new];
[groupBuilder setMembersArray:gThread.groupModel.groupMemberIds];
[groupBuilder setName:gThread.groupModel.groupName];
[groupBuilder setId:gThread.groupModel.groupId];
switch (message.groupMetaMessage) {
case TSGroupMessageQuit:
[groupBuilder setType:PushMessageContentGroupContextTypeQuit];
break;
case TSGroupMessageUpdate:
case TSGroupMessageNew:
[groupBuilder setType:PushMessageContentGroupContextTypeUpdate];
break;
default:
[groupBuilder setType:PushMessageContentGroupContextTypeDeliver];
break;
}
//[groupBuilder setAvatar:(PushMessageContentAttachmentPointer *)]; //TODOATTACHMENTS for avatar
[builder setGroup:groupBuilder.build];
}
NSMutableArray *attachmentsArray = [NSMutableArray array]; NSMutableArray *attachmentsArray = [NSMutableArray array];

@ -98,8 +98,10 @@
- (void)handleDeliveryReceipt:(IncomingPushMessageSignal*)signal{ - (void)handleDeliveryReceipt:(IncomingPushMessageSignal*)signal{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSOutgoingMessage *message = [TSOutgoingMessage fetchObjectWithUniqueID:[TSInteraction stringFromTimeStamp:signal.timestamp] transaction:transaction]; TSOutgoingMessage *message = [TSOutgoingMessage fetchObjectWithUniqueID:[TSInteraction stringFromTimeStamp:signal.timestamp] transaction:transaction];
message.messageState = TSOutgoingMessageStateDelivered; if(![message isKindOfClass:[TSInfoMessage class]]){
[message saveWithTransaction:transaction]; message.messageState = TSOutgoingMessageStateDelivered;
[message saveWithTransaction:transaction];
}
}]; }];
} }
@ -179,14 +181,11 @@
if ((content.flags & PushMessageContentFlagsEndSession) != 0) { if ((content.flags & PushMessageContentFlagsEndSession) != 0) {
DDLogVerbose(@"Received end session message..."); DDLogVerbose(@"Received end session message...");
[self handleEndSessionMessage:incomingMessage withContent:content]; [self handleEndSessionMessage:incomingMessage withContent:content];
} else if (content.hasGroup && (content.group.type != PushMessageContentGroupContextTypeDeliver)) {
DDLogVerbose(@"Received push group update message...");
[self handleGroupMessage:incomingMessage withContent:content];
} else if (content.attachments.count > 0) { } else if (content.attachments.count > 0) {
DDLogVerbose(@"Received push media message (attachment) ..."); DDLogVerbose(@"Received push media message (attachment) ...");
[self handleReceivedMediaMessage:incomingMessage withContent:content]; [self handleReceivedMediaMessage:incomingMessage withContent:content];
} else { } else {
DDLogVerbose(@"Received push text message..."); DDLogVerbose(@"Received individual push text message...");
[self handleReceivedTextMessage:incomingMessage withContent:content]; [self handleReceivedTextMessage:incomingMessage withContent:content];
} }
} }
@ -204,12 +203,8 @@
[[TSStorageManager sharedManager] deleteAllSessionsForContact:message.source]; [[TSStorageManager sharedManager] deleteAllSessionsForContact:message.source];
} }
- (void)handleGroupMessage:(IncomingPushMessageSignal*)message withContent:(PushMessageContent*)content{
// TO DO
}
- (void)handleReceivedTextMessage:(IncomingPushMessageSignal*)message withContent:(PushMessageContent*)content{ - (void)handleReceivedTextMessage:(IncomingPushMessageSignal*)message withContent:(PushMessageContent*)content{
[self handleReceivedMessage:message withContent:content attachments:nil]; [self handleReceivedMessage:message withContent:content attachments:content.attachments];
} }
- (void)handleReceivedMessage:(IncomingPushMessageSignal*)message withContent:(PushMessageContent*)content attachments:(NSArray*)attachments { - (void)handleReceivedMessage:(IncomingPushMessageSignal*)message withContent:(PushMessageContent*)content attachments:(NSArray*)attachments {
@ -221,17 +216,25 @@
TSIncomingMessage *incomingMessage; TSIncomingMessage *incomingMessage;
TSThread *thread; TSThread *thread;
if (groupId) { if (groupId) {
TSGroupThread *gThread = [TSGroupThread threadWithGroupId:groupId]; GroupModel *model = [[GroupModel alloc] initWithTitle:content.group.name memberIds:[[NSMutableArray alloc ] initWithArray:content.group.members] image:nil groupId:content.group.id]; //TODOGROUP group avatar will not be nil generically
[gThread saveWithTransaction:transaction]; TSGroupThread *gThread = [TSGroupThread threadWithGroupModel:model transaction:transaction];
incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:gThread authorId:message.source messageBody:body attachments:attachments]; [gThread saveWithTransaction:transaction];
if(content.group.type==PushMessageContentGroupContextTypeUpdate) {
[[[TSInfoMessage alloc] initWithTimestamp:timeStamp inThread:gThread messageType:TSInfoMessageTypeGroupUpdate] saveWithTransaction:transaction];
}
else {
incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:gThread authorId:message.source messageBody:body attachments:attachments];
[incomingMessage saveWithTransaction:transaction];
}
thread = gThread; thread = gThread;
} else{ }
else{
TSContactThread *cThread = [TSContactThread threadWithContactId:message.source transaction:transaction]; TSContactThread *cThread = [TSContactThread threadWithContactId:message.source transaction:transaction];
[cThread saveWithTransaction:transaction]; [cThread saveWithTransaction:transaction];
incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:cThread messageBody:body attachments:attachments]; incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:cThread messageBody:body attachments:attachments];
[incomingMessage saveWithTransaction:transaction];
thread = cThread; thread = cThread;
} }
[incomingMessage saveWithTransaction:transaction];
NSString *name = [thread name]; NSString *name = [thread name];
[self notifyUserForIncomingMessage:incomingMessage from:name]; [self notifyUserForIncomingMessage:incomingMessage from:name];
}]; }];
@ -266,10 +269,13 @@
- (void)processException:(NSException*)exception outgoingMessage:(TSOutgoingMessage*)message{ - (void)processException:(NSException*)exception outgoingMessage:(TSOutgoingMessage*)message{
DDLogWarn(@"Got exception: %@", exception.description); DDLogWarn(@"Got exception: %@", exception.description);
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { if(message.groupMetaMessage==TSGroupMessageNone) {
[message setMessageState:TSOutgoingMessageStateUnsent]; // Only update this with exception if it is not a group message as group messages may except for one group send but not another and the UI doesn't know how to handle that
[message saveWithTransaction:transaction]; [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
}]; [message setMessageState:TSOutgoingMessageStateUnsent];
[message saveWithTransaction:transaction];
}];
}
} }
- (void)notifyUserForIncomingMessage:(TSIncomingMessage*)message from:(NSString*)name{ - (void)notifyUserForIncomingMessage:(TSIncomingMessage*)message from:(NSString*)name{

@ -18,5 +18,4 @@ typedef NS_ENUM(NSInteger, TSOutgoingMessageState){
}; };
@property (nonatomic) TSOutgoingMessageState messageState; @property (nonatomic) TSOutgoingMessageState messageState;
@end @end

@ -20,6 +20,12 @@
if (self) { if (self) {
_messageState = TSOutgoingMessageStateAttemptingOut; _messageState = TSOutgoingMessageStateAttemptingOut;
if( [thread isKindOfClass:[TSGroupThread class]]) {
self.groupMetaMessage = TSGroupMessageDeliver;
}
else {
self.groupMetaMessage = TSGroupMessageNone;
}
} }
return self; return self;

@ -7,14 +7,20 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "TSYapDatabaseObject.h"
@interface GroupModel : NSObject
@property (nonatomic, strong) NSMutableArray * groupMembers;
@property (nonatomic, strong) UIImage * groupImage;
@property (nonatomic, strong) NSString * groupName;
@interface GroupModel : TSYapDatabaseObject
-(instancetype)initWithTitle:(NSString*)title members:(NSMutableArray*)members image:(UIImage*)image; @property (nonatomic, strong) NSMutableArray *groupMemberIds; //
@property (nonatomic, strong) UIImage *groupImage;
@property (nonatomic, strong) NSString *groupName;
@property (nonatomic, strong) NSData* groupId;
-(instancetype)initWithTitle:(NSString*)title memberIds:(NSMutableArray*)members image:(UIImage*)image groupId:(NSData*)groupId;
- (BOOL)isEqual:(id)other;
- (BOOL)isEqualToGroupModel:(GroupModel *)model;
@end @end

@ -10,13 +10,44 @@
@implementation GroupModel @implementation GroupModel
-(instancetype)initWithTitle:(NSString*)title members:(NSMutableArray*)members image:(UIImage*)image -(instancetype)initWithTitle:(NSString*)title memberIds:(NSMutableArray*)memberIds image:(UIImage*)image groupId:(NSData *)groupId{
{
_groupName=title; _groupName=title;
_groupMembers = [members copy]; _groupMemberIds = [memberIds copy];
_groupImage = image; _groupImage = image;
_groupId = groupId;
return self; return self;
} }
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
}
if (!other || ![other isKindOfClass:[self class]]) {
return NO;
}
return [self isEqualToGroupModel:other];
}
- (BOOL)isEqualToGroupModel:(GroupModel *)other {
if (self == other)
return YES;
if(![_groupId isEqualToData:other.groupId] ) {
return NO;
}
if (![_groupName isEqual:other.groupName]) {
return NO;
}
if( !(_groupImage!=nil && other.groupImage!=nil && [UIImagePNGRepresentation(_groupImage) isEqualToData:UIImagePNGRepresentation(other.groupImage)])) {
return NO;
}
NSMutableArray* compareMyGroupMemberIds = [NSMutableArray arrayWithArray:_groupMemberIds];
[compareMyGroupMemberIds removeObjectsInArray:other.groupMemberIds];
if([compareMyGroupMemberIds count] > 0 ) {
return NO;
}
return YES;
}
@end @end

@ -8,12 +8,14 @@
#import "JSQMessagesViewController.h" #import "JSQMessagesViewController.h"
#import "JSQMessages.h" #import "JSQMessages.h"
#import "GroupModel.h"
@class TSThread; @class TSThread;
@interface MessagesViewController : JSQMessagesViewController <UIImagePickerControllerDelegate,UINavigationControllerDelegate> @interface MessagesViewController : JSQMessagesViewController <UIImagePickerControllerDelegate,UINavigationControllerDelegate>
- (void)setupWithThread:(TSThread*)thread; - (void)setupWithThread:(TSThread*)thread;
- (void)setupWithTSIdentifier:(NSString*)identifier; - (void)setupWithTSIdentifier:(NSString*)identifier;
- (void)setupWithTSGroup:(GroupModel*)model;
@end @end

@ -11,6 +11,7 @@
#import "MessagesViewController.h" #import "MessagesViewController.h"
#import "FullImageViewController.h" #import "FullImageViewController.h"
#import "FingerprintViewController.h" #import "FingerprintViewController.h"
#import "NewGroupViewController.h"
#import "JSQCallCollectionViewCell.h" #import "JSQCallCollectionViewCell.h"
#import "JSQCall.h" #import "JSQCall.h"
@ -49,6 +50,9 @@
#import "ContactsManager.h" #import "ContactsManager.h"
static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60; static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60;
static NSString *const kUpdateGroupSegueIdentifier = @"updateGroupSegue";
static NSString *const kFingerprintSegueIdentifier = @"fingerprintSegue";
typedef enum : NSUInteger { typedef enum : NSUInteger {
kMediaTypePicture, kMediaTypePicture,
@ -82,16 +86,27 @@ typedef enum : NSUInteger {
}]; }];
} }
- (void)setupWithTSGroup:(GroupModel*)model {
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
self.thread = [TSGroupThread threadWithGroupModel:model transaction:transaction];
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:self.thread messageBody:@"" attachments:nil];
message.groupMetaMessage = TSGroupMessageNew;
[[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread];
isGroupConversation = YES;
}];
}
- (void)setupWithThread:(TSThread *)thread{ - (void)setupWithThread:(TSThread *)thread{
self.thread = thread; self.thread = thread;
isGroupConversation = [self.thread isKindOfClass:[TSGroupThread class]];
} }
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
[self markAllMessagesAsRead]; [self markAllMessagesAsRead];
isGroupConversation = NO; // TODO: Support Group Conversations
[self initializeBubbles]; [self initializeBubbles];
self.messageMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[self.thread.uniqueId] self.messageMappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[self.thread.uniqueId]
@ -143,10 +158,10 @@ typedef enum : NSUInteger {
self.title = self.thread.name; self.title = self.thread.name;
UIBarButtonItem * lockButton = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"lock"] style:UIBarButtonItemStylePlain target:self action:@selector(showFingerprint)];
if (!isGroupConversation && [self isRedPhoneReachable]) { if (!isGroupConversation && [self isRedPhoneReachable]) {
UIBarButtonItem * lockButton = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"lock"] style:UIBarButtonItemStylePlain target:self action:@selector(showFingerprint)];
UIBarButtonItem * callButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"call_tab"] style:UIBarButtonItemStylePlain target:self action:@selector(callAction)]; UIBarButtonItem * callButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"call_tab"] style:UIBarButtonItemStylePlain target:self action:@selector(callAction)];
[callButton setImageInsets:UIEdgeInsetsMake(0, -10, 0, -50)]; [callButton setImageInsets:UIEdgeInsetsMake(0, -10, 0, -50)];
UIBarButtonItem *negativeSeparator = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil]; UIBarButtonItem *negativeSeparator = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
@ -154,7 +169,8 @@ typedef enum : NSUInteger {
self.navigationItem.rightBarButtonItems = @[negativeSeparator, lockButton, callButton]; self.navigationItem.rightBarButtonItems = @[negativeSeparator, lockButton, callButton];
} else { } else {
self.navigationItem.rightBarButtonItem = lockButton; UIBarButtonItem *groupMenuButton = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"settings_tab"] style:UIBarButtonItemStylePlain target:self action:@selector(didPressGroupMenuButton:)];
self.navigationItem.rightBarButtonItem = groupMenuButton;
} }
} }
@ -188,7 +204,7 @@ typedef enum : NSUInteger {
-(void)showFingerprint -(void)showFingerprint
{ {
[self markAllMessagesAsRead]; [self markAllMessagesAsRead];
[self performSegueWithIdentifier:@"fingerprintSegue" sender:self]; [self performSegueWithIdentifier:kFingerprintSegueIdentifier sender:self];
} }
@ -363,8 +379,9 @@ typedef enum : NSUInteger {
} }
else { else {
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath]; TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
TSMessageAdapter *previousMessage = [self messageAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row-1 inSection:indexPath.section]]; TSMessageAdapter *previousMessage = [self messageAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row-1 inSection:indexPath.section]];
NSTimeInterval timeDifference = [currentMessage.date timeIntervalSinceDate:previousMessage.date]; NSTimeInterval timeDifference = [currentMessage.date timeIntervalSinceDate:previousMessage.date];
if (timeDifference > kTSMessageSentDateShowTimeInterval) { if (timeDifference > kTSMessageSentDateShowTimeInterval) {
showDate = YES; showDate = YES;
@ -375,10 +392,11 @@ typedef enum : NSUInteger {
-(NSAttributedString*)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath -(NSAttributedString*)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath
{ {
TSMessageAdapter * msg = [self messageAtIndexPath:indexPath];
if ([self showDateAtIndexPath:indexPath]) if ([self showDateAtIndexPath:indexPath]) {
{ TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
return [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:msg.date];
return [[JSQMessagesTimestampFormatter sharedFormatter] attributedTimestampForDate:currentMessage.date];
} }
return nil; return nil;
@ -387,20 +405,22 @@ typedef enum : NSUInteger {
-(BOOL)shouldShowMessageStatusAtIndexPath:(NSIndexPath*)indexPath -(BOOL)shouldShowMessageStatusAtIndexPath:(NSIndexPath*)indexPath
{ {
TSMessageAdapter * currentMessage = [self messageAtIndexPath:indexPath]; TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
if([self.thread isKindOfClass:[TSGroupThread class]]) {
if (indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section]-1) return currentMessage.messageType == TSIncomingMessageAdapter;
{
return [self isMessageOutgoingAndDelivered:currentMessage];
} }
else {
if (![self isMessageOutgoingAndDelivered:currentMessage]) if (indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section]-1) {
{ return [self isMessageOutgoingAndDelivered:currentMessage];
return NO; }
if (![self isMessageOutgoingAndDelivered:currentMessage]) {
return NO;
}
TSMessageAdapter *nextMessage = [self nextOutgoingMessage:indexPath];
return ![self isMessageOutgoingAndDelivered:nextMessage];
} }
TSMessageAdapter * nextMessage = [self nextOutgoingMessage:indexPath];
return ![self isMessageOutgoingAndDelivered:nextMessage];
} }
-(TSMessageAdapter*)nextOutgoingMessage:(NSIndexPath*)indexPath -(TSMessageAdapter*)nextOutgoingMessage:(NSIndexPath*)indexPath
@ -422,19 +442,29 @@ typedef enum : NSUInteger {
} }
-(NSAttributedString*)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath -(NSAttributedString*)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath {
{ TSMessageAdapter *msg = [self messageAtIndexPath:indexPath];
if ([self shouldShowMessageStatusAtIndexPath:indexPath]) if ([self shouldShowMessageStatusAtIndexPath:indexPath]) {
{ if([self.thread isKindOfClass:[TSGroupThread class]]) {
_lastDeliveredMessageIndexPath = indexPath; NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; textAttachment.bounds = CGRectMake(0, 0, 11.0f, 10.0f);
textAttachment.bounds = CGRectMake(0, 0, 11.0f, 10.0f); NSString *name = [[Environment getCurrent].contactsManager nameStringForPhoneIdentifier:msg.senderId];
NSMutableAttributedString * attrStr = [[NSMutableAttributedString alloc]initWithString:@"Delivered"]; name = name ? name : msg.senderId;
[attrStr appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]]; NSMutableAttributedString * attrStr = [[NSMutableAttributedString alloc]initWithString:name];
[attrStr appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
return (NSAttributedString*)attrStr;
}
else {
_lastDeliveredMessageIndexPath = indexPath;
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.bounds = CGRectMake(0, 0, 11.0f, 10.0f);
NSMutableAttributedString * attrStr = [[NSMutableAttributedString alloc]initWithString:@"Delivered"];
[attrStr appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
return (NSAttributedString*)attrStr; return (NSAttributedString*)attrStr;
}
} }
return nil; return nil;
} }
@ -442,8 +472,12 @@ typedef enum : NSUInteger {
layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath layout:(JSQMessagesCollectionViewFlowLayout *)collectionViewLayout heightForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
{ {
TSMessageAdapter * msg = [self messageAtIndexPath:indexPath]; TSMessageAdapter * msg = [self messageAtIndexPath:indexPath];
if([self.thread isKindOfClass:[TSGroupThread class]]) {
if (msg.messageType == TSOutgoingMessageAdapter) { if(msg.messageType == TSIncomingMessageAdapter) {
return 16.0f;
}
}
else if (msg.messageType == TSOutgoingMessageAdapter) {
return 16.0f; return 16.0f;
} }
@ -547,12 +581,18 @@ typedef enum : NSUInteger {
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"fingerprintSegue"]){ if ([segue.identifier isEqualToString:kFingerprintSegueIdentifier]){
FingerprintViewController *vc = [segue destinationViewController]; FingerprintViewController *vc = [segue destinationViewController];
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[vc configWithThread:self.thread]; [vc configWithThread:self.thread];
}]; }];
} }
else if ([segue.identifier isEqualToString:kUpdateGroupSegueIdentifier]) {
NewGroupViewController *vc = [segue destinationViewController];
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[vc configWithThread:(TSGroupThread*)self.thread];
}];
}
} }
@ -652,7 +692,6 @@ typedef enum : NSUInteger {
// Process the notification(s), // Process the notification(s),
// and get the change-set(s) as applies to my view and mappings configuration. // and get the change-set(s) as applies to my view and mappings configuration.
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction]; NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
NSArray *messageRowChanges = nil; NSArray *messageRowChanges = nil;
[[self.uiDatabaseConnection ext:TSMessageDatabaseViewExtensionName] getSectionChanges:nil [[self.uiDatabaseConnection ext:TSMessageDatabaseViewExtensionName] getSectionChanges:nil
@ -733,6 +772,44 @@ typedef enum : NSUInteger {
TSInteraction *interaction = [self interactionAtIndexPath:indexPath]; TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
return [TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread]; return [TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread];
} }
#pragma mark group action view
-(void)didPressGroupMenuButton:(UIButton *)sender
{
[self.inputToolbar.contentView.textView resignFirstResponder];
UIView *presenter = self.parentViewController.view;
[DJWActionSheet showInView:presenter
withTitle:nil
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@[@"Update group", @"Leave group", @"Delete thread"]
tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
NSLog(@"User Cancelled");
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
NSLog(@"Destructive button tapped");
}else {
switch (tappedButtonIndex) {
case 0:
DDLogDebug(@"update group picked");
[self performSegueWithIdentifier:kUpdateGroupSegueIdentifier sender:self];
break;
case 1:
DDLogDebug(@"leave group picket");
break;
case 2:
DDLogDebug(@"delete thread");
break;
default:
break;
}
}
}];
}
#pragma mark Accessory View #pragma mark Accessory View

@ -7,9 +7,11 @@
// //
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "TSGroupThread.h"
@interface NewGroupViewController : UIViewController <UITableViewDelegate, UITabBarDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate> @interface NewGroupViewController : UIViewController <UITableViewDelegate, UITabBarDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate>
- (void)configWithThread:(TSGroupThread*)thread;
@property(nonatomic, strong) IBOutlet UITableView* tableView; @property(nonatomic, strong) IBOutlet UITableView* tableView;
@property(nonatomic, strong) IBOutlet UITextField* nameGroupTextField; @property(nonatomic, strong) IBOutlet UITextField* nameGroupTextField;

@ -14,6 +14,8 @@
#import "Contact.h" #import "Contact.h"
#import "GroupModel.h" #import "GroupModel.h"
#import "SecurityUtils.h"
#import "SignalKeyingStorage.h"
#import "UIUtil.h" #import "UIUtil.h"
#import "DJWActionSheet.h" #import "DJWActionSheet.h"
@ -24,19 +26,29 @@
@interface NewGroupViewController () { @interface NewGroupViewController () {
NSArray* contacts; NSArray* contacts;
} }
@property TSGroupThread* thread;
@end @end
@implementation NewGroupViewController @implementation NewGroupViewController
- (void)configWithThread:(TSGroupThread *)gThread{
_thread = gThread;
}
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
if(_thread==nil) {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"Create" style:UIBarButtonItemStylePlain target:self action:@selector(createGroup)]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"Create" style:UIBarButtonItemStylePlain target:self action:@selector(createGroup)];
self.navigationItem.title = @"New Group"; self.navigationItem.title = @"New Group";
}
else {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"Update" style:UIBarButtonItemStylePlain target:self action:@selector(updateGroup)];
self.navigationItem.title = _thread.groupModel.groupName;
self.nameGroupTextField.text = _thread.groupModel.groupName;
}
contacts = [Environment getCurrent].contactsManager.textSecureContacts; contacts = [Environment getCurrent].contactsManager.textSecureContacts;
[self initializeDelegates]; [self initializeDelegates];
[self initializeTableView]; [self initializeTableView];
[self initializeKeyboardHandlers]; [self initializeKeyboardHandlers];
@ -73,28 +85,29 @@
#pragma mark - Actions #pragma mark - Actions
-(void)createGroup { -(void)createGroup {
SignalsViewController* s = (SignalsViewController*)((UINavigationController*)[((UITabBarController*)self.parentViewController.presentingViewController).childViewControllers objectAtIndex:1]).topViewController; GroupModel* model = [self makeGroup];
[Environment groupModel:model];
s.groupFromCompose = [self makeGroup]; }
[self dismissViewControllerAnimated:YES completion:^(){
[s performSegueWithIdentifier:@"showSegue" sender:nil]; -(void)updateGroup {
}]; DDLogDebug(@"Update gruop not implemented");
} }
-(GroupModel*)makeGroup { -(GroupModel*)makeGroup {
//TODO: Add it to Envirronment
NSString* title = _nameGroupTextField.text; NSString* title = _nameGroupTextField.text;
UIImage* img = _groupImageButton.imageView.image; UIImage* img = _groupImageButton.imageView.image;
NSMutableArray* mut = [[NSMutableArray alloc]init]; NSMutableArray* mut = [[NSMutableArray alloc]init];
for (NSIndexPath* idx in _tableView.indexPathsForSelectedRows) { for (NSIndexPath* idx in _tableView.indexPathsForSelectedRows) {
[mut addObject:[contacts objectAtIndex:(NSUInteger)idx.row-1]]; [mut addObjectsFromArray:[[contacts objectAtIndex:(NSUInteger)idx.row-1] textSecureIdentifiers]];
} }
// Also add the originator
[mut addObject:[SignalKeyingStorage.localNumber toE164]];
NSData* groupId = [SecurityUtils generateRandomBytes:16];
return [[GroupModel alloc] initWithTitle:title members:mut image:img]; return [[GroupModel alloc] initWithTitle:title memberIds:mut image:img groupId:groupId];
} }
-(IBAction)addGroupPhoto:(id)sender -(IBAction)addGroupPhoto:(id)sender
@ -198,12 +211,17 @@
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: indexPath.row == 0 ? @"HeaderCell" : @"GroupSearchCell"]; cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: indexPath.row == 0 ? @"HeaderCell" : @"GroupSearchCell"];
} }
if (indexPath.row > 0) { if (indexPath.row > 0) {
NSUInteger row = (NSUInteger)indexPath.row; NSUInteger row = (NSUInteger)indexPath.row;
Contact* contact = contacts[row-1]; Contact* contact = contacts[row-1];
if(_thread) {
cell.textLabel.attributedText = [self attributedStringForContact:contact inCell:cell]; //TODOGROUP inefficient way of doing this, will not scale well
NSMutableSet *usersInGroup = [NSMutableSet setWithArray:_thread.groupModel.groupMemberIds];
[usersInGroup intersectSet:[NSSet setWithArray:contact.userTextPhoneNumbers]];
cell.accessoryType = [usersInGroup count]>0 ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
}
cell.textLabel.attributedText = [self attributedStringForContact:contact inCell:cell];
} else { } else {
cell.textLabel.text = @"Add People:"; cell.textLabel.text = @"Add People:";

@ -16,7 +16,6 @@
@property (nonatomic) NSString *contactIdentifierFromCompose; @property (nonatomic) NSString *contactIdentifierFromCompose;
@property (nonatomic) GroupModel *groupFromCompose; @property (nonatomic) GroupModel *groupFromCompose;
@property (nonatomic, retain) IBOutlet UITableView *tableView; @property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UISegmentedControl *inboxArchiveSwitch; @property (nonatomic, retain) IBOutlet UISegmentedControl *inboxArchiveSwitch;

@ -161,12 +161,20 @@ static NSString *const kSegueIndentifier = @"showSegue";
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
TSThread *thread = [self threadForIndexPath:selectedIndexPath]; TSThread *thread = [self threadForIndexPath:selectedIndexPath];
if (thread) { if (self.contactIdentifierFromCompose){
[vc setupWithThread:thread];
} else if (self.contactIdentifierFromCompose){
[vc setupWithTSIdentifier:self.contactIdentifierFromCompose]; [vc setupWithTSIdentifier:self.contactIdentifierFromCompose];
self.contactIdentifierFromCompose = nil; self.contactIdentifierFromCompose = nil;
} }
else if (self.groupFromCompose) {
[vc setupWithTSGroup:self.groupFromCompose];
self.groupFromCompose = nil;
}
else if (thread) {
[vc setupWithThread:thread];
}
} }
} }

@ -108,4 +108,6 @@
}]; }];
} }
// TODO: group storage tests
@end @end

Loading…
Cancel
Save