2020-01-28 06:27:42 +01:00
final class NewClosedGroupVC : UIViewController , UITableViewDataSource , UITableViewDelegate {
2020-01-29 00:18:45 +01:00
private var selectedContacts : Set < String > = [ ]
2020-01-28 06:27:42 +01:00
private lazy var contacts : [ String ] = {
var result : [ String ] = [ ]
2020-02-04 04:16:21 +01:00
let storage = OWSPrimaryStorage . shared ( )
storage . dbReadConnection . read { transaction in
TSContactThread . enumerateCollectionObjects ( with : transaction ) { object , _ in
guard let thread = object as ? TSContactThread , thread . isContactFriend else { return }
let hexEncodedPublicKey = thread . contactIdentifier ( )
// W e s h o u l d n ' t b e a b l e t o a d d s l a v e d e v i c e s t o g r o u p s
if ( storage . getMasterHexEncodedPublicKey ( for : hexEncodedPublicKey , in : transaction ) = = nil ) {
result . append ( hexEncodedPublicKey )
}
}
2020-01-28 06:27:42 +01:00
}
func getDisplayName ( for hexEncodedPublicKey : String ) -> String {
2020-01-30 23:42:36 +01:00
return UserDisplayNameUtilities . getPrivateChatDisplayName ( for : hexEncodedPublicKey ) ? ? " Unknown Contact "
2020-01-28 06:27:42 +01:00
}
2020-01-30 10:09:02 +01:00
let userHexEncodedPublicKey = getUserHexEncodedPublicKey ( )
2020-01-30 05:51:46 +01:00
var linkedDeviceHexEncodedPublicKeys : Set < String > = [ userHexEncodedPublicKey ]
OWSPrimaryStorage . shared ( ) . dbReadConnection . read { transaction in
linkedDeviceHexEncodedPublicKeys = LokiDatabaseUtilities . getLinkedDeviceHexEncodedPublicKeys ( for : userHexEncodedPublicKey , in : transaction )
2020-01-28 06:27:42 +01:00
}
2020-01-30 05:51:46 +01:00
result = result . filter { ! linkedDeviceHexEncodedPublicKeys . contains ( $0 ) }
result = result . sorted { getDisplayName ( for : $0 ) < getDisplayName ( for : $1 ) }
2020-01-28 06:27:42 +01:00
return result
} ( )
// MARK: C o m p o n e n t s
2020-01-30 01:59:12 +01:00
private lazy var nameTextField = TextField ( placeholder : NSLocalizedString ( " Enter a group name " , comment : " " ) )
private lazy var tableView : UITableView = {
2020-01-28 06:27:42 +01:00
let result = UITableView ( )
result . dataSource = self
result . delegate = self
result . register ( Cell . self , forCellReuseIdentifier : " Cell " )
result . separatorStyle = . none
result . backgroundColor = . clear
result . showsVerticalScrollIndicator = false
return result
} ( )
// MARK: L i f e c y c l e
override func viewDidLoad ( ) {
// S e t g r a d i e n t b a c k g r o u n d
view . backgroundColor = . clear
let gradient = Gradients . defaultLokiBackground
view . setGradient ( gradient )
// S e t n a v i g a t i o n b a r b a c k g r o u n d c o l o r
let navigationBar = navigationController ! . navigationBar
navigationBar . setBackgroundImage ( UIImage ( ) , for : UIBarMetrics . default )
navigationBar . shadowImage = UIImage ( )
navigationBar . isTranslucent = false
navigationBar . barTintColor = Colors . navigationBarBackground
2020-01-29 00:18:45 +01:00
// S e t u p n a v i g a t i o n b a r b u t t o n s
let closeButton = UIBarButtonItem ( image : # imageLiteral ( resourceName : " X " ) , style : . plain , target : self , action : #selector ( close ) )
closeButton . tintColor = Colors . text
navigationItem . leftBarButtonItem = closeButton
let doneButton = UIBarButtonItem ( barButtonSystemItem : . done , target : self , action : #selector ( createClosedGroup ) )
doneButton . tintColor = Colors . text
navigationItem . rightBarButtonItem = doneButton
// C u s t o m i z e t i t l e
let titleLabel = UILabel ( )
titleLabel . text = NSLocalizedString ( " New Closed Group " , comment : " " )
titleLabel . textColor = Colors . text
titleLabel . font = . boldSystemFont ( ofSize : Values . veryLargeFontSize )
navigationItem . titleView = titleLabel
2020-01-29 03:32:08 +01:00
// S e t u p c o n t e n t
if ! contacts . isEmpty {
2020-01-30 01:59:12 +01:00
view . addSubview ( nameTextField )
nameTextField . pin ( . leading , to : . leading , of : view , withInset : Values . largeSpacing )
nameTextField . pin ( . top , to : . top , of : view , withInset : Values . mediumSpacing )
nameTextField . pin ( . trailing , to : . trailing , of : view , withInset : - Values . largeSpacing )
2020-02-04 10:07:16 +01:00
let explanationLabel = UILabel ( )
explanationLabel . textColor = Colors . text . withAlphaComponent ( Values . unimportantElementOpacity )
explanationLabel . font = . systemFont ( ofSize : Values . smallFontSize )
explanationLabel . text = NSLocalizedString ( " Closed groups are end-to-end encrypted group chats for up to 10 members. They provide the same privacy protections as one-on-one sessions. " , comment : " " )
explanationLabel . numberOfLines = 0
explanationLabel . textAlignment = . center
explanationLabel . lineBreakMode = . byWordWrapping
view . addSubview ( explanationLabel )
explanationLabel . pin ( . leading , to : . leading , of : view , withInset : Values . largeSpacing )
explanationLabel . pin ( . top , to : . bottom , of : nameTextField , withInset : Values . mediumSpacing )
explanationLabel . pin ( . trailing , to : . trailing , of : view , withInset : - Values . largeSpacing )
2020-01-30 01:59:12 +01:00
let separator = UIView ( )
separator . backgroundColor = Colors . separator
separator . set ( . height , to : Values . separatorThickness )
view . addSubview ( separator )
separator . pin ( . leading , to : . leading , of : view )
2020-02-04 10:07:16 +01:00
separator . pin ( . top , to : . bottom , of : explanationLabel , withInset : Values . largeSpacing )
2020-01-30 01:59:12 +01:00
separator . pin ( . trailing , to : . trailing , of : view )
2020-01-29 03:32:08 +01:00
view . addSubview ( tableView )
2020-01-30 01:59:12 +01:00
tableView . pin ( . leading , to : . leading , of : view )
tableView . pin ( . top , to : . bottom , of : separator )
tableView . pin ( . trailing , to : . trailing , of : view )
tableView . pin ( . bottom , to : . bottom , of : view )
2020-01-29 03:32:08 +01:00
} else {
let explanationLabel = UILabel ( )
explanationLabel . textColor = Colors . text
explanationLabel . font = . systemFont ( ofSize : Values . smallFontSize )
explanationLabel . numberOfLines = 0
explanationLabel . lineBreakMode = . byWordWrapping
explanationLabel . textAlignment = . center
explanationLabel . text = NSLocalizedString ( " You don't have any contacts yet " , comment : " " )
let createNewPrivateChatButton = Button ( style : . prominentOutline , size : . medium )
createNewPrivateChatButton . setTitle ( NSLocalizedString ( " Start a Session " , comment : " " ) , for : UIControl . State . normal )
createNewPrivateChatButton . addTarget ( self , action : #selector ( createPrivateChat ) , for : UIControl . Event . touchUpInside )
createNewPrivateChatButton . set ( . width , to : 160 )
let stackView = UIStackView ( arrangedSubviews : [ explanationLabel , createNewPrivateChatButton ] )
stackView . axis = . vertical
stackView . spacing = Values . mediumSpacing
stackView . alignment = . center
view . addSubview ( stackView )
stackView . center ( . horizontal , in : view )
let verticalCenteringConstraint = stackView . center ( . vertical , in : view )
verticalCenteringConstraint . constant = - 16 // M a k e s t h i n g s a p p e a r c e n t e r e d v i s u a l l y
}
2020-01-28 06:27:42 +01:00
}
// MARK: D a t a
func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
return contacts . count
}
func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
let cell = tableView . dequeueReusableCell ( withIdentifier : " Cell " ) as ! Cell
let contact = contacts [ indexPath . row ]
cell . hexEncodedPublicKey = contact
2020-01-29 00:18:45 +01:00
cell . hasTick = selectedContacts . contains ( contact )
2020-01-28 06:27:42 +01:00
return cell
}
// MARK: I n t e r a c t i o n
func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
2020-01-29 00:18:45 +01:00
let contact = contacts [ indexPath . row ]
if ! selectedContacts . contains ( contact ) {
selectedContacts . insert ( contact )
} else {
selectedContacts . remove ( contact )
}
guard let cell = tableView . cellForRow ( at : indexPath ) as ? Cell else { return }
cell . hasTick = selectedContacts . contains ( contact )
tableView . deselectRow ( at : indexPath , animated : true )
}
@objc private func close ( ) {
dismiss ( animated : true , completion : nil )
}
@objc private func createClosedGroup ( ) {
2020-01-30 01:59:12 +01:00
func showError ( title : String , message : String = " " ) {
let alert = UIAlertController ( title : title , message : message , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default , handler : nil ) )
presentAlert ( alert )
}
guard let name = nameTextField . text ? . trimmingCharacters ( in : . whitespacesAndNewlines ) , name . count > 0 else {
return showError ( title : NSLocalizedString ( " Please enter a group name " , comment : " " ) )
}
guard name . count < 64 else {
return showError ( title : NSLocalizedString ( " Please enter a shorter group name " , comment : " " ) )
}
guard selectedContacts . count >= 2 else {
return showError ( title : NSLocalizedString ( " Please pick at least 2 group members " , comment : " " ) )
}
2020-02-04 10:07:16 +01:00
guard selectedContacts . count <= 10 else {
return showError ( title : NSLocalizedString ( " A closed group cannot have more than 10 members " , comment : " " ) )
}
2020-01-30 10:09:02 +01:00
let userHexEncodedPublicKey = getUserHexEncodedPublicKey ( )
2020-01-29 00:18:45 +01:00
let members = [ String ] ( selectedContacts ) + [ userHexEncodedPublicKey ]
let admins = [ userHexEncodedPublicKey ]
let groupID = LKGroupUtilities . getEncodedClosedGroupIDAsData ( Randomness . generateRandomBytes ( kGroupIdLength ) ! . toHexString ( ) )
2020-01-30 01:59:12 +01:00
let group = TSGroupModel ( title : name , memberIds : members , image : nil , groupId : groupID , groupType : . closedGroup , adminIds : admins )
2020-01-29 00:18:45 +01:00
let thread = TSGroupThread . getOrCreateThread ( with : group )
OWSProfileManager . shared ( ) . addThread ( toProfileWhitelist : thread )
ModalActivityIndicatorViewController . present ( fromViewController : navigationController ! , canCancel : false ) { [ weak self ] modalActivityIndicator in
let message = TSOutgoingMessage ( in : thread , groupMetaMessage : . new , expiresInSeconds : 0 )
2020-01-29 04:38:52 +01:00
message . update ( withCustomMessage : " Closed group created " )
2020-01-29 00:18:45 +01:00
DispatchQueue . main . async {
SSKEnvironment . shared . messageSender . send ( message , success : {
DispatchQueue . main . async {
self ? . presentingViewController ? . dismiss ( animated : true , completion : nil )
2020-02-18 05:31:20 +01:00
SignalApp . shared ( ) . presentConversation ( for : thread , action : . compose , animated : false )
2020-01-29 00:18:45 +01:00
}
} , failure : { error in
let message = TSErrorMessage ( timestamp : NSDate . ows_millisecondTimeStamp ( ) , in : thread , failedMessageType : . groupCreationFailed )
message . save ( )
DispatchQueue . main . async {
self ? . presentingViewController ? . dismiss ( animated : true , completion : nil )
2020-02-18 05:31:20 +01:00
SignalApp . shared ( ) . presentConversation ( for : thread , action : . compose , animated : false )
2020-01-29 00:18:45 +01:00
}
} )
}
}
2020-01-28 06:27:42 +01:00
}
2020-01-29 03:32:08 +01:00
@objc private func createPrivateChat ( ) {
presentingViewController ? . dismiss ( animated : true , completion : nil )
SignalApp . shared ( ) . homeViewController ! . createPrivateChat ( )
}
2020-01-28 06:27:42 +01:00
}
// MARK: - C e l l
private extension NewClosedGroupVC {
final class Cell : UITableViewCell {
2020-01-29 00:18:45 +01:00
var hexEncodedPublicKey = " " { didSet { update ( ) } }
var hasTick = false { didSet { update ( ) } }
2020-01-28 06:27:42 +01:00
// MARK: C o m p o n e n t s
private lazy var profilePictureView = ProfilePictureView ( )
private lazy var displayNameLabel : UILabel = {
let result = UILabel ( )
result . textColor = Colors . text
result . font = . boldSystemFont ( ofSize : Values . mediumFontSize )
result . lineBreakMode = . byTruncatingTail
return result
} ( )
2020-01-29 00:18:45 +01:00
private lazy var tickImageView : UIImageView = {
let result = UIImageView ( )
result . contentMode = . scaleAspectFit
let size : CGFloat = 24
result . set ( . width , to : size )
result . set ( . height , to : size )
return result
} ( )
private lazy var separator : UIView = {
2020-01-28 06:27:42 +01:00
let result = UIView ( )
result . backgroundColor = Colors . separator
result . set ( . height , to : Values . separatorThickness )
return result
} ( )
// MARK: I n i t i a l i z a t i o n
override init ( style : UITableViewCell . CellStyle , reuseIdentifier : String ? ) {
super . init ( style : style , reuseIdentifier : reuseIdentifier )
setUpViewHierarchy ( )
}
required init ? ( coder : NSCoder ) {
super . init ( coder : coder )
setUpViewHierarchy ( )
}
private func setUpViewHierarchy ( ) {
// S e t t h e c e l l b a c k g r o u n d c o l o r
backgroundColor = Colors . cellBackground
// S e t u p t h e h i g h l i g h t c o l o r
let selectedBackgroundView = UIView ( )
2020-01-30 01:59:12 +01:00
selectedBackgroundView . backgroundColor = . clear // D i s a b l e d f o r n o w
2020-01-28 06:27:42 +01:00
self . selectedBackgroundView = selectedBackgroundView
// S e t u p t h e p r o f i l e p i c t u r e i m a g e v i e w
let profilePictureViewSize = Values . smallProfilePictureSize
profilePictureView . set ( . width , to : profilePictureViewSize )
profilePictureView . set ( . height , to : profilePictureViewSize )
profilePictureView . size = profilePictureViewSize
// S e t u p t h e m a i n s t a c k v i e w
2020-01-29 00:18:45 +01:00
let stackView = UIStackView ( arrangedSubviews : [ profilePictureView , displayNameLabel , tickImageView ] )
2020-01-28 06:27:42 +01:00
stackView . axis = . horizontal
stackView . alignment = . center
stackView . spacing = Values . mediumSpacing
stackView . set ( . height , to : profilePictureViewSize )
contentView . addSubview ( stackView )
stackView . pin ( . leading , to : . leading , of : contentView , withInset : Values . mediumSpacing )
stackView . pin ( . top , to : . top , of : contentView , withInset : Values . mediumSpacing )
contentView . pin ( . bottom , to : . bottom , of : stackView , withInset : Values . mediumSpacing )
stackView . set ( . width , to : UIScreen . main . bounds . width - 2 * Values . mediumSpacing )
// S e t u p t h e s e p a r a t o r
addSubview ( separator )
separator . pin ( . leading , to : . leading , of : self )
separator . pin ( . bottom , to : . bottom , of : self )
2020-01-29 00:18:45 +01:00
separator . set ( . width , to : UIScreen . main . bounds . width )
2020-01-28 06:27:42 +01:00
}
// MARK: U p d a t i n g
private func update ( ) {
profilePictureView . hexEncodedPublicKey = hexEncodedPublicKey
profilePictureView . update ( )
2020-01-30 23:42:36 +01:00
displayNameLabel . text = UserDisplayNameUtilities . getPrivateChatDisplayName ( for : hexEncodedPublicKey ) ? ? " Unknown Contact "
2020-01-29 00:18:45 +01:00
tickImageView . image = hasTick ? # imageLiteral ( resourceName : " CircleCheck " ) : # imageLiteral ( resourceName : " Circle " )
2020-01-28 06:27:42 +01:00
}
}
}