diff --git a/Signal/Images.xcassets/Loki V2/Circle.imageset/Circle.pdf b/Signal/Images.xcassets/Loki V2/Circle.imageset/Circle.pdf
new file mode 100644
index 000000000..254f9a559
Binary files /dev/null and b/Signal/Images.xcassets/Loki V2/Circle.imageset/Circle.pdf differ
diff --git a/Signal/Images.xcassets/Loki V2/Circle.imageset/Contents.json b/Signal/Images.xcassets/Loki V2/Circle.imageset/Contents.json
new file mode 100644
index 000000000..3d338af2b
--- /dev/null
+++ b/Signal/Images.xcassets/Loki V2/Circle.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "Circle.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist
index cb4b80d83..daba681ed 100644
--- a/Signal/Signal-Info.plist
+++ b/Signal/Signal-Info.plist
@@ -7,7 +7,7 @@
CarthageVersion
0.34.0
OSXVersion
- 10.15.1
+ 10.15.2
WebRTCCommit
1445d719bf05280270e9f77576f80f973fd847f8 M73
diff --git a/Signal/src/Loki/Components/ConversationCell.swift b/Signal/src/Loki/Components/ConversationCell.swift
index a7028ccc3..2f2d40de1 100644
--- a/Signal/src/Loki/Components/ConversationCell.swift
+++ b/Signal/src/Loki/Components/ConversationCell.swift
@@ -180,8 +180,8 @@ final class ConversationCell : UITableViewCell {
private func getDisplayName() -> String {
if threadViewModel.isGroupThread {
- if threadViewModel.name.isEmpty {
- return NSLocalizedString("New Group", comment: "")
+ if threadViewModel.name.isEmpty || threadViewModel.name == "New Group" {
+ return DisplayNameUtilities.getDisplayName(for: threadViewModel.threadRecord as! TSGroupThread)
} else {
return threadViewModel.name
}
diff --git a/Signal/src/Loki/Components/ConversationTitleView.swift b/Signal/src/Loki/Components/ConversationTitleView.swift
index ed0877458..c87deedcd 100644
--- a/Signal/src/Loki/Components/ConversationTitleView.swift
+++ b/Signal/src/Loki/Components/ConversationTitleView.swift
@@ -72,8 +72,8 @@ final class ConversationTitleView : UIView {
private func updateTitle() {
let title: String
if thread.isGroupThread() {
- if thread.name().isEmpty {
- title = NSLocalizedString("New Group", comment: "")
+ if thread.name().isEmpty || thread.name() == "New Group" {
+ title = DisplayNameUtilities.getDisplayName(for: thread as! TSGroupThread)
} else {
title = thread.name()
}
diff --git a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift
index 4aa2f2b16..d2b765698 100644
--- a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift
+++ b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift
@@ -1,5 +1,6 @@
final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableViewDelegate {
+ private var selectedContacts: Set = []
private lazy var contacts: [String] = {
var result: [String] = []
@@ -41,6 +42,19 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = Colors.navigationBarBackground
+ // Set up navigation bar buttons
+ 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
+ // Customize title
+ let titleLabel = UILabel()
+ titleLabel.text = NSLocalizedString("New Closed Group", comment: "")
+ titleLabel.textColor = Colors.text
+ titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
+ navigationItem.titleView = titleLabel
// Set up table view
view.addSubview(tableView)
tableView.pin(to: view)
@@ -55,12 +69,54 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
let contact = contacts[indexPath.row]
cell.hexEncodedPublicKey = contact
+ cell.hasTick = selectedContacts.contains(contact)
return cell
}
// MARK: Interaction
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- // TODO: Implement
+ 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() {
+ let userHexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
+ let members = [String](selectedContacts) + [ userHexEncodedPublicKey ]
+ let admins = [ userHexEncodedPublicKey ]
+ let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(Randomness.generateRandomBytes(kGroupIdLength)!.toHexString())
+ let group = TSGroupModel(title: nil, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins)
+ 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)
+ message.update(withCustomMessage: NSLocalizedString("GROUP_CREATED", comment: ""))
+ DispatchQueue.main.async {
+ SSKEnvironment.shared.messageSender.send(message, success: {
+ DispatchQueue.main.async {
+ SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
+ self?.presentingViewController?.dismiss(animated: true, completion: nil)
+ }
+ }, failure: { error in
+ let message = TSErrorMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, failedMessageType: .groupCreationFailed)
+ message.save()
+ DispatchQueue.main.async {
+ SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
+ self?.presentingViewController?.dismiss(animated: true, completion: nil)
+ }
+ })
+ }
+ }
}
}
@@ -69,7 +125,8 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV
private extension NewClosedGroupVC {
final class Cell : UITableViewCell {
- var hexEncodedPublicKey: String = "" { didSet { update() } }
+ var hexEncodedPublicKey = "" { didSet { update() } }
+ var hasTick = false { didSet { update() } }
// MARK: Components
private lazy var profilePictureView = ProfilePictureView()
@@ -82,7 +139,16 @@ private extension NewClosedGroupVC {
return result
}()
- lazy var separator: UIView = {
+ 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 = {
let result = UIView()
result.backgroundColor = Colors.separator
result.set(.height, to: Values.separatorThickness)
@@ -113,7 +179,7 @@ private extension NewClosedGroupVC {
profilePictureView.set(.height, to: profilePictureViewSize)
profilePictureView.size = profilePictureViewSize
// Set up the main stack view
- let stackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ])
+ let stackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel, tickImageView ])
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.mediumSpacing
@@ -121,21 +187,21 @@ private extension NewClosedGroupVC {
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(.trailing, to: .trailing, of: stackView, withInset: Values.mediumSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.mediumSpacing)
stackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing)
// Set up the separator
addSubview(separator)
separator.pin(.leading, to: .leading, of: self)
- separator.pin(.trailing, to: .trailing, of: self)
separator.pin(.bottom, to: .bottom, of: self)
+ separator.set(.width, to: UIScreen.main.bounds.width)
}
// MARK: Updating
private func update() {
- displayNameLabel.text = DisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) ?? "Unknown Contact"
profilePictureView.hexEncodedPublicKey = hexEncodedPublicKey
profilePictureView.update()
+ displayNameLabel.text = DisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) ?? "Unknown Contact"
+ tickImageView.image = hasTick ? #imageLiteral(resourceName: "CircleCheck") : #imageLiteral(resourceName: "Circle")
}
}
}
diff --git a/Signal/src/UserInterface/Notifications/AppNotifications.swift b/Signal/src/UserInterface/Notifications/AppNotifications.swift
index 94083e78a..8ad0c625f 100644
--- a/Signal/src/UserInterface/Notifications/AppNotifications.swift
+++ b/Signal/src/UserInterface/Notifications/AppNotifications.swift
@@ -371,8 +371,8 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
notificationTitle = senderName
case is TSGroupThread:
var groupName = thread.name()
- if groupName.count < 1 {
- groupName = MessageStrings.newGroupDefaultTitle
+ if groupName.count < 1 || groupName == "New Group" {
+ groupName = DisplayNameUtilities.getDisplayName(for: thread as! TSGroupThread)
}
notificationTitle = String(format: NotificationStrings.incomingGroupMessageTitleFormat,
senderName,
diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings
index 7bfe0b557..6c5917f63 100644
--- a/Signal/translations/en.lproj/Localizable.strings
+++ b/Signal/translations/en.lproj/Localizable.strings
@@ -2788,3 +2788,4 @@
"Would you like to restore your session with %@?" = "Would you like to restore your session with %@?";
"Restore" = "Restore";
"Dismiss" = "Dismiss";
+"New Closed Group" = "New Closed Group";
diff --git a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift b/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift
index ff41607f3..262be8037 100644
--- a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift
+++ b/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift
@@ -1,4 +1,6 @@
+// TODO: Rename some of these functions to make the distinctions between them clearer.
+
@objc(LKDisplayNameUtilities)
public final class DisplayNameUtilities : NSObject {
@@ -12,6 +14,7 @@ public final class DisplayNameUtilities : NSObject {
return SSKEnvironment.shared.profileManager.localProfileName()
}
+ // MARK: Sessions
@objc public static func getPrivateChatDisplayName(for hexEncodedPublicKey: String) -> String? {
if hexEncodedPublicKey == userHexEncodedPublicKey {
return userDisplayName
@@ -20,6 +23,19 @@ public final class DisplayNameUtilities : NSObject {
}
}
+ // MARK: Closed Groups
+ @objc public static func getDisplayName(for group: TSGroupThread) -> String {
+ let members = group.groupModel.groupMemberIds
+ let displayNames = members.map { hexEncodedPublicKey -> String in
+ guard let displayName = DisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) else { return hexEncodedPublicKey }
+ let regex = try! NSRegularExpression(pattern: ".* \\(\\.\\.\\.[0-9a-fA-F]*\\)")
+ guard regex.hasMatch(input: displayName) else { return displayName }
+ return String(displayName[displayName.startIndex..<(displayName.index(displayName.endIndex, offsetBy: -14))])
+ }.sorted()
+ return displayNames.joined(separator: ", ")
+ }
+
+ // MARK: Open Groups
@objc public static func getPublicChatDisplayName(for hexEncodedPublicKey: String, in channel: UInt64, on server: String) -> String? {
var result: String?
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in