session-ios/Session/Closed Groups/NewClosedGroupVC.swift
Morgan Pretty ea32e407a9 Applied theming to a number of screens, some minor cleanup and bug fixes
Updated the HomeVC, SettingsVC and GlobalSearch UI to use theming
Removed the "fade view" gradients from the various screens
Added a simple log to the PagedDatabaseObserver to make debugging easier
Updated the FullConversationCell to also show the "read" state for messages
Updated the read receipt icons to use SFSymbols directly
Updated the PlaceholderIcon to use the PrimaryColour's as it's colour options
2022-08-12 17:28:00 +10:00

227 lines
10 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import GRDB
import PromiseKit
import SessionUIKit
import SessionMessagingKit
private protocol TableViewTouchDelegate {
func tableViewWasTouched(_ tableView: TableView)
}
private final class TableView: UITableView {
var touchDelegate: TableViewTouchDelegate?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
touchDelegate?.tableViewWasTouched(self)
return super.hitTest(point, with: event)
}
}
final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate, TableViewTouchDelegate, UITextFieldDelegate, UIScrollViewDelegate {
private let contactProfiles: [Profile] = Profile.fetchAllContactProfiles(excludeCurrentUser: true)
private var selectedContacts: Set<String> = []
// MARK: - Components
private lazy var nameTextField = TextField(placeholder: "vc_create_closed_group_text_field_hint".localized())
private lazy var tableView: TableView = {
let result: TableView = TableView()
result.dataSource = self
result.delegate = self
result.touchDelegate = self
result.separatorStyle = .none
result.backgroundColor = .clear
result.isScrollEnabled = false
result.register(view: UserCell.self)
return result
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let customTitleFontSize = Values.largeFontSize
setNavBarTitle("vc_create_closed_group_title".localized(), customFontSize: customTitleFontSize)
// 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
// Set up content
setUpViewHierarchy()
}
private func setUpViewHierarchy() {
guard !contactProfiles.isEmpty else {
let explanationLabel: UILabel = UILabel()
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = "vc_create_closed_group_empty_state_message".localized()
explanationLabel.textColor = Colors.text
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.numberOfLines = 0
let createNewPrivateChatButton: OutlineButton = OutlineButton(style: .regular, size: .medium)
createNewPrivateChatButton.setTitle("vc_create_closed_group_empty_state_button_title".localized(), for: .normal)
createNewPrivateChatButton.addTarget(self, action: #selector(createNewDM), for: .touchUpInside)
createNewPrivateChatButton.set(.width, to: 196)
let stackView: UIStackView = 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 // Makes things appear centered visually
return
}
let mainStackView: UIStackView = UIStackView()
mainStackView.axis = .vertical
nameTextField.delegate = self
let nameTextFieldContainer: UIView = UIView()
nameTextFieldContainer.addSubview(nameTextField)
nameTextField.pin(.leading, to: .leading, of: nameTextFieldContainer, withInset: Values.largeSpacing)
nameTextField.pin(.top, to: .top, of: nameTextFieldContainer, withInset: Values.mediumSpacing)
nameTextFieldContainer.pin(.trailing, to: .trailing, of: nameTextField, withInset: Values.largeSpacing)
nameTextFieldContainer.pin(.bottom, to: .bottom, of: nameTextField, withInset: Values.largeSpacing)
mainStackView.addArrangedSubview(nameTextFieldContainer)
let separator: UIView = UIView()
separator.backgroundColor = Colors.separator
separator.set(.height, to: Values.separatorThickness)
mainStackView.addArrangedSubview(separator)
tableView.set(.height, to: CGFloat(contactProfiles.count * 65)) // A cell is exactly 65 points high
tableView.set(.width, to: UIScreen.main.bounds.width)
mainStackView.addArrangedSubview(tableView)
let scrollView: UIScrollView = UIScrollView(wrapping: mainStackView, withInsets: UIEdgeInsets.zero)
scrollView.showsVerticalScrollIndicator = false
scrollView.delegate = self
view.addSubview(scrollView)
scrollView.set(.width, to: UIScreen.main.bounds.width)
scrollView.pin(to: view)
}
// MARK: - Table View Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contactProfiles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UserCell = tableView.dequeue(type: UserCell.self, for: indexPath)
cell.update(
with: contactProfiles[indexPath.row].id,
profile: contactProfiles[indexPath.row],
isZombie: false,
accessory: .tick(isSelected: selectedContacts.contains(contactProfiles[indexPath.row].id))
)
return cell
}
// MARK: - Interaction
func textFieldDidEndEditing(_ textField: UITextField) {
crossfadeLabel.text = textField.text!.isEmpty ? NSLocalizedString("vc_create_closed_group_title", comment: "") : textField.text!
}
fileprivate func tableViewWasTouched(_ tableView: TableView) {
if nameTextField.isFirstResponder {
nameTextField.resignFirstResponder()
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let nameTextFieldCenterY = nameTextField.convert(nameTextField.bounds.center, to: scrollView).y
let tableViewOriginY = tableView.convert(tableView.bounds.origin, to: scrollView).y
let titleLabelAlpha = 1 - (scrollView.contentOffset.y - nameTextFieldCenterY) / (tableViewOriginY - nameTextFieldCenterY)
let crossfadeLabelAlpha = 1 - titleLabelAlpha
navBarTitleLabel.alpha = titleLabelAlpha
crossfadeLabel.alpha = crossfadeLabelAlpha
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if !selectedContacts.contains(contactProfiles[indexPath.row].id) {
selectedContacts.insert(contactProfiles[indexPath.row].id)
}
else {
selectedContacts.remove(contactProfiles[indexPath.row].id)
}
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadRows(at: [indexPath], with: .none)
}
@objc private func close() {
dismiss(animated: true, completion: nil)
}
@objc private func createClosedGroup() {
func showError(title: String, message: String = "") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
presentAlert(alert)
}
guard let name = nameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), name.count > 0 else {
return showError(title: NSLocalizedString("vc_create_closed_group_group_name_missing_error", comment: ""))
}
guard name.count < 64 else {
return showError(title: NSLocalizedString("vc_create_closed_group_group_name_too_long_error", comment: ""))
}
guard selectedContacts.count >= 1 else {
return showError(title: "Please pick at least 1 group member")
}
guard selectedContacts.count < 100 else { // Minus one because we're going to include self later
return showError(title: NSLocalizedString("vc_create_closed_group_too_many_group_members_error", comment: ""))
}
let selectedContacts = self.selectedContacts
let message: String? = (selectedContacts.count > 20) ? "Please wait while the group is created..." : nil
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, message: message) { [weak self] _ in
Storage.shared
.writeAsync { db in
try MessageSender.createClosedGroup(db, name: name, members: selectedContacts)
}
.done(on: DispatchQueue.main) { thread in
Storage.shared.writeAsync { db in
try? MessageSender.syncConfiguration(db, forceSyncNow: true).retainUntilComplete()
}
self?.presentingViewController?.dismiss(animated: true, completion: nil)
SessionApp.presentConversation(for: thread.id, action: .compose, animated: false)
}
.catch(on: DispatchQueue.main) { [weak self] _ in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
let title = "Couldn't Create Group"
let message = "Please check your internet connection and try again."
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil))
self?.presentAlert(alert)
}
.retainUntilComplete()
}
}
@objc private func createNewDM() {
presentingViewController?.dismiss(animated: true, completion: nil)
SessionApp.homeViewController.wrappedValue?.createNewDM()
}
}