diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 3dfd0f5f7..e08f24e50 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -590,6 +590,7 @@ B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; }; B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; }; B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; }; + B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */; }; B891105C2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; B891105E2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; B891105F2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; @@ -1434,6 +1435,7 @@ B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; B886B4A62398B23E00211ABE /* QRCodeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeVC.swift; sourceTree = ""; }; B886B4A82398BA1500211ABE /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; + B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersVC.swift; sourceTree = ""; }; B891105B2320872800F15FCC /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = ""; }; B894D0702339D6F300B4D94D /* DeviceLinkingModalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModalDelegate.swift; sourceTree = ""; }; @@ -2822,6 +2824,7 @@ B85357C623A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */, + B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */, B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */, B894D0742339EDCF00B4D94D /* NukeDataModal.swift */, B886B4A62398B23E00211ABE /* QRCodeVC.swift */, @@ -4066,6 +4069,7 @@ 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */, 34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */, 340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */, + B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */, B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */, B85357C723A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift in Sources */, 340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */, diff --git a/Signal/src/Loki/View Controllers/GroupMembersVC.swift b/Signal/src/Loki/View Controllers/GroupMembersVC.swift new file mode 100644 index 000000000..876ffb185 --- /dev/null +++ b/Signal/src/Loki/View Controllers/GroupMembersVC.swift @@ -0,0 +1,145 @@ + +@objc(LKGroupMembersVC) +final class GroupMembersVC : UIViewController, UITableViewDataSource { + private let thread: TSGroupThread + + private lazy var members: [String] = { + func getDisplayName(for hexEncodedPublicKey: String) -> String { + return DisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) ?? "Unknown Contact" + } + return thread.groupModel.groupMemberIds.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } + }() + + // MARK: Components + @objc private lazy var tableView: UITableView = { + let result = UITableView() + result.dataSource = self + result.register(Cell.self, forCellReuseIdentifier: "Cell") + result.separatorStyle = .none + result.backgroundColor = .clear + result.showsVerticalScrollIndicator = false + return result + }() + + // MARK: Lifecycle + @objc init(thread: TSGroupThread) { + self.thread = thread + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { fatalError("Using GroupMembersVC.init(nibName:bundle:) isn't allowed. Use GroupMembersVC.init(thread:) instead.") } + override init(nibName: String?, bundle: Bundle?) { fatalError("Using GroupMembersVC.init(nibName:bundle:) isn't allowed. Use GroupMembersVC.init(thread:) instead.") } + + override func viewDidLoad() { + // Set gradient background + view.backgroundColor = .clear + let gradient = Gradients.defaultLokiBackground + view.setGradient(gradient) + // Set navigation bar background color + let navigationBar = navigationController!.navigationBar + navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) + navigationBar.shadowImage = UIImage() + navigationBar.isTranslucent = false + navigationBar.barTintColor = Colors.navigationBarBackground + // Customize title + let titleLabel = UILabel() + titleLabel.text = NSLocalizedString("Group Members", 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) + } + + // MARK: Data + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return members.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell + let contact = members[indexPath.row] + cell.hexEncodedPublicKey = contact + return cell + } + + @objc private func close() { + dismiss(animated: true, completion: nil) + } +} + +// MARK: - Cell + +private extension GroupMembersVC { + + final class Cell : UITableViewCell { + var hexEncodedPublicKey = "" { didSet { update() } } + + // MARK: Components + 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 + }() + + private lazy var separator: UIView = { + let result = UIView() + result.backgroundColor = Colors.separator + result.set(.height, to: Values.separatorThickness) + return result + }() + + // MARK: Initialization + 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() { + // Set the cell background color + backgroundColor = Colors.cellBackground + // Set up the highlight color + let selectedBackgroundView = UIView() + selectedBackgroundView.backgroundColor = .clear // Disabled for now + self.selectedBackgroundView = selectedBackgroundView + // Set up the profile picture image view + let profilePictureViewSize = Values.smallProfilePictureSize + profilePictureView.set(.width, to: profilePictureViewSize) + profilePictureView.set(.height, to: profilePictureViewSize) + profilePictureView.size = profilePictureViewSize + // Set up the main stack view + let stackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ]) + 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) + // Set up the separator + addSubview(separator) + separator.pin(.leading, to: .leading, of: self) + separator.pin(.bottom, to: .bottom, of: self) + separator.set(.width, to: UIScreen.main.bounds.width) + } + + // MARK: Updating + private func update() { + profilePictureView.hexEncodedPublicKey = hexEncodedPublicKey + profilePictureView.update() + displayNameLabel.text = DisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) ?? "Unknown Contact" + } + } +} diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m index e9abcd3e1..d8a39202c 100644 --- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m @@ -634,55 +634,51 @@ const CGFloat kIconViewLength = 24; if (self.isGroupThread && self.isPrivateGroupChat) { - NSArray *groupItems = @[ - [OWSTableItem - itemWithCustomCellBlock:^{ - UITableViewCell *cell = - [weakSelf disclosureCellWithName:NSLocalizedString(@"EDIT_GROUP_ACTION", - @"table cell label in conversation settings") - iconName:@"table_ic_group_edit" - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( - OWSConversationSettingsViewController, @"edit_group")]; - cell.userInteractionEnabled = !weakSelf.hasLeftGroup; - return cell; - } - actionBlock:^{ - [weakSelf showUpdateGroupView:UpdateGroupMode_Default]; - }], - [OWSTableItem - itemWithCustomCellBlock:^{ - UITableViewCell *cell = - [weakSelf disclosureCellWithName:NSLocalizedString(@"LIST_GROUP_MEMBERS_ACTION", - @"table cell label in conversation settings") - iconName:@"table_ic_group_members" - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( - OWSConversationSettingsViewController, @"group_members")]; - cell.userInteractionEnabled = !weakSelf.hasLeftGroup; - return cell; - } - actionBlock:^{ - [weakSelf showGroupMembersView]; - }], - [OWSTableItem - itemWithCustomCellBlock:^{ - UITableViewCell *cell = - [weakSelf disclosureCellWithName:NSLocalizedString(@"LEAVE_GROUP_ACTION", - @"table cell label in conversation settings") - iconName:@"table_ic_group_leave" - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( - OWSConversationSettingsViewController, @"leave_group")]; - cell.userInteractionEnabled = !weakSelf.hasLeftGroup; - - return cell; - } - actionBlock:^{ - [weakSelf didTapLeaveGroup]; - }], +// [OWSTableItem +// itemWithCustomCellBlock:^{ +// UITableViewCell *cell = +// [weakSelf disclosureCellWithName:NSLocalizedString(@"EDIT_GROUP_ACTION", +// @"table cell label in conversation settings") +// iconName:@"table_ic_group_edit" +// accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( +// OWSConversationSettingsViewController, @"edit_group")]; +// cell.userInteractionEnabled = !weakSelf.hasLeftGroup; +// return cell; +// } +// actionBlock:^{ +// [weakSelf showUpdateGroupView:UpdateGroupMode_Default]; +// }], + [mainSection addItem:[OWSTableItem + itemWithCustomCellBlock:^{ + UITableViewCell *cell = + [weakSelf disclosureCellWithName:NSLocalizedString(@"LIST_GROUP_MEMBERS_ACTION", + @"table cell label in conversation settings") + iconName:@"table_ic_group_members" + accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( + OWSConversationSettingsViewController, @"group_members")]; + cell.userInteractionEnabled = !weakSelf.hasLeftGroup; + return cell; + } + actionBlock:^{ + [weakSelf showGroupMembersView]; + }] ]; + [mainSection addItem:[OWSTableItem + itemWithCustomCellBlock:^{ + UITableViewCell *cell = + [weakSelf disclosureCellWithName:NSLocalizedString(@"LEAVE_GROUP_ACTION", + @"table cell label in conversation settings") + iconName:@"table_ic_group_leave" + accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME( + OWSConversationSettingsViewController, @"leave_group")]; + cell.userInteractionEnabled = !weakSelf.hasLeftGroup; - [contents addSection:[OWSTableSection sectionWithTitle:NSLocalizedString(@"GROUP_MANAGEMENT_SECTION", - @"Conversation settings table section title") - items:groupItems]]; + return cell; + } + actionBlock:^{ + [weakSelf didTapLeaveGroup]; + }] + ]; } @@ -1083,10 +1079,9 @@ const CGFloat kIconViewLength = 24; - (void)showGroupMembersView { - TSGroupThread *groupThread = (TSGroupThread *)self.thread; - ShowGroupMembersViewController *showGroupMembersViewController = [ShowGroupMembersViewController new]; - [showGroupMembersViewController configWithThread:groupThread]; - [self.navigationController pushViewController:showGroupMembersViewController animated:YES]; + TSGroupThread *thread = (TSGroupThread *)self.thread; + LKGroupMembersVC *groupMembersVC = [[LKGroupMembersVC alloc] initWithThread:thread]; + [self.navigationController pushViewController:groupMembersVC animated:YES]; } - (void)showUpdateGroupView:(UpdateGroupMode)mode diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 6c5917f63..6b4786412 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2789,3 +2789,4 @@ "Restore" = "Restore"; "Dismiss" = "Dismiss"; "New Closed Group" = "New Closed Group"; +"Group Members" = "Group Members";