mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
13 KiB
Swift
367 lines
13 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import UIKit
|
|
import GRDB
|
|
import SessionUIKit
|
|
import SessionMessagingKit
|
|
import SessionUtilitiesKit
|
|
import SignalUtilitiesKit
|
|
|
|
final class NewConversationVC: BaseVC, ThemedNavigation, UITableViewDelegate, UITableViewDataSource {
|
|
private let viewModel: NewConversationViewModel
|
|
private var groupedContacts: OrderedDictionary<String, [Profile]> = OrderedDictionary()
|
|
|
|
// MARK: - Initialization
|
|
|
|
init(using dependencies: Dependencies) {
|
|
self.viewModel = NewConversationViewModel(using: dependencies)
|
|
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: - UI
|
|
|
|
var navigationBackground: ThemeValue { .newConversation_background }
|
|
|
|
private lazy var newDMButton: NewConversationButton = {
|
|
let result = NewConversationButton(icon: #imageLiteral(resourceName: "Message"), title: "vc_create_private_chat_title".localized())
|
|
result.accessibilityIdentifier = "New direct message"
|
|
result.isAccessibilityElement = true
|
|
|
|
return result
|
|
}()
|
|
|
|
private lazy var newGroupButton: NewConversationButton = {
|
|
let result = NewConversationButton(icon: #imageLiteral(resourceName: "Group"), title: "vc_create_closed_group_title".localized())
|
|
result.accessibilityLabel = "Create group"
|
|
result.isAccessibilityElement = true
|
|
|
|
return result
|
|
}()
|
|
private lazy var joinCommunityButton: NewConversationButton = NewConversationButton(icon: #imageLiteral(resourceName: "Globe"), title: "vc_join_public_chat_title".localized(), shouldShowSeparator: false)
|
|
|
|
private lazy var buttonStackView: UIStackView = {
|
|
let lineTop: UIView = UIView()
|
|
lineTop.themeBackgroundColor = .borderSeparator
|
|
lineTop.set(.height, to: Values.separatorThickness)
|
|
|
|
let lineBottom = UIView()
|
|
lineBottom.themeBackgroundColor = .borderSeparator
|
|
lineBottom.set(.height, to: Values.separatorThickness)
|
|
|
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
|
tapGestureRecognizer.numberOfTapsRequired = 1
|
|
|
|
let result = UIStackView(
|
|
arrangedSubviews: [
|
|
lineTop,
|
|
newDMButton,
|
|
newGroupButton,
|
|
joinCommunityButton,
|
|
lineBottom
|
|
]
|
|
)
|
|
result.axis = .vertical
|
|
result.addGestureRecognizer(tapGestureRecognizer)
|
|
|
|
return result
|
|
}()
|
|
|
|
private lazy var buttonStackViewContainer = UIView(wrapping: buttonStackView, withInsets: .zero)
|
|
|
|
private lazy var contactsTitleLabel: UILabel = {
|
|
let result: UILabel = UILabel()
|
|
result.font = .systemFont(ofSize: Values.mediumFontSize)
|
|
result.text = (viewModel.sectionData.isEmpty ?
|
|
"vc_create_closed_group_empty_state_message".localized() :
|
|
"NEW_CONVERSATION_CONTACTS_SECTION_TITLE".localized()
|
|
)
|
|
result.themeTextColor = (viewModel.sectionData.isEmpty ?
|
|
.textSecondary :
|
|
.textPrimary
|
|
)
|
|
|
|
return result
|
|
}()
|
|
|
|
private lazy var contactsTableView: UITableView = {
|
|
let result: UITableView = UITableView()
|
|
result.delegate = self
|
|
result.dataSource = self
|
|
result.separatorStyle = .none
|
|
result.themeBackgroundColor = .newConversation_background
|
|
result.register(view: SessionCell.self)
|
|
|
|
if #available(iOS 15.0, *) {
|
|
result.sectionHeaderTopPadding = 0
|
|
}
|
|
|
|
return result
|
|
}()
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
setNavBarTitle("vc_new_conversation_title".localized())
|
|
view.themeBackgroundColor = .newConversation_background
|
|
|
|
// Set up navigation bar buttons
|
|
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
|
|
closeButton.themeTintColor = .textPrimary
|
|
navigationItem.leftBarButtonItem = closeButton
|
|
setUpViewHierarchy()
|
|
}
|
|
|
|
private func setUpViewHierarchy() {
|
|
buttonStackViewContainer.themeBackgroundColor = .newConversation_background
|
|
|
|
let headerView = UIView(
|
|
frame: CGRect(
|
|
x: 0, y: 0,
|
|
width: UIScreen.main.bounds.width,
|
|
height: NewConversationButton.height * 3 + Values.mediumSpacing * 2 + Values.mediumFontSize
|
|
)
|
|
)
|
|
headerView.addSubview(buttonStackViewContainer)
|
|
buttonStackViewContainer.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing], to: headerView)
|
|
buttonStackViewContainer.pin(.top, to: .top, of: headerView, withInset: Values.verySmallSpacing)
|
|
headerView.addSubview(contactsTitleLabel)
|
|
contactsTitleLabel.pin(.leading, to: .leading, of: headerView, withInset: Values.mediumSpacing)
|
|
contactsTitleLabel.pin(.top, to: .bottom, of: buttonStackViewContainer, withInset: Values.mediumSpacing)
|
|
|
|
contactsTableView.tableHeaderView = headerView
|
|
view.addSubview(contactsTableView)
|
|
contactsTableView.pin(to: view)
|
|
}
|
|
|
|
// MARK: - UITableViewDataSource
|
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
|
return viewModel.sectionData.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
return viewModel.sectionData[section].contacts.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cell: SessionCell = tableView.dequeue(type: SessionCell.self, for: indexPath)
|
|
let profile = viewModel.sectionData[indexPath.section].contacts[indexPath.row]
|
|
cell.update(
|
|
with: SessionCell.Info(
|
|
id: profile,
|
|
position: Position.with(
|
|
indexPath.row,
|
|
count: viewModel.sectionData[indexPath.section].contacts.count
|
|
),
|
|
leadingAccessory: .profile(id: profile.id, profile: profile),
|
|
title: profile.displayName(),
|
|
styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge)
|
|
),
|
|
using: viewModel.dependencies
|
|
)
|
|
|
|
return cell
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
let label: UILabel = UILabel()
|
|
label.font = .systemFont(ofSize: Values.smallFontSize)
|
|
label.text = viewModel.sectionData[section].sectionName
|
|
label.themeTextColor = .textPrimary
|
|
|
|
let headerView: UIView = UIView()
|
|
headerView.themeBackgroundColor = .newConversation_background
|
|
headerView.addSubview(label)
|
|
|
|
label.pin(.leading, to: .leading, of: headerView, withInset: Values.mediumSpacing)
|
|
label.pin(.top, to: .top, of: headerView, withInset: Values.verySmallSpacing)
|
|
label.pin(.bottom, to: .bottom, of: headerView, withInset: -Values.verySmallSpacing)
|
|
|
|
return headerView
|
|
}
|
|
|
|
// MARK: - UITableViewDelegate
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
let sessionId = viewModel.sectionData[indexPath.section].contacts[indexPath.row].id
|
|
|
|
viewModel.dependencies[singleton: .app].presentConversationCreatingIfNeeded(
|
|
for: sessionId,
|
|
variant: .contact,
|
|
dismissing: navigationController,
|
|
animated: false
|
|
)
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
|
|
view.themeBackgroundColor = .newConversation_background
|
|
}
|
|
|
|
// MARK: - Interaction
|
|
|
|
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
|
let location = gestureRecognizer.location(in: self.view)
|
|
|
|
if newDMButton.frame.contains(location) {
|
|
createNewDM()
|
|
}
|
|
else if newGroupButton.frame.contains(location) {
|
|
createClosedGroup()
|
|
}
|
|
else if joinCommunityButton.frame.contains(location) {
|
|
joinOpenGroup()
|
|
}
|
|
}
|
|
|
|
@objc private func close() {
|
|
dismiss(animated: true, completion: nil)
|
|
}
|
|
|
|
@objc func createNewDM() {
|
|
let newDMVC = NewDMVC(using: viewModel.dependencies)
|
|
self.navigationController?.pushViewController(newDMVC, animated: true)
|
|
}
|
|
|
|
@objc func createClosedGroup() {
|
|
let newClosedGroupVC = NewClosedGroupVC(using: viewModel.dependencies)
|
|
self.navigationController?.pushViewController(newClosedGroupVC, animated: true)
|
|
}
|
|
|
|
@objc func joinOpenGroup() {
|
|
let joinOpenGroupVC: JoinOpenGroupVC = JoinOpenGroupVC(using: viewModel.dependencies)
|
|
self.navigationController?.pushViewController(joinOpenGroupVC, animated: true)
|
|
}
|
|
}
|
|
|
|
// MARK: - NewConversationButton
|
|
|
|
private final class NewConversationButton: UIView {
|
|
private let icon: UIImage
|
|
private let title: String
|
|
private let shouldShowSeparator: Bool
|
|
private var didTouchDownInside: Bool = false
|
|
|
|
public static let height: CGFloat = 56
|
|
private static let iconSize: CGFloat = 38
|
|
|
|
private let selectedBackgroundView: UIView = {
|
|
let result: UIView = UIView()
|
|
result.themeBackgroundColor = .highlighted(.settings_tabBackground)
|
|
result.isHidden = true
|
|
|
|
return result
|
|
}()
|
|
|
|
init(icon: UIImage, title: String, shouldShowSeparator: Bool = true) {
|
|
self.icon = icon.withRenderingMode(.alwaysTemplate)
|
|
self.title = title
|
|
self.shouldShowSeparator = shouldShowSeparator
|
|
|
|
super.init(frame: .zero)
|
|
|
|
self.themeBackgroundColor = .settings_tabBackground
|
|
setUpViewHierarchy()
|
|
}
|
|
|
|
override init(frame: CGRect) {
|
|
preconditionFailure("Use init(icon:title:) instead.")
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
preconditionFailure("Use init(icon:title:) instead.")
|
|
}
|
|
|
|
private func setUpViewHierarchy() {
|
|
addSubview(selectedBackgroundView)
|
|
selectedBackgroundView.pin(to: self)
|
|
|
|
let iconImageView = UIImageView(image: self.icon)
|
|
iconImageView.contentMode = .center
|
|
iconImageView.themeTintColor = .textPrimary
|
|
iconImageView.set(.width, to: NewConversationButton.iconSize)
|
|
|
|
let titleLable: UILabel = UILabel()
|
|
titleLable.font = .systemFont(ofSize: Values.mediumFontSize)
|
|
titleLable.text = self.title
|
|
titleLable.themeTextColor = .textPrimary
|
|
|
|
let stackView = UIStackView(
|
|
arrangedSubviews: [
|
|
iconImageView,
|
|
UIView.hSpacer(Values.mediumSpacing),
|
|
titleLable,
|
|
UIView.hStretchingSpacer()
|
|
]
|
|
)
|
|
stackView.axis = .horizontal
|
|
stackView.alignment = .center
|
|
stackView.isLayoutMarginsRelativeArrangement = true
|
|
stackView.layoutMargins = UIEdgeInsets(uniform: Values.mediumSpacing)
|
|
addSubview(stackView)
|
|
stackView.pin(to: self)
|
|
stackView.set(.width, to: UIScreen.main.bounds.width)
|
|
stackView.set(.height, to: NewConversationButton.height)
|
|
|
|
let line: UIView = UIView()
|
|
line.themeBackgroundColor = .borderSeparator
|
|
addSubview(line)
|
|
|
|
line.pin([UIView.VerticalEdge.bottom, UIView.HorizontalEdge.trailing], to: self)
|
|
line.pin(
|
|
.leading,
|
|
to: .leading,
|
|
of: self,
|
|
withInset: (NewConversationButton.iconSize + 2 * Values.mediumSpacing)
|
|
)
|
|
line.set(.height, to: Values.separatorThickness)
|
|
|
|
line.isHidden = !shouldShowSeparator
|
|
}
|
|
|
|
// MARK: - Interaction
|
|
|
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
guard
|
|
isUserInteractionEnabled,
|
|
let location: CGPoint = touches.first?.location(in: self),
|
|
bounds.contains(location)
|
|
else { return }
|
|
|
|
didTouchDownInside = true
|
|
selectedBackgroundView.isHidden = false
|
|
}
|
|
|
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
guard
|
|
isUserInteractionEnabled,
|
|
let location: CGPoint = touches.first?.location(in: self),
|
|
bounds.contains(location),
|
|
didTouchDownInside
|
|
else {
|
|
selectedBackgroundView.isHidden = true
|
|
return
|
|
}
|
|
|
|
selectedBackgroundView.isHidden = false
|
|
}
|
|
|
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
selectedBackgroundView.isHidden = true
|
|
didTouchDownInside = false
|
|
}
|
|
|
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
selectedBackgroundView.isHidden = true
|
|
didTouchDownInside = false
|
|
}
|
|
}
|