diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 04ada8e65..49029612a 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -164,6 +164,7 @@ D84BB76D2C3D88B000493718 /* DataSourceFacade+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */; }; D84BB76F2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */; }; D84BB7722C3E566C00493718 /* NotificationPolicyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */; }; + D84BB7752C3EB80900493718 /* NotificationRequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; }; @@ -802,6 +803,7 @@ D84BB76C2C3D88B000493718 /* DataSourceFacade+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Notifications.swift"; sourceTree = ""; }; D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestsTableViewController.swift; sourceTree = ""; }; D84BB7712C3E566C00493718 /* NotificationPolicyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPolicyHeaderView.swift; sourceTree = ""; }; + D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestTableViewCell.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; D84C09A02B0F9E41009E685E /* How-it-works.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "How-it-works.md"; sourceTree = ""; }; @@ -1774,8 +1776,8 @@ D80EC2602C2978CB009724A5 /* Notification Filtering */ = { isa = PBXGroup; children = ( + D84BB7732C3EB7F600493718 /* Requests */, D84BB7702C3E565100493718 /* Policy */, - D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */, ); path = "Notification Filtering"; sourceTree = ""; @@ -1839,6 +1841,15 @@ path = Policy; sourceTree = ""; }; + D84BB7732C3EB7F600493718 /* Requests */ = { + isa = PBXGroup; + children = ( + D84BB76E2C3D8DBC00493718 /* NotificationRequestsTableViewController.swift */, + D84BB7742C3EB80900493718 /* NotificationRequestTableViewCell.swift */, + ); + path = Requests; + sourceTree = ""; + }; D84C099E2B0F9E41009E685E /* Documentation */ = { isa = PBXGroup; children = ( @@ -3620,6 +3631,7 @@ D81A94172B07A1D30067A19D /* ProfileCardView+Configuration.swift in Sources */, DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */, D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */, + D84BB7752C3EB80900493718 /* NotificationRequestTableViewCell.swift in Sources */, DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */, DB98EB5327B0F9890082E365 /* ReportHeadlineTableViewCell.swift in Sources */, DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift index 937f75eff..d465fdffa 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Notifications.swift @@ -11,8 +11,8 @@ extension DataSourceFacade { provider.coordinator.showLoading() do { - let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox) - let viewModel = NotificationRequestsViewModel() + let notificationRequests = try await provider.context.apiService.notificationRequests(authenticationBox: provider.authContext.mastodonAuthenticationBox).value + let viewModel = NotificationRequestsViewModel(requests: notificationRequests) provider.coordinator.hideLoading() diff --git a/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift deleted file mode 100644 index 42f121b9f..000000000 --- a/Mastodon/Scene/Notification/Notification Filtering/NotificationRequestsTableViewController.swift +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © 2024 Mastodon gGmbH. All rights reserved. - -import UIKit - -struct NotificationRequestsViewModel { - -} - -class NotificationRequestsTableViewController: UIViewController { - let tableView: UITableView - - init(viewModel: NotificationRequestsViewModel) { - //TODO: Cell, DataSource, Delegate.... - tableView = UITableView(frame: .zero) - tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.backgroundColor = .systemBackground - - super.init(nibName: nil, bundle: nil) - - view.addSubview(tableView) - tableView.pinToParent() - } - - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } -} diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift new file mode 100644 index 000000000..4b56751f9 --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestTableViewCell.swift @@ -0,0 +1,90 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonSDK +import MetaTextKit +import MastodonMeta +import MastodonUI +import MastodonCore + +class NotificationRequestTableViewCell: UITableViewCell { + static let reuseIdentifier = "NotificationRequestTableViewCell" + + let nameLabel: MetaLabel + let usernameLabel: MetaLabel + let avatarButton: AvatarButton + + private let labelStackView: UIStackView + private let contentStackView: UIStackView + +// private let stack + // accept/deny-button + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + nameLabel = MetaLabel(style: .statusName) + usernameLabel = MetaLabel(style: .statusUsername) + avatarButton = AvatarButton() + avatarButton.translatesAutoresizingMaskIntoConstraints = false + avatarButton.size = CGSize.authorAvatarButtonSize + avatarButton.avatarImageView.imageViewSize = CGSize.authorAvatarButtonSize + + labelStackView = UIStackView(arrangedSubviews: [nameLabel, usernameLabel]) + labelStackView.axis = .vertical + labelStackView.alignment = .leading + labelStackView.spacing = 4 + + contentStackView = UIStackView(arrangedSubviews: [avatarButton, labelStackView]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + contentStackView.axis = .horizontal + contentStackView.alignment = .center + contentStackView.spacing = 12 + + super.init(style: style, reuseIdentifier: reuseIdentifier) + + contentView.addSubview(contentStackView) + setupConstraints() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + + contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), + contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16), + contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16), + + avatarButton.widthAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.width).priority(.required - 1), + avatarButton.heightAnchor.constraint(equalToConstant: CGSize.authorAvatarButtonSize.height).priority(.required - 1), + ] + NSLayoutConstraint.activate(constraints) + + } + + override func prepareForReuse() { + avatarButton.avatarImageView.image = nil + avatarButton.avatarImageView.cancelTask() + } + + func configure(with request: Mastodon.Entity.NotificationRequest) { + let account = request.account + + avatarButton.avatarImageView.configure(with: account.avatarImageURL()) + avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12))) + + // author name + let metaAccountName: MetaContent + do { + let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojis.asDictionary) + metaAccountName = try MastodonMetaContent.convert(document: content) + } catch { + assertionFailure(error.localizedDescription) + metaAccountName = PlaintextMetaContent(string: account.displayNameWithFallback) + } + nameLabel.configure(content: metaAccountName) + + let metaUsername = PlaintextMetaContent(string: "@\(account.acct)") + usernameLabel.configure(content: metaUsername) + } +} diff --git a/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift new file mode 100644 index 000000000..54797e60a --- /dev/null +++ b/Mastodon/Scene/Notification/Notification Filtering/Requests/NotificationRequestsTableViewController.swift @@ -0,0 +1,70 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonSDK + +struct NotificationRequestsViewModel { + var requests: [Mastodon.Entity.NotificationRequest] +} + +enum NotificationRequestsSection: Hashable { + case main +} + +enum NotificationRequestItem: Hashable { + case item(Mastodon.Entity.NotificationRequest) +} + +class NotificationRequestsTableViewController: UIViewController { + + let tableView: UITableView + var viewModel: NotificationRequestsViewModel + var dataSource: UITableViewDiffableDataSource? + + init(viewModel: NotificationRequestsViewModel) { + //TODO: DataSource, Delegate.... + self.viewModel = viewModel + + tableView = UITableView(frame: .zero) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.backgroundColor = .systemBackground + tableView.register(NotificationRequestTableViewCell.self, forCellReuseIdentifier: NotificationRequestTableViewCell.reuseIdentifier) + + super.init(nibName: nil, bundle: nil) + + view.addSubview(tableView) + tableView.pinToParent() + + let dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, itemIdentifier in + guard let cell = tableView.dequeueReusableCell(withIdentifier: NotificationRequestTableViewCell.reuseIdentifier, for: indexPath) as? NotificationRequestTableViewCell else { + fatalError("No NotificationRequestTableViewCell") + } + + let request = viewModel.requests[indexPath.row] + cell.configure(with: request) + + return cell + } + + tableView.dataSource = dataSource + tableView.delegate = self + self.dataSource = dataSource + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(viewModel.requests.compactMap { NotificationRequestItem.item($0) } ) + + dataSource?.apply(snapshot) + } +} + +extension NotificationRequestsTableViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } +}