From 790bbfab6e845915036522d59b1e8817dc4d8fac Mon Sep 17 00:00:00 2001 From: Bruno Muniz Azevedo Filho <bruno@elixxir.io> Date: Tue, 26 Jul 2022 15:35:56 -0300 Subject: [PATCH] Fixed some points from the mr, worked on drawer list items and requests flow --- .../Controller/ChatListController.swift | 4 +- .../ViewModel/ChatListViewModel.swift | 7 +- .../Controllers/CreateGroupController.swift | 18 +-- .../Views/ContactListActionButton.swift | 26 +++- .../Views/ContactListView.swift | 6 +- .../Views/CreateGroupView.swift | 2 - Sources/Countries/CountryListCell.swift | 48 +++--- Sources/Countries/CountryListController.swift | 63 ++++---- Sources/Countries/CountryListView.swift | 45 +++--- Sources/Countries/CountryListViewModel.swift | 72 ++++----- Sources/DrawerFeature/Items/DrawerList.swift | 68 ++++++++ .../DrawerFeature/Items/DrawerListCell.swift | 77 +++++++++ .../Items/DrawerListCellModel.swift | 23 +++ Sources/DrawerFeature/Items/DrawerTable.swift | 146 ------------------ .../RequestsReceivedController.swift | 2 +- .../RequestsReceivedViewModel.swift | 6 +- .../Views/RequestsReceivedView.swift | 1 - .../Controllers/SearchLeftController.swift | 1 - .../ViewModels/SearchLeftViewModel.swift | 1 - .../SearchFeature/Views/SearchLeftView.swift | 1 - .../Controllers/DiffEditableDataSource.swift | 13 -- Sources/Shared/EditStateHandler.swift | 18 --- Sources/Shared/Views/AvatarCell.swift | 15 +- 23 files changed, 334 insertions(+), 329 deletions(-) create mode 100644 Sources/DrawerFeature/Items/DrawerList.swift create mode 100644 Sources/DrawerFeature/Items/DrawerListCell.swift create mode 100644 Sources/DrawerFeature/Items/DrawerListCellModel.swift delete mode 100644 Sources/DrawerFeature/Items/DrawerTable.swift delete mode 100644 Sources/Shared/Controllers/DiffEditableDataSource.swift delete mode 100644 Sources/Shared/EditStateHandler.swift diff --git a/Sources/ChatListFeature/Controller/ChatListController.swift b/Sources/ChatListFeature/Controller/ChatListController.swift index bddccd28..180c3798 100644 --- a/Sources/ChatListFeature/Controller/ChatListController.swift +++ b/Sources/ChatListFeature/Controller/ChatListController.swift @@ -16,7 +16,7 @@ public final class ChatListController: UIViewController { lazy private var topRightView = ChatListTopRightNavView() lazy private var tableController = ChatListTableController(viewModel) lazy private var searchTableController = ChatSearchTableController(viewModel) - private var collectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>! + private var collectionDataSource: UICollectionViewDiffableDataSource<Int, Contact>! private let viewModel = ChatListViewModel() private var cancellables = Set<AnyCancellable>() @@ -112,7 +112,7 @@ public final class ChatListController: UIViewController { .collectionView .register(ChatListRecentContactCell.self) - collectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>( + collectionDataSource = UICollectionViewDiffableDataSource<Int, Contact>( collectionView: screenView.listContainerView.collectionView ) { collectionView, indexPath, contact in let cell: ChatListRecentContactCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift index f481b96f..852901a4 100644 --- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift +++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift @@ -18,7 +18,7 @@ enum SearchItem: Equatable, Hashable { case connection(Contact) } -typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, Contact> +typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<Int, Contact> typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem> final class ChatListViewModel { @@ -40,10 +40,9 @@ final class ChatListViewModel { session.dbManager.fetchContactsPublisher(.init(isRecent: true)) .assertNoFailure() .map { - let section = SectionId() var snapshot = RecentsSnapshot() - snapshot.appendSections([section]) - snapshot.appendItems($0, toSection: section) + snapshot.appendSections([0]) + snapshot.appendItems($0, toSection: 0) return snapshot }.eraseToAnyPublisher() } diff --git a/Sources/ContactListFeature/Controllers/CreateGroupController.swift b/Sources/ContactListFeature/Controllers/CreateGroupController.swift index fdca1638..a8c75d45 100644 --- a/Sources/ContactListFeature/Controllers/CreateGroupController.swift +++ b/Sources/ContactListFeature/Controllers/CreateGroupController.swift @@ -19,8 +19,8 @@ public final class CreateGroupController: UIViewController { } private let viewModel = CreateGroupViewModel() private var cancellables = Set<AnyCancellable>() - private var topCollectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>! - private var bottomCollectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>! + private var topCollectionDataSource: UICollectionViewDiffableDataSource<Int, Contact>! + private var bottomCollectionDataSource: UICollectionViewDiffableDataSource<Int, Contact>! private var count = 0 { didSet { @@ -68,7 +68,7 @@ public final class CreateGroupController: UIViewController { screenView.bottomCollectionView.register(AvatarCell.self) screenView.topCollectionView.register(CreateGroupCollectionCell.self) - topCollectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>( + topCollectionDataSource = UICollectionViewDiffableDataSource<Int, Contact>( collectionView: screenView.topCollectionView ) { [weak viewModel] collectionView, indexPath, contact in let cell: CreateGroupCollectionCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) @@ -80,7 +80,7 @@ public final class CreateGroupController: UIViewController { return cell } - bottomCollectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>( + bottomCollectionDataSource = UICollectionViewDiffableDataSource<Int, Contact>( collectionView: screenView.bottomCollectionView ) { [weak self] collectionView, indexPath, contact in let cell: AvatarCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) @@ -122,8 +122,8 @@ public final class CreateGroupController: UIViewController { }.store(in: &cancellables) selected.map { selectedContacts in - var snapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>() - let sections = [SectionId()] + var snapshot = NSDiffableDataSourceSnapshot<Int, Contact>() + let sections = [0] snapshot.appendSections(sections) sections.forEach { section in snapshot.appendItems(selectedContacts, toSection: section) } return snapshot @@ -133,9 +133,9 @@ public final class CreateGroupController: UIViewController { .store(in: &cancellables) viewModel.contacts - .map { contacts -> NSDiffableDataSourceSnapshot<SectionId, Contact> in - var snapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>() - let sections = [SectionId()] + .map { contacts -> NSDiffableDataSourceSnapshot<Int, Contact> in + var snapshot = NSDiffableDataSourceSnapshot<Int, Contact>() + let sections = [0] snapshot.appendSections(sections) sections.forEach { section in snapshot.appendItems(contacts, toSection: section) } return snapshot diff --git a/Sources/ContactListFeature/Views/ContactListActionButton.swift b/Sources/ContactListFeature/Views/ContactListActionButton.swift index f63e480d..21743fef 100644 --- a/Sources/ContactListFeature/Views/ContactListActionButton.swift +++ b/Sources/ContactListFeature/Views/ContactListActionButton.swift @@ -6,6 +6,7 @@ final class ContactListActionButton: UIControl { private let imageView = UIImageView() private let stackView = UIStackView() private let notificationLabel = UILabel() + private let notificationContainerView = UIView() init() { super.init(frame: .zero) @@ -13,18 +14,21 @@ final class ContactListActionButton: UIControl { 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) + notificationLabel.font = Fonts.Mulish.bold.font(size: 12.0) + + notificationContainerView.isHidden = true + notificationContainerView.layer.cornerRadius = 10 + notificationContainerView.layer.masksToBounds = true + notificationContainerView.addSubview(notificationLabel) + notificationContainerView.backgroundColor = Asset.brandPrimary.color stackView.spacing = 16 stackView.addArrangedSubview(imageView) stackView.addArrangedSubview(titleLabel) - stackView.addArrangedSubview(notificationLabel) + stackView.addArrangedSubview(notificationContainerView) stackView.addArrangedSubview(FlexibleSpace()) - stackView.setCustomSpacing(6, after: titleLabel) + stackView.setCustomSpacing(8, after: titleLabel) stackView.isUserInteractionEnabled = false addSubview(stackView) @@ -40,8 +44,8 @@ final class ContactListActionButton: UIControl { } func updateNotification(_ count: Int) { - notificationLabel.isHidden = count < 1 - notificationLabel.text = " \(count) " // TODO: Use insets (?) for padding + notificationLabel.text = "\(count)" + notificationContainerView.isHidden = count == 0 } private func setupConstraints() { @@ -53,5 +57,11 @@ final class ContactListActionButton: UIControl { stackView.snp.makeConstraints { $0.edges.equalToSuperview() } + + notificationLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.left.equalToSuperview().offset(8) + $0.right.equalToSuperview().offset(-8) + } } } diff --git a/Sources/ContactListFeature/Views/ContactListView.swift b/Sources/ContactListFeature/Views/ContactListView.swift index 6411e7ed..bcfe0ce7 100644 --- a/Sources/ContactListFeature/Views/ContactListView.swift +++ b/Sources/ContactListFeature/Views/ContactListView.swift @@ -3,9 +3,9 @@ import Shared final class ContactListView: UIView { private let separatorView = UIView() - private(set) var emptyView = ContactListEmptyView() - private(set) var newGroupButton = ContactListActionButton() - private(set) var requestsButton = ContactListActionButton() + let emptyView = ContactListEmptyView() + let newGroupButton = ContactListActionButton() + let requestsButton = ContactListActionButton() lazy var collectionView: UICollectionView = { var config = UICollectionLayoutListConfiguration(appearance: .plain) diff --git a/Sources/ContactListFeature/Views/CreateGroupView.swift b/Sources/ContactListFeature/Views/CreateGroupView.swift index b333956b..1b732578 100644 --- a/Sources/ContactListFeature/Views/CreateGroupView.swift +++ b/Sources/ContactListFeature/Views/CreateGroupView.swift @@ -15,8 +15,6 @@ final class CreateGroupView: UIView { let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout) collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0) return collectionView - - //tableView.setEditing(true, animated: true) }() let layout: UICollectionViewFlowLayout = { diff --git a/Sources/Countries/CountryListCell.swift b/Sources/Countries/CountryListCell.swift index b3b650e5..fa01c680 100644 --- a/Sources/Countries/CountryListCell.swift +++ b/Sources/Countries/CountryListCell.swift @@ -1,16 +1,15 @@ import UIKit import Shared -final class CountryListCell: UITableViewCell { - let nameLabel = UILabel() - let flagLabel = UILabel() - let prefixLabel = UILabel() - let separatorView = UIView() +final class CountryListCell: UICollectionViewCell { + private let nameLabel = UILabel() + private let flagLabel = UILabel() + private let prefixLabel = UILabel() + private let separatorView = UIView() - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) + override init(frame: CGRect) { + super.init(frame: frame) - selectionStyle = .none backgroundColor = Asset.neutralWhite.color nameLabel.textColor = Asset.neutralDark.color @@ -26,6 +25,29 @@ final class CountryListCell: UITableViewCell { contentView.addSubview(prefixLabel) contentView.addSubview(separatorView) + setupConstraints() + } + + required init?(coder: NSCoder) { nil } + + override func prepareForReuse() { + super.prepareForReuse() + nameLabel.text = nil + flagLabel.text = nil + prefixLabel.text = nil + } + + func set( + flag: String, + name: String, + prefix: String + ) { + flagLabel.text = flag + nameLabel.text = name + prefixLabel.text = prefix + } + + private func setupConstraints() { flagLabel.snp.makeConstraints { $0.left.top.equalToSuperview().inset(18) $0.bottom.equalToSuperview().offset(-16) @@ -49,14 +71,4 @@ final class CountryListCell: UITableViewCell { $0.height.equalTo(1) } } - - required init?(coder: NSCoder) { nil } - - override func prepareForReuse() { - super.prepareForReuse() - - nameLabel.text = nil - flagLabel.text = nil - prefixLabel.text = nil - } } diff --git a/Sources/Countries/CountryListController.swift b/Sources/Countries/CountryListController.swift index ab7ca357..20309062 100644 --- a/Sources/Countries/CountryListController.swift +++ b/Sources/Countries/CountryListController.swift @@ -1,4 +1,3 @@ -import os import Theme import UIKit import Shared @@ -6,14 +5,14 @@ import Combine import DependencyInjection public final class CountryListController: UIViewController { - @Dependency private var statusBarController: StatusBarStyleControlling + @Dependency var statusBarController: StatusBarStyleControlling lazy private var screenView = CountryListView() private var didChoose: ((Country) -> Void)! private let viewModel = CountryListViewModel() private var cancellables = Set<AnyCancellable>() - private var dataSource: UITableViewDiffableDataSource<SectionId, Country>! + private var dataSource: UICollectionViewDiffableDataSource<Int, Country>! public init(_ didChoose: @escaping (Country) -> Void) { self.didChoose = didChoose @@ -25,7 +24,6 @@ public final class CountryListController: UIViewController { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) statusBarController.style.send(.darkContent) - navigationController?.navigationBar.customize( backgroundColor: Asset.neutralWhite.color, shadowColor: Asset.neutralDisabled.color @@ -38,65 +36,70 @@ public final class CountryListController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() - screenView.tableView.register(CountryListCell.self) setupNavigationBar() + setupCollectionView() setupBindings() - - viewModel.fetchCountryList() } private func setupNavigationBar() { navigationItem.backButtonTitle = " " - let title = UILabel() - title.text = Localized.Countries.title - title.textColor = Asset.neutralActive.color - title.font = Fonts.Mulish.semiBold.font(size: 18.0) + let titleLabel = UILabel() + titleLabel.text = Localized.Countries.title + titleLabel.textColor = Asset.neutralActive.color + titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0) - let back = UIButton.back() - back.addTarget(self, action: #selector(didTapBack), for: .touchUpInside) + let backButton = UIButton.back() + backButton.addTarget(self, action: #selector(didTapBack), for: .touchUpInside) navigationItem.leftBarButtonItem = UIBarButtonItem( - customView: UIStackView(arrangedSubviews: [back, title]) + customView: UIStackView(arrangedSubviews: [backButton, titleLabel]) ) } private func setupBindings() { - viewModel.countries + viewModel.countriesPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] in dataSource.apply($0, animatingDifferences: false) } .store(in: &cancellables) - dataSource = UITableViewDiffableDataSource<SectionId, Country>( - tableView: screenView.tableView - ) { tableView, indexPath, country in - let cell: CountryListCell = tableView.dequeueReusableCell(forIndexPath: indexPath) - cell.flagLabel.text = country.flag - cell.nameLabel.text = country.name - cell.prefixLabel.text = country.prefix - return cell - } - screenView.searchComponent .textPublisher .removeDuplicates() .sink { [unowned self] in viewModel.didSearchFor($0) } .store(in: &cancellables) + } + + private func setupCollectionView() { + screenView.collectionView.register(CountryListCell.self) - screenView.tableView.delegate = self - screenView.tableView.dataSource = dataSource + dataSource = UICollectionViewDiffableDataSource<Int, Country>( + collectionView: screenView.collectionView + ) { collectionView, indexPath, country in + let cell: CountryListCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) + + cell.set( + flag: country.flag, + name: country.name, + prefix: country.prefix + ) + return cell + } + + screenView.collectionView.delegate = self + screenView.collectionView.dataSource = dataSource } @objc private func didTapBack() { navigationController?.popViewController(animated: true) } +} - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { +extension CountryListController: UICollectionViewDelegate { + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let country = dataSource.itemIdentifier(for: indexPath) { didChoose(country) navigationController?.popViewController(animated: true) } } } - -extension CountryListController: UITableViewDelegate {} diff --git a/Sources/Countries/CountryListView.swift b/Sources/Countries/CountryListView.swift index cf743823..0b82227b 100644 --- a/Sources/Countries/CountryListView.swift +++ b/Sources/Countries/CountryListView.swift @@ -2,19 +2,20 @@ import UIKit import Shared final class CountryListView: UIView { - let tableView = UITableView() let searchComponent = SearchComponent() + lazy var collectionView: UICollectionView = { + var config = UICollectionLayoutListConfiguration(appearance: .plain) + config.backgroundColor = .clear + 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() { - tableView.separatorStyle = .none - tableView.backgroundColor = .clear backgroundColor = Asset.neutralWhite.color searchComponent.set( @@ -23,20 +24,26 @@ final class CountryListView: UIView { rightAccessibility: Localized.Accessibility.Countries.Search.right ) - addSubview(tableView) + addSubview(collectionView) addSubview(searchComponent) - searchComponent.snp.makeConstraints { make in - make.top.equalToSuperview().offset(20) - make.left.equalToSuperview().offset(20) - make.right.equalToSuperview().offset(-20) + setupConstraints() + } + + required init?(coder: NSCoder) { nil } + + private func setupConstraints() { + searchComponent.snp.makeConstraints { + $0.top.equalToSuperview().offset(20) + $0.left.equalToSuperview().offset(20) + $0.right.equalToSuperview().offset(-20) } - tableView.snp.makeConstraints { make in - make.top.equalTo(searchComponent.snp.bottom).offset(20) - make.left.equalToSuperview() - make.bottom.equalToSuperview() - make.right.equalToSuperview() + collectionView.snp.makeConstraints { + $0.top.equalTo(searchComponent.snp.bottom).offset(20) + $0.left.equalToSuperview() + $0.bottom.equalToSuperview() + $0.right.equalToSuperview() } } } diff --git a/Sources/Countries/CountryListViewModel.swift b/Sources/Countries/CountryListViewModel.swift index e4157a16..b7377947 100644 --- a/Sources/Countries/CountryListViewModel.swift +++ b/Sources/Countries/CountryListViewModel.swift @@ -1,49 +1,51 @@ -import os import UIKit import Shared import Combine -import Foundation -private let logger = Logger(subsystem: "logs_xxmessenger", category: "Countries.CountryListViewModel.swift") +typealias CountryListSnapshot = NSDiffableDataSourceSnapshot<Int, Country> final class CountryListViewModel { - var countries: AnyPublisher<NSDiffableDataSourceSnapshot<SectionId, Country>, Never> { - countriesRelay.eraseToAnyPublisher() + var queryPublisher: AnyPublisher<String, Never> { + querySubject.eraseToAnyPublisher() + } + + var countriesPublisher: AnyPublisher<CountryListSnapshot, Never> { + countriesSubject.eraseToAnyPublisher() } private var cancellables = Set<AnyCancellable>() - private let searchQueryRelay = CurrentValueSubject<String, Never>("") - private let countriesRelay = CurrentValueSubject<NSDiffableDataSourceSnapshot<SectionId, Country>, Never>(.init()) - - func fetchCountryList() { - logger.log("fetchCountryList()") - - Publishers.CombineLatest(Just(Country.all()), searchQueryRelay) - .map { countryList, query -> NSDiffableDataSourceSnapshot<SectionId, Country> in - var snapshot = NSDiffableDataSourceSnapshot<SectionId, Country>() - let section = SectionId() - snapshot.appendSections([section]) - - guard !query.isEmpty else { - logger.log("query.isEmpty, returning all countries") - snapshot.appendItems(countryList, toSection: section) - return snapshot - } - - let filtered = countryList.filter { - $0.name.lowercased().contains(query.lowercased()) || - $0.prefix.lowercased().contains(query.lowercased()) - } - - snapshot.appendItems(filtered, toSection: section) - return snapshot - - }.sink { [weak countriesRelay] in countriesRelay?.send($0) } - .store(in: &cancellables) + private let querySubject = CurrentValueSubject<String, Never>("") + private let countriesSubject = CurrentValueSubject<CountryListSnapshot, Never>(.init()) + + init() { + Publishers.CombineLatest( + Just(Country.all()), + queryPublisher + ) + .map(prepareSnapshot(_:)) + .sink { [unowned self] in countriesSubject.send($0) } + .store(in: &cancellables) } func didSearchFor(_ string: String) { - logger.log("didSearchFor \(string, privacy: .public)()") - searchQueryRelay.send(string) + querySubject.send(string) + } + + private func prepareSnapshot(_ pair: (countries: [Country], query: String)) -> CountryListSnapshot { + var snapshot = CountryListSnapshot() + snapshot.appendSections([0]) + + guard !pair.query.isEmpty else { + snapshot.appendItems(pair.countries, toSection: 0) + return snapshot + } + + let filtered = pair.countries.filter { + $0.name.lowercased().contains(pair.query.lowercased()) || + $0.prefix.lowercased().contains(pair.query.lowercased()) + } + + snapshot.appendItems(filtered, toSection: 0) + return snapshot } } diff --git a/Sources/DrawerFeature/Items/DrawerList.swift b/Sources/DrawerFeature/Items/DrawerList.swift new file mode 100644 index 00000000..8be6eab4 --- /dev/null +++ b/Sources/DrawerFeature/Items/DrawerList.swift @@ -0,0 +1,68 @@ +import UIKit +import Shared +import SnapKit + +public final class DrawerList: DrawerItem { + private let view = UIView() + private var heightConstraint: Constraint? + private let collectionView = UICollectionView() + private let dataSource: UICollectionViewDiffableDataSource<Int, DrawerListCellModel> + + public var spacingAfter: CGFloat? = 0 + + public init(spacingAfter: CGFloat? = 10) { + self.dataSource = .init( + collectionView: collectionView, + cellProvider: { collectionView, indexPath, model in + var subtitle: String? + var subtitleColor = Asset.neutralSecondaryAlternative.color + let cell: DrawerListCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) + + if model.isCreator { + subtitle = "Creator" + subtitleColor = Asset.accentSafe.color + } else if !model.isConnection { + subtitle = "Not a connection" + } + + cell.set( + image: model.image, + title: model.title, + subtitle: subtitle, + subtitleColor: subtitleColor + ) + + return cell + }) + + self.spacingAfter = spacingAfter + } + + public func makeView() -> UIView { + collectionView.register(DrawerListCell.self) + collectionView.dataSource = dataSource + + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { + $0.edges.equalToSuperview() + heightConstraint = $0.height.equalTo(1).priority(.low).constraint + } + + return view + } + + public func update(models: [DrawerListCellModel]) { + let cellHeight = 56 + self.heightConstraint?.update(offset: cellHeight * models.count) + + var snapshot = NSDiffableDataSourceSnapshot<Int, DrawerListCellModel>() + snapshot.appendSections([0]) + snapshot.appendItems(models, toSection: 0) + dataSource.apply(snapshot, animatingDifferences: false) { [self] in + let frameHeight = collectionView.frame.height + let sizeHeight = collectionView.contentSize.height + collectionView.isScrollEnabled = sizeHeight > frameHeight + } + } +} diff --git a/Sources/DrawerFeature/Items/DrawerListCell.swift b/Sources/DrawerFeature/Items/DrawerListCell.swift new file mode 100644 index 00000000..d1ac87a9 --- /dev/null +++ b/Sources/DrawerFeature/Items/DrawerListCell.swift @@ -0,0 +1,77 @@ +import UIKit +import Shared + +final class DrawerListCell: UICollectionViewCell { + private let titleLabel = UILabel() + private let subtitleLabel = UILabel() + private let avatarView = AvatarView() + private let stackView = UIStackView() + + override init(frame: CGRect) { + super.init(frame: frame) + + backgroundColor = Asset.neutralWhite.color + + titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0) + subtitleLabel.font = Fonts.Mulish.regular.font(size: 14.0) + titleLabel.textColor = Asset.neutralActive.color + + stackView.axis = .vertical + stackView.addArrangedSubview(titleLabel) + stackView.addArrangedSubview(subtitleLabel) + + contentView.addSubview(avatarView) + contentView.addSubview(stackView) + + setupConstraints() + } + + required init?(coder: NSCoder) { nil } + + override func prepareForReuse() { + super.prepareForReuse() + + titleLabel.text = nil + subtitleLabel.text = nil + avatarView.prepareForReuse() + } + + func set( + image: Data?, + title: String, + subtitle: String?, + subtitleColor: UIColor = Asset.accentSafe.color + ) { + titleLabel.text = title + avatarView.setupProfile( + title: title, + image: image, + size: .medium + ) + + if let subtitle = subtitle { + subtitleLabel.text = subtitle + subtitleLabel.isHidden = false + subtitleLabel.textColor = subtitleColor + } else { + subtitleLabel.isHidden = true + } + } + + private func setupConstraints() { + avatarView.snp.makeConstraints { + $0.width.equalTo(36) + $0.height.equalTo(36) + $0.top.equalToSuperview().offset(10) + $0.left.equalToSuperview() + $0.bottom.equalToSuperview().offset(-10) + } + + stackView.snp.makeConstraints { + $0.left.equalTo(avatarView.snp.right).offset(15) + $0.top.equalTo(avatarView) + $0.bottom.equalTo(avatarView) + $0.right.equalToSuperview() + } + } +} diff --git a/Sources/DrawerFeature/Items/DrawerListCellModel.swift b/Sources/DrawerFeature/Items/DrawerListCellModel.swift new file mode 100644 index 00000000..d9bf356f --- /dev/null +++ b/Sources/DrawerFeature/Items/DrawerListCellModel.swift @@ -0,0 +1,23 @@ +import Foundation + +public struct DrawerListCellModel: Hashable { + let id: Data + let image: Data? + let title: String + let isCreator: Bool + let isConnection: Bool + + public init( + id: Data, + title: String, + image: Data? = nil, + isCreator: Bool = false, + isConnection: Bool = true + ) { + self.id = id + self.title = title + self.image = image + self.isCreator = isCreator + self.isConnection = isConnection + } +} diff --git a/Sources/DrawerFeature/Items/DrawerTable.swift b/Sources/DrawerFeature/Items/DrawerTable.swift deleted file mode 100644 index 726dae9f..00000000 --- a/Sources/DrawerFeature/Items/DrawerTable.swift +++ /dev/null @@ -1,146 +0,0 @@ -import UIKit -import Shared -import SnapKit - -enum DrawerTableSection { - case main -} - -public final class DrawerTable: DrawerItem { - private let view = UIView() - private let tableView = UITableView() - private var heightConstraint: Constraint? - private let dataSource: UITableViewDiffableDataSource<DrawerTableSection, DrawerTableCellModel> - - public var spacingAfter: CGFloat? = 0 - - public init(spacingAfter: CGFloat? = 10) { - self.dataSource = .init( - tableView: tableView, - cellProvider: { tableView, indexPath, model in - let cell: DrawerTableCell = tableView.dequeueReusableCell(forIndexPath: indexPath) - - cell.titleLabel.text = model.title - cell.avatarView.setupProfile( - title: model.title, - image: model.image, - size: .medium - ) - - if model.isCreator { - cell.subtitleLabel.text = "Creator" - cell.subtitleLabel.isHidden = false - cell.subtitleLabel.textColor = Asset.accentSafe.color - } else if !model.isConnection { - cell.subtitleLabel.text = "Not a connection" - cell.subtitleLabel.isHidden = false - cell.subtitleLabel.textColor = Asset.neutralSecondaryAlternative.color - } else { - cell.subtitleLabel.isHidden = true - } - - return cell - }) - - self.spacingAfter = spacingAfter - } - - public func makeView() -> UIView { - tableView.register(DrawerTableCell.self) - tableView.dataSource = dataSource - tableView.separatorStyle = .none - - view.addSubview(tableView) - - tableView.snp.makeConstraints { - $0.edges.equalToSuperview() - heightConstraint = $0.height.equalTo(1).priority(.low).constraint - } - - return view - } - - public func update(models: [DrawerTableCellModel]) { - let cellHeight = 56 - self.heightConstraint?.update(offset: cellHeight * models.count) - - var snapshot = NSDiffableDataSourceSnapshot<DrawerTableSection, DrawerTableCellModel>() - snapshot.appendSections([.main]) - snapshot.appendItems(models, toSection: .main) - dataSource.apply(snapshot, animatingDifferences: false) { [self] in - tableView.isScrollEnabled = tableView.contentSize.height > tableView.frame.height - } - } -} - -public struct DrawerTableCellModel: Hashable { - let id: Data - let title: String - let image: Data? - let isCreator: Bool - let isConnection: Bool - - public init( - id: Data, - title: String, - image: Data? = nil, - isCreator: Bool = false, - isConnection: Bool = true - ) { - self.id = id - self.title = title - self.image = image - self.isCreator = isCreator - self.isConnection = isConnection - } -} - -final class DrawerTableCell: UITableViewCell { - let titleLabel = UILabel() - let subtitleLabel = UILabel() - let avatarView = AvatarView() - let stackView = UIStackView() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - - selectionStyle = .none - backgroundColor = Asset.neutralWhite.color - - titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0) - subtitleLabel.font = Fonts.Mulish.regular.font(size: 14.0) - titleLabel.textColor = Asset.neutralActive.color - - stackView.axis = .vertical - stackView.addArrangedSubview(titleLabel) - stackView.addArrangedSubview(subtitleLabel) - - contentView.addSubview(avatarView) - contentView.addSubview(stackView) - - avatarView.snp.makeConstraints { - $0.width.equalTo(36) - $0.height.equalTo(36) - $0.top.equalToSuperview().offset(10) - $0.left.equalToSuperview() - $0.bottom.equalToSuperview().offset(-10) - } - - stackView.snp.makeConstraints { - $0.left.equalTo(avatarView.snp.right).offset(15) - $0.top.equalTo(avatarView) - $0.bottom.equalTo(avatarView) - $0.right.equalToSuperview() - } - } - - required init?(coder: NSCoder) { nil } - - override func prepareForReuse() { - super.prepareForReuse() - - titleLabel.text = nil - subtitleLabel.text = nil - avatarView.prepareForReuse() - } -} diff --git a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift index 0a30d05f..37f37fae 100644 --- a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift +++ b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift @@ -299,7 +299,7 @@ extension RequestsReceivedController { items.append(drawerLoading) - let drawerTable = DrawerTable(spacingAfter: 23) + let drawerTable = DrawerList(spacingAfter: 23) drawerLoading.retryPublisher .receive(on: DispatchQueue.main) diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift index ded18f77..4a38f458 100644 --- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift +++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift @@ -165,7 +165,7 @@ final class RequestsReceivedViewModel { func fetchMembers( _ group: Group, - _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void + _ completion: @escaping (Result<[DrawerListCellModel], Error>) -> Void ) { if let info = try? session.dbManager.fetchGroupInfos(.init(groupId: group.id)).first { session.dbManager.fetchContactsPublisher(.init(id: Set(info.members.map(\.id)))) @@ -174,7 +174,7 @@ final class RequestsReceivedViewModel { let withUsername = members .filter { $0.username != nil } .map { - DrawerTableCellModel( + DrawerListCellModel( id: $0.id, title: $0.nickname ?? $0.username!, image: $0.photo, @@ -186,7 +186,7 @@ final class RequestsReceivedViewModel { let withoutUsername = members .filter { $0.username == nil } .map { - DrawerTableCellModel( + DrawerListCellModel( id: $0.id, title: "Fetching username...", image: $0.photo, diff --git a/Sources/RequestsFeature/Views/RequestsReceivedView.swift b/Sources/RequestsFeature/Views/RequestsReceivedView.swift index d4df66fe..9874c455 100644 --- a/Sources/RequestsFeature/Views/RequestsReceivedView.swift +++ b/Sources/RequestsFeature/Views/RequestsReceivedView.swift @@ -36,7 +36,6 @@ final class RequestsReceivedView: UIView { let layout = UICollectionViewCompositionalLayout(section: section) let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout) - collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.backgroundColor = Asset.neutralWhite.color collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0) return collectionView diff --git a/Sources/SearchFeature/Controllers/SearchLeftController.swift b/Sources/SearchFeature/Controllers/SearchLeftController.swift index e7c39382..c4176b34 100644 --- a/Sources/SearchFeature/Controllers/SearchLeftController.swift +++ b/Sources/SearchFeature/Controllers/SearchLeftController.swift @@ -142,7 +142,6 @@ final class SearchLeftController: UIViewController { .sink { [unowned self] in viewModel.didTapCancelSearch() } .store(in: &self.hudCancellables) } else { - hudCancellables.forEach { $0.cancel() } hudCancellables.removeAll() } } diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift index d7749c15..a9beb9e5 100644 --- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift +++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift @@ -59,7 +59,6 @@ final class SearchLeftViewModel { } func didTapCancelSearch() { - searchCancellables.forEach { $0.cancel() } searchCancellables.removeAll() hudSubject.send(.none) } diff --git a/Sources/SearchFeature/Views/SearchLeftView.swift b/Sources/SearchFeature/Views/SearchLeftView.swift index 53730b9b..1474be90 100644 --- a/Sources/SearchFeature/Views/SearchLeftView.swift +++ b/Sources/SearchFeature/Views/SearchLeftView.swift @@ -50,7 +50,6 @@ final class SearchLeftView: UIView { let layout = UICollectionViewCompositionalLayout(section: section) let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout) - collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.backgroundColor = Asset.neutralWhite.color return collectionView }() diff --git a/Sources/Shared/Controllers/DiffEditableDataSource.swift b/Sources/Shared/Controllers/DiffEditableDataSource.swift deleted file mode 100644 index 9103733b..00000000 --- a/Sources/Shared/Controllers/DiffEditableDataSource.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit - -public struct SectionId: Hashable { - public init() {} -} - -public final class DiffEditableDataSource<SectionIdentifierType, ItemIdentifierType> -: UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> -where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable { - - public override func tableView(_ tableView: UITableView, - canEditRowAt indexPath: IndexPath) -> Bool { true } -} diff --git a/Sources/Shared/EditStateHandler.swift b/Sources/Shared/EditStateHandler.swift deleted file mode 100644 index 1a8d4c7e..00000000 --- a/Sources/Shared/EditStateHandler.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Combine - -public final class EditStateHandler { - // MARK: Properties - - public var isEditing: AnyPublisher<Bool, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<Bool, Never>(false) - - // MARK: Lifecycle - - public init() {} - - // MARK: Public - - public func didSwitchEditing() { - stateRelay.value.toggle() - } -} diff --git a/Sources/Shared/Views/AvatarCell.swift b/Sources/Shared/Views/AvatarCell.swift index cc39fc57..8dea6ba2 100644 --- a/Sources/Shared/Views/AvatarCell.swift +++ b/Sources/Shared/Views/AvatarCell.swift @@ -76,7 +76,6 @@ public final class AvatarCell: UICollectionViewCell { avatarView.prepareForReuse() actionButton.prepareForReuse() - cancellables.forEach { $0.cancel() } cancellables.removeAll() } @@ -107,23 +106,11 @@ public final class AvatarCell: UICollectionViewCell { separatorView.isHidden = !showSeparator if let action = action { - actionButton.set( - image: action.image, - title: action.title, - titleColor: action.color - ) - - didTapAction = action.action - - actionButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in didTapAction?() } - .store(in: &cancellables) + update(action: action) } } public func update(action: Action) { - cancellables.forEach { $0.cancel() } cancellables.removeAll() actionButton.set( -- GitLab