From 71c7a8df49dc2801db828ac8d8d1a805bf827154 Mon Sep 17 00:00:00 2001 From: Bruno Muniz Azevedo Filho <bruno@elixxir.io> Date: Fri, 22 Jul 2022 20:55:33 -0300 Subject: [PATCH] Finished refactoring ContactList --- .../Controllers/ContactListController.swift | 122 ++++++++++++------ .../ContactListTableController.swift | 83 ------------ .../ViewModels/ContactListViewModel.swift | 15 ++- .../Views/ContactListActionButton.swift | 57 ++++++++ .../Views/ContactListEmptyView.swift | 47 +++++++ .../Views/ContactListItemButton.swift | 60 --------- .../Views/ContactListView.swift | 105 ++++++++------- .../Views/SearchLeftEmptyView.swift | 14 +- .../SearchFeature/Views/SearchLeftView.swift | 2 +- Sources/Shared/AutoGenerated/Strings.swift | 4 +- .../Resources/en.lproj/Localizable.strings | 4 +- 11 files changed, 268 insertions(+), 245 deletions(-) delete mode 100644 Sources/ContactListFeature/Controllers/ContactListTableController.swift create mode 100644 Sources/ContactListFeature/Views/ContactListActionButton.swift create mode 100644 Sources/ContactListFeature/Views/ContactListEmptyView.swift delete mode 100644 Sources/ContactListFeature/Views/ContactListItemButton.swift diff --git a/Sources/ContactListFeature/Controllers/ContactListController.swift b/Sources/ContactListFeature/Controllers/ContactListController.swift index 1e09b937..b0957b62 100644 --- a/Sources/ContactListFeature/Controllers/ContactListController.swift +++ b/Sources/ContactListFeature/Controllers/ContactListController.swift @@ -2,17 +2,18 @@ import UIKit import Theme import Shared import Combine +import XXModels import DependencyInjection public final class ContactListController: UIViewController { - @Dependency private var coordinator: ContactListCoordinating - @Dependency private var statusBarController: StatusBarStyleControlling + @Dependency var coordinator: ContactListCoordinating + @Dependency var statusBarController: StatusBarStyleControlling lazy private var screenView = ContactListView() - lazy private var tableController = ContactListTableController(viewModel) private let viewModel = ContactListViewModel() private var cancellables = Set<AnyCancellable>() + private var dataSource: UICollectionViewDiffableDataSource<Int, Contact>! public override func loadView() { view = screenView @@ -21,16 +22,39 @@ public final class ContactListController: UIViewController { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) statusBarController.style.send(.darkContent) - navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color) + navigationController?.navigationBar.customize( + backgroundColor: Asset.neutralWhite.color + ) } public override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() - setupTableView() + setupCollectionView() setupBindings() } + private func setupCollectionView() { + screenView.collectionView.delegate = self + screenView.collectionView.register(AvatarCell.self) + screenView.collectionView.dataSource = dataSource + + dataSource = UICollectionViewDiffableDataSource<Int, Contact>( + collectionView: screenView.collectionView + ) { collectionView, indexPath, contact in + let cell: AvatarCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) + let name = (contact.nickname ?? contact.username) ?? "Fetching username..." + + cell.set( + image: contact.photo, + h1Text: name, + showSeparator: false + ) + + return cell + } + } + private func setupNavigationBar() { navigationItem.backButtonTitle = " " @@ -49,11 +73,11 @@ public final class ContactListController: UIViewController { customView: UIStackView(arrangedSubviews: [menuButton, titleLabel]) ) - let search = UIButton() - search.tintColor = Asset.neutralActive.color - search.setImage(Asset.contactListSearch.image, for: .normal) - search.addTarget(self, action: #selector(didTapSearch), for: .touchUpInside) - search.accessibilityIdentifier = Localized.Accessibility.ContactList.search + let searchButton = UIButton() + searchButton.tintColor = Asset.neutralActive.color + searchButton.setImage(Asset.contactListSearch.image, for: .normal) + searchButton.addTarget(self, action: #selector(didTapSearch), for: .touchUpInside) + searchButton.accessibilityIdentifier = Localized.Accessibility.ContactList.search let scanButton = UIButton() scanButton.setImage(Asset.sharedScan.image, for: .normal) @@ -62,62 +86,54 @@ public final class ContactListController: UIViewController { let rightStack = UIStackView() rightStack.spacing = 15 rightStack.addArrangedSubview(scanButton) - rightStack.addArrangedSubview(search) + rightStack.addArrangedSubview(searchButton) navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightStack) - search.snp.makeConstraints { $0.width.equalTo(40) } - } - - private func setupTableView() { - addChild(tableController) - screenView.addSubview(tableController.view) - - tableController.view.snp.makeConstraints { make in - make.top.equalTo(screenView.topStackView.snp.bottom) - make.left.bottom.right.equalToSuperview() + searchButton.snp.makeConstraints { + $0.width.equalTo(40) } - - tableController.didMove(toParent: self) } private func setupBindings() { - tableController.didTap - .receive(on: DispatchQueue.main) - .sink { [unowned self] in coordinator.toSingleChat(with: $0, from: self) } - .store(in: &cancellables) - screenView.requestsButton .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { [unowned self] in coordinator.toRequests(from: self) } - .store(in: &cancellables) + .sink { [unowned self] in + coordinator.toRequests(from: self) + }.store(in: &cancellables) screenView.newGroupButton .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { [unowned self] in coordinator.toNewGroup(from: self) } - .store(in: &cancellables) + .sink { [unowned self] in + coordinator.toNewGroup(from: self) + }.store(in: &cancellables) - screenView.searchButton + screenView.emptyView.searchButton .publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { [unowned self] in coordinator.toSearch(from: self) } - .store(in: &cancellables) + .sink { [unowned self] in + coordinator.toSearch(from: self) + }.store(in: &cancellables) viewModel.requestCount .receive(on: DispatchQueue.main) - .sink { [weak screenView] in screenView?.requestsButton.updateNotification($0) } - .store(in: &cancellables) + .sink { [weak screenView] in + screenView?.requestsButton.updateNotification($0) + }.store(in: &cancellables) - viewModel.contacts + viewModel.snapshotPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] in - screenView.stackView.isHidden = !$0.isEmpty + screenView.emptyView.isHidden = $0.numberOfItems > 0 - if $0.isEmpty { - screenView.bringSubviewToFront(screenView.stackView) + if $0.numberOfItems == 0 { + screenView.bringSubviewToFront(screenView.emptyView) } + + let animatingDifferences = dataSource.snapshot().numberOfItems > 0 + dataSource.apply($0, animatingDifferences: animatingDifferences) }.store(in: &cancellables) } @@ -133,3 +149,27 @@ public final class ContactListController: UIViewController { coordinator.toSideMenu(from: self) } } + +extension ContactListController: UICollectionViewDelegate { + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let contact = dataSource.itemIdentifier(for: indexPath) { + coordinator.toSingleChat(with: contact, from: self) + } + } +} + + +//final class ContactListTableController: UITableViewController { +// private var collation = UILocalizedIndexedCollation.current() +// +// override func sectionIndexTitles(for: UITableView) -> [String]? { +// collation.sectionIndexTitles +// } +// +// override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { +// collation.sectionTitles[section] +// } +// +// override func tableView(_: UITableView, sectionForSectionIndexTitle: String, at index: Int) -> Int { +// collation.section(forSectionIndexTitle: index) +// } diff --git a/Sources/ContactListFeature/Controllers/ContactListTableController.swift b/Sources/ContactListFeature/Controllers/ContactListTableController.swift deleted file mode 100644 index b01fa771..00000000 --- a/Sources/ContactListFeature/Controllers/ContactListTableController.swift +++ /dev/null @@ -1,83 +0,0 @@ -import UIKit -import Shared -import Models -import Combine -import XXModels - -final class ContactListTableController: UITableViewController { - private var collation = UILocalizedIndexedCollation.current() - private var sections: [[Contact]] = [] { - didSet { self.tableView.reloadData() } - } - - private let viewModel: ContactListViewModel - private var cancellables = Set<AnyCancellable>() - private let tapRelay = PassthroughSubject<Contact, Never>() - - var didTap: AnyPublisher<Contact, Never> { tapRelay.eraseToAnyPublisher() } - - override func viewDidLoad() { - super.viewDidLoad() - setupTableView() - } - - init(_ viewModel: ContactListViewModel) { - self.viewModel = viewModel - super.init(style: .grouped) - } - - required init?(coder: NSCoder) { nil } - - private func setupTableView() { - tableView.separatorStyle = .none - tableView.register(AvatarCell.self) - tableView.backgroundColor = Asset.neutralWhite.color - tableView.sectionIndexColor = Asset.neutralDark.color - tableView.contentInset = UIEdgeInsets(top: -20, left: 0, bottom: 0, right: 0) - - viewModel.contacts - .receive(on: DispatchQueue.main) - .sink { [unowned self] in - let results = IndexedListCollator().sectioned(items: $0) - self.collation = results.collation - self.sections = results.sections - }.store(in: &cancellables) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: AvatarCell = tableView.dequeueReusableCell(forIndexPath: indexPath) - let contact = sections[indexPath.section][indexPath.row] - let name = (contact.nickname ?? contact.username) ?? "Fetching username..." - - cell.set(image: contact.photo, h1Text: name) - return cell - } - - override func numberOfSections(in: UITableView) -> Int { - sections.count - } - - override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { - sections[section].count - } - - override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { - tapRelay.send(sections[indexPath.section][indexPath.row]) - } - - override func sectionIndexTitles(for: UITableView) -> [String]? { - collation.sectionIndexTitles - } - - override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { - collation.sectionTitles[section] - } - - override func tableView(_: UITableView, sectionForSectionIndexTitle: String, at index: Int) -> Int { - collation.section(forSectionIndexTitle: index) - } - - override func tableView(_: UITableView, heightForRowAt: IndexPath) -> CGFloat { - 64 - } -} diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift index 04674460..30a88501 100644 --- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift +++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift @@ -1,16 +1,25 @@ +import UIKit import Models import Combine import XXModels import Integration import DependencyInjection +typealias ContactListSnapshot = NSDiffableDataSourceSnapshot<Int, Contact> + final class ContactListViewModel { - @Dependency private var session: SessionType + @Dependency var session: SessionType - var contacts: AnyPublisher<[Contact], Never> { + var snapshotPublisher: AnyPublisher<ContactListSnapshot, Never> { session.dbManager.fetchContactsPublisher(.init(authStatus: [.friend])) .assertNoFailure() - .map { $0.filter { $0.id != self.session.myId }} + .map { + let removingMyself = $0.filter { $0.id != self.session.myId } + var snapshot = ContactListSnapshot() + snapshot.appendSections([0]) + snapshot.appendItems(removingMyself, toSection: 0) + return snapshot + } .eraseToAnyPublisher() } diff --git a/Sources/ContactListFeature/Views/ContactListActionButton.swift b/Sources/ContactListFeature/Views/ContactListActionButton.swift new file mode 100644 index 00000000..f63e480d --- /dev/null +++ b/Sources/ContactListFeature/Views/ContactListActionButton.swift @@ -0,0 +1,57 @@ +import UIKit +import Shared + +final class ContactListActionButton: UIControl { + private let titleLabel = UILabel() + private let imageView = UIImageView() + private let stackView = UIStackView() + private let notificationLabel = UILabel() + + init() { + super.init(frame: .zero) + + titleLabel.textColor = Asset.brandPrimary.color + titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0) + + notificationLabel.layer.cornerRadius = 5 + notificationLabel.layer.masksToBounds = true + notificationLabel.textColor = Asset.neutralWhite.color + notificationLabel.backgroundColor = Asset.brandPrimary.color + notificationLabel.font = Fonts.Mulish.black.font(size: 12.0) + + stackView.spacing = 16 + stackView.addArrangedSubview(imageView) + stackView.addArrangedSubview(titleLabel) + stackView.addArrangedSubview(notificationLabel) + stackView.addArrangedSubview(FlexibleSpace()) + stackView.setCustomSpacing(6, after: titleLabel) + stackView.isUserInteractionEnabled = false + + addSubview(stackView) + + setupConstraints() + } + + required init?(coder: NSCoder) { nil } + + func setup(title: String, image: UIImage) { + titleLabel.text = title + imageView.image = image + } + + func updateNotification(_ count: Int) { + notificationLabel.isHidden = count < 1 + notificationLabel.text = " \(count) " // TODO: Use insets (?) for padding + } + + private func setupConstraints() { + imageView.snp.makeConstraints { + $0.width.equalTo(25) + $0.height.equalTo(25) + } + + stackView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } +} diff --git a/Sources/ContactListFeature/Views/ContactListEmptyView.swift b/Sources/ContactListFeature/Views/ContactListEmptyView.swift new file mode 100644 index 00000000..f84e339b --- /dev/null +++ b/Sources/ContactListFeature/Views/ContactListEmptyView.swift @@ -0,0 +1,47 @@ +import UIKit +import Shared + +final class ContactListEmptyView: UIView { + private let titleLabel = UILabel() + private let stackView = UIStackView() + private(set) var searchButton = CapsuleButton() + + init() { + super.init(frame: .zero) + + let paragraph = NSMutableParagraphStyle() + paragraph.lineHeightMultiple = 1.2 + paragraph.alignment = .center + + titleLabel.attributedText = NSAttributedString( + string: Localized.ContactList.Empty.title, + attributes: [ + .paragraphStyle: paragraph, + .foregroundColor: Asset.neutralActive.color, + .font: Fonts.Mulish.bold.font(size: 24.0) as UIFont + ] + ) + + titleLabel.numberOfLines = 0 + searchButton.setStyle(.brandColored) + searchButton.setTitle(Localized.ContactList.Empty.action, for: .normal) + + stackView.spacing = 24 + stackView.axis = .vertical + stackView.alignment = .center + stackView.addArrangedSubview(titleLabel) + stackView.addArrangedSubview(searchButton) + + addSubview(stackView) + + setupConstraints() + } + + required init?(coder: NSCoder) { nil } + + private func setupConstraints() { + stackView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + } +} diff --git a/Sources/ContactListFeature/Views/ContactListItemButton.swift b/Sources/ContactListFeature/Views/ContactListItemButton.swift deleted file mode 100644 index c498f27a..00000000 --- a/Sources/ContactListFeature/Views/ContactListItemButton.swift +++ /dev/null @@ -1,60 +0,0 @@ -import UIKit -import Shared - -final class ItemButton: UIControl { - let titleLabel = UILabel() - let iconImageView = UIImageView() - let separatorView = UIView() - let stackView = UIStackView() - let notificationLabel = UILabel() - - init() { - super.init(frame: .zero) - - titleLabel.textColor = Asset.brandPrimary.color - titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0) - separatorView.backgroundColor = Asset.neutralLine.color - - notificationLabel.isHidden = true - notificationLabel.layer.cornerRadius = 5 - notificationLabel.layer.masksToBounds = true - notificationLabel.textColor = Asset.neutralWhite.color - notificationLabel.backgroundColor = Asset.brandPrimary.color - notificationLabel.font = Fonts.Mulish.bold.font(size: 12.0) - - stackView.spacing = 16 - stackView.addArrangedSubview(iconImageView) - stackView.addArrangedSubview(titleLabel) - stackView.addArrangedSubview(notificationLabel) - stackView.setCustomSpacing(6, after: titleLabel) - - stackView.isUserInteractionEnabled = false - addSubview(stackView) - addSubview(separatorView) - - stackView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(12) - make.left.equalToSuperview().offset(24) - make.bottom.equalTo(separatorView.snp.top).offset(-12) - } - - separatorView.snp.makeConstraints { make in - make.left.equalToSuperview().offset(24) - make.right.equalToSuperview().offset(-24) - make.bottom.equalToSuperview() - make.height.equalTo(1) - } - } - - required init?(coder: NSCoder) { nil } - - func setup(title: String, image: UIImage) { - titleLabel.text = title - iconImageView.image = image - } - - func updateNotification(_ count: Int) { - notificationLabel.isHidden = count < 1 - notificationLabel.text = " \(count) " - } -} diff --git a/Sources/ContactListFeature/Views/ContactListView.swift b/Sources/ContactListFeature/Views/ContactListView.swift index e7a52a71..6411e7ed 100644 --- a/Sources/ContactListFeature/Views/ContactListView.swift +++ b/Sources/ContactListFeature/Views/ContactListView.swift @@ -2,71 +2,76 @@ import UIKit import Shared final class ContactListView: UIView { - let newGroupButton = ItemButton() - let requestsButton = ItemButton() - let topStackView = UIStackView() - let stackView = UIStackView() - let emptyTitleLabel = UILabel() - let searchButton = CapsuleButton() + private let separatorView = UIView() + private(set) var emptyView = ContactListEmptyView() + private(set) var newGroupButton = ContactListActionButton() + private(set) var requestsButton = ContactListActionButton() + + lazy var collectionView: UICollectionView = { + var config = UICollectionLayoutListConfiguration(appearance: .plain) + config.backgroundColor = Asset.neutralWhite.color + config.showsSeparators = false + let layout = UICollectionViewCompositionalLayout.list(using: config) + let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout) + collectionView.contentInset = .init(top: 20, left: 0, bottom: 0, right: 0) + return collectionView + }() init() { super.init(frame: .zero) - setup() - } - - required init?(coder: NSCoder) { nil } - - private func setup() { backgroundColor = Asset.neutralWhite.color + separatorView.backgroundColor = Asset.neutralLine.color - requestsButton.separatorView.isHidden = true - requestsButton.setup(title: "Requests", image: Asset.contactListRequests.image) - newGroupButton.setup(title: Localized.ContactList.newGroup, image: Asset.contactListNewGroup.image) + let requestsTitle = Localized.ContactList.requests + let requestsImage = Asset.contactListRequests.image + requestsButton.setup(title: requestsTitle, image: requestsImage) - let paragraph = NSMutableParagraphStyle() - paragraph.lineHeightMultiple = 1.2 - paragraph.alignment = .center + let newGroupTitle = Localized.ContactList.newGroup + let newGroupImage = Asset.contactListNewGroup.image + newGroupButton.setup(title: newGroupTitle, image: newGroupImage) - emptyTitleLabel.attributedText = NSAttributedString( - string: Localized.ContactList.Empty.title, - attributes: [ - .paragraphStyle: paragraph, - .foregroundColor: Asset.neutralActive.color, - .font: Fonts.Mulish.bold.font(size: 24.0) as UIFont - ] - ) - emptyTitleLabel.numberOfLines = 0 + addSubview(emptyView) + addSubview(separatorView) + addSubview(collectionView) + addSubview(newGroupButton) + addSubview(requestsButton) - searchButton.setStyle(.brandColored) - searchButton.setTitle(Localized.ContactList.Empty.action, for: .normal) + setupConstraints() + } - stackView.spacing = 24 - stackView.axis = .vertical - stackView.alignment = .center - stackView.addArrangedSubview(emptyTitleLabel) - stackView.addArrangedSubview(searchButton) + required init?(coder: NSCoder) { nil } - topStackView.axis = .vertical - topStackView.addArrangedSubview(newGroupButton) - topStackView.addArrangedSubview(requestsButton) + private func setupConstraints() { + newGroupButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(20) + $0.left.equalToSuperview().offset(24) + $0.right.equalToSuperview().offset(-24) + } - addSubview(topStackView) - addSubview(stackView) + separatorView.snp.makeConstraints { + $0.top.equalTo(newGroupButton.snp.bottom).offset(10) + $0.left.equalToSuperview().offset(24) + $0.right.equalToSuperview().offset(-24) + $0.height.equalTo(1) + } - setupConstraints() - } + requestsButton.snp.makeConstraints { + $0.top.equalTo(separatorView.snp.bottom).offset(10) + $0.left.equalToSuperview().offset(24) + $0.right.equalToSuperview().offset(-24) + } - private func setupConstraints() { - topStackView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(20) - make.left.equalToSuperview() - make.right.equalToSuperview() + emptyView.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.left.equalToSuperview().offset(24) + $0.right.equalToSuperview().offset(-24) } - stackView.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.left.equalToSuperview().offset(24) - make.right.equalToSuperview().offset(-24) + collectionView.snp.makeConstraints { + $0.top.equalTo(requestsButton.snp.bottom) + $0.left.equalToSuperview() + $0.right.equalToSuperview() + $0.bottom.equalToSuperview() } } } diff --git a/Sources/SearchFeature/Views/SearchLeftEmptyView.swift b/Sources/SearchFeature/Views/SearchLeftEmptyView.swift index 84c64c87..95b33318 100644 --- a/Sources/SearchFeature/Views/SearchLeftEmptyView.swift +++ b/Sources/SearchFeature/Views/SearchLeftEmptyView.swift @@ -2,7 +2,7 @@ import UIKit import Shared final class SearchLeftEmptyView: UIView { - let titleLabel = UILabel() + private let titleLabel = UILabel() init() { super.init(frame: .zero) @@ -15,12 +15,20 @@ final class SearchLeftEmptyView: UIView { addSubview(titleLabel) + setupConstraints() + } + + required init?(coder: NSCoder) { nil } + + func set(title: String) { + titleLabel.text = title + } + + private func setupConstraints() { titleLabel.snp.makeConstraints { $0.center.equalToSuperview() $0.left.equalToSuperview().offset(20) $0.right.equalToSuperview().offset(-20) } } - - required init?(coder: NSCoder) { nil } } diff --git a/Sources/SearchFeature/Views/SearchLeftView.swift b/Sources/SearchFeature/Views/SearchLeftView.swift index b4d59f3b..53730b9b 100644 --- a/Sources/SearchFeature/Views/SearchLeftView.swift +++ b/Sources/SearchFeature/Views/SearchLeftView.swift @@ -79,7 +79,7 @@ final class SearchLeftView: UIView { countryButton.isHidden = item != .phone let emptyTitle = Localized.Ud.Search.empty(item.written) - emptyView.titleLabel.text = emptyTitle + emptyView.set(title: emptyTitle) let inputFieldTitle = Localized.Ud.Search.input(item.written) inputField.set(placeholder: inputFieldTitle, imageAtRight: nil) diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift index ed3fb2e5..8b178702 100644 --- a/Sources/Shared/AutoGenerated/Strings.swift +++ b/Sources/Shared/AutoGenerated/Strings.swift @@ -542,10 +542,10 @@ public enum Localized { public enum ContactList { /// New Group public static let newGroup = Localized.tr("Localizable", "contactList.newGroup") + /// Requests + public static let requests = Localized.tr("Localizable", "contactList.requests") /// Connections public static let title = Localized.tr("Localizable", "contactList.title") - /// User Search - public static let userSearch = Localized.tr("Localizable", "contactList.userSearch") public enum Empty { /// Add contact public static let action = Localized.tr("Localizable", "contactList.empty.action") diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings index b0dfdda4..d2f7684d 100644 --- a/Sources/Shared/Resources/en.lproj/Localizable.strings +++ b/Sources/Shared/Resources/en.lproj/Localizable.strings @@ -914,8 +914,8 @@ "contactList.newGroup" = "New Group"; -"contactList.userSearch" -= "User Search"; +"contactList.requests" += "Requests"; "contactList.title" = "Connections"; "contactList.empty.title" -- GitLab