From a2729b17c56328d3973947f44c16946b246b93bf Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 12 May 2023 14:10:08 +0200 Subject: [PATCH 01/29] Minor UI-improvements in UserView (IOS-157) Make avatar-image bigger, fix position of follow-button --- .../MastodonUI/View/Content/UserView.swift | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift index b02fd3602..3ee4c1973 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift @@ -99,7 +99,13 @@ public final class UserView: UIView { label.textColor = .secondaryLabel return label }() - + + private let followButtonWrapper = { + let wrapper = UIView() + + return wrapper + }() + private let followButton: FollowButton = { let button = FollowButton() button.cornerRadius = 10 @@ -149,10 +155,7 @@ extension UserView { avatarButton.translatesAutoresizingMaskIntoConstraints = false containerStackView.addArrangedSubview(avatarButton) - NSLayoutConstraint.activate([ - avatarButton.widthAnchor.constraint(equalToConstant: 28).priority(.required - 1), - avatarButton.heightAnchor.constraint(equalToConstant: 28).priority(.required - 1), - ]) + avatarButton.setContentHuggingPriority(.defaultLow, for: .vertical) avatarButton.setContentHuggingPriority(.defaultLow, for: .horizontal) @@ -162,7 +165,19 @@ extension UserView { containerStackView.addArrangedSubview(labelStackView) // follow button - containerStackView.addArrangedSubview(followButton) + followButtonWrapper.translatesAutoresizingMaskIntoConstraints = false + followButtonWrapper.addSubview(followButton) + + containerStackView.addArrangedSubview(followButtonWrapper) + + NSLayoutConstraint.activate([ + followButton.topAnchor.constraint(lessThanOrEqualTo: avatarButton.topAnchor), + followButton.leadingAnchor.constraint(equalTo: followButtonWrapper.leadingAnchor), + followButtonWrapper.trailingAnchor.constraint(equalTo: followButton.trailingAnchor), + followButtonWrapper.bottomAnchor.constraint(greaterThanOrEqualTo: followButton.bottomAnchor), + + followButtonWrapper.heightAnchor.constraint(equalTo: containerStackView.heightAnchor), + ]) let nameStackView = UIStackView() nameStackView.axis = .horizontal @@ -181,7 +196,14 @@ extension UserView { authorUsernameLabel.setContentCompressionResistancePriority(.defaultHigh - 1, for: .horizontal) labelStackView.addArrangedSubview(nameStackView) - + + NSLayoutConstraint.activate([ + avatarButton.heightAnchor.constraint(lessThanOrEqualToConstant: 56), + avatarButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 28), + avatarButton.heightAnchor.constraint(equalTo: avatarButton.widthAnchor), + avatarButton.heightAnchor.constraint(equalTo: labelStackView.heightAnchor), + ]) + let verifiedSpacerView = UIView() let verifiedStackTrailingSpacerView = UIView() From c0532d7b5618091e3337c0dcdd13fe62bc6a85c2 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 12 May 2023 21:04:47 +0200 Subject: [PATCH 02/29] Remove collection view from suggestion-screen (IOS-157) --- Mastodon.xcodeproj/project.pbxproj | 16 ------ .../Account/SelectedAccountItem.swift | 15 ------ .../Account/SelectedAccountSection.swift | 37 ------------- .../SuggestionAccountViewController.swift | 54 +------------------ .../SuggestionAccountViewModel+Diffable.swift | 33 ------------ .../SuggestionAccountViewModel.swift | 17 +----- 6 files changed, 4 insertions(+), 168 deletions(-) delete mode 100644 Mastodon/Diffable/Account/SelectedAccountItem.swift delete mode 100644 Mastodon/Diffable/Account/SelectedAccountSection.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 46d7ea619..eaa6de62f 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -76,8 +76,6 @@ 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; }; 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */; }; 2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */; }; - 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */; }; - 2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */; }; 2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */; }; 2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */; }; 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */; }; @@ -695,8 +693,6 @@ 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposeBagCollectable.swift; sourceTree = ""; }; 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITapGestureRecognizer.swift; sourceTree = ""; }; 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountCollectionViewCell.swift; sourceTree = ""; }; - 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedAccountSection.swift; sourceTree = ""; }; - 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedAccountItem.swift; sourceTree = ""; }; 2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarProgressView.swift; sourceTree = ""; }; 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewController.swift; sourceTree = ""; }; 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewModel.swift; sourceTree = ""; }; @@ -1625,7 +1621,6 @@ isa = PBXGroup; children = ( DB4F097826A039B400D62E92 /* Onboarding */, - DB0617FB27855B740030EE79 /* Account */, DB0617F827855B170030EE79 /* User */, DB0617F927855B460030EE79 /* Profile */, DB0FCB892796BE1E006C02E2 /* RecommandAccount */, @@ -1907,15 +1902,6 @@ path = Settings; sourceTree = ""; }; - DB0617FB27855B740030EE79 /* Account */ = { - isa = PBXGroup; - children = ( - 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */, - 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */, - ); - path = Account; - sourceTree = ""; - }; DB0618082785B2790030EE79 /* Cell */ = { isa = PBXGroup; children = ( @@ -3689,7 +3675,6 @@ DB63F7542799491600455B82 /* DataSourceFacade+SearchHistory.swift in Sources */, DB7A9F912818EAF10016AF98 /* MastodonRegisterView.swift in Sources */, DBF1572F27046F1A00EC00B7 /* SecondaryPlaceholderViewController.swift in Sources */, - 2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */, DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */, 2AB12E4629362F27006BC925 /* DataSourceFacade+Translate.swift in Sources */, DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, @@ -3927,7 +3912,6 @@ DBB525412611ED54002F1F29 /* ProfileHeaderViewController.swift in Sources */, DBDFF19A28055A1400557A48 /* DiscoveryViewController.swift in Sources */, DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */, - 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */, DB3EA8F1281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift in Sources */, 85BC11B32932414900E191CD /* AltTextViewController.swift in Sources */, DB63F775279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift in Sources */, diff --git a/Mastodon/Diffable/Account/SelectedAccountItem.swift b/Mastodon/Diffable/Account/SelectedAccountItem.swift deleted file mode 100644 index 05ecdae8d..000000000 --- a/Mastodon/Diffable/Account/SelectedAccountItem.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// SelectedAccountItem.swift -// Mastodon -// -// Created by sxiaojian on 2021/4/22. -// - -import CoreData -import Foundation -import CoreDataStack - -enum SelectedAccountItem: Hashable { - case account(ManagedObjectRecord) - case placeHolder(uuid: UUID) -} diff --git a/Mastodon/Diffable/Account/SelectedAccountSection.swift b/Mastodon/Diffable/Account/SelectedAccountSection.swift deleted file mode 100644 index d71cbf326..000000000 --- a/Mastodon/Diffable/Account/SelectedAccountSection.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// SelectedAccountSection.swift -// Mastodon -// -// Created by sxiaojian on 2021/4/22. -// - -import UIKit -import CoreData -import CoreDataStack -import MastodonCore -import MastodonSDK - -enum SelectedAccountSection: Equatable, Hashable { - case main -} - -extension SelectedAccountSection { - static func collectionViewDiffableDataSource( - collectionView: UICollectionView, - context: AppContext - ) -> UICollectionViewDiffableDataSource { - UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SuggestionAccountCollectionViewCell.self), for: indexPath) as! SuggestionAccountCollectionViewCell - switch item { - case .account(let record): - context.managedObjectContext.performAndWait { - guard let user = record.object(in: context.managedObjectContext) else { return } - cell.config(with: user) - } - case .placeHolder: - cell.configAsPlaceHolder() - } - return cell - } - } -} diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 7eacdc205..941b0f429 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -17,9 +17,7 @@ import MastodonUI import MastodonLocalization class SuggestionAccountViewController: UIViewController, NeedsDependency { - - static let collectionViewHeight: CGFloat = 24 + 64 + 24 - + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } @@ -41,19 +39,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { return UICollectionViewCompositionalLayout(section: section) } - let collectionView: UICollectionView = { - let collectionViewLayout = SuggestionAccountViewController.createCollectionViewLayout() - let view = ControlContainableCollectionView( - frame: .zero, - collectionViewLayout: collectionViewLayout - ) - view.register(SuggestionAccountCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: SuggestionAccountCollectionViewCell.self)) - view.backgroundColor = .clear - view.showsHorizontalScrollIndicator = false - view.showsVerticalScrollIndicator = false - view.layer.masksToBounds = false - return view - }() let tableView: UITableView = { let tableView = ControlContainableTableView() @@ -89,31 +74,16 @@ extension SuggestionAccountViewController { target: self, action: #selector(SuggestionAccountViewController.doneButtonDidClick(_:)) ) - - collectionView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(collectionView) - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - collectionView.heightAnchor.constraint(equalToConstant: SuggestionAccountViewController.collectionViewHeight), - ]) - defer { view.bringSubviewToFront(collectionView) } tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: collectionView.bottomAnchor), + tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - collectionView.delegate = self - viewModel.setupDiffableDataSource( - collectionView: collectionView - ) - tableView.delegate = self viewModel.setupDiffableDataSource( tableView: tableView, @@ -129,26 +99,6 @@ extension SuggestionAccountViewController { private func setupBackgroundColor(theme: Theme) { view.backgroundColor = theme.systemBackgroundColor - collectionView.backgroundColor = theme.systemGroupedBackgroundColor - } -} - -// MARK: - UICollectionViewDelegateFlowLayout -extension SuggestionAccountViewController: UICollectionViewDelegate { - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { -// guard let diffableDataSource = viewModel.collectionDiffableDataSource else { return } -// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } -// switch item { -// case .accountObjectID(let accountObjectID): -// let mastodonUser = context.managedObjectContext.object(with: accountObjectID) as! MastodonUser -// let viewModel = ProfileViewModel(context: context, optionalMastodonUser: mastodonUser) -// DispatchQueue.main.async { -// self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) -// } -// default: -// break -// } } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift index afb3eb7ec..08ce749a4 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift @@ -38,37 +38,4 @@ extension SuggestionAccountViewModel { } .store(in: &disposeBag) } - - func setupDiffableDataSource( - collectionView: UICollectionView - ) { - collectionViewDiffableDataSource = SelectedAccountSection.collectionViewDiffableDataSource( - collectionView: collectionView, - context: context - ) - - selectedUserFetchedResultsController.$records - .receive(on: DispatchQueue.main) - .sink { [weak self] records in - guard let self = self else { return } - guard let collectionViewDiffableDataSource = self.collectionViewDiffableDataSource else { return } - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - var items: [SelectedAccountItem] = records.map { SelectedAccountItem.account($0) } - - if items.count < 10 { - let count = 10 - items.count - let placeholderItems: [SelectedAccountItem] = (0..() // output - var collectionViewDiffableDataSource: UICollectionViewDiffableDataSource? var tableViewDiffableDataSource: UITableViewDiffableDataSource? init( @@ -46,20 +44,10 @@ final class SuggestionAccountViewModel: NSObject { domain: nil, additionalPredicate: nil ) - self.selectedUserFetchedResultsController = UserFetchedResultsController( - managedObjectContext: context.managedObjectContext, - domain: nil, - additionalPredicate: nil - ) super.init() userFetchedResultsController.domain = authContext.mastodonAuthenticationBox.domain - selectedUserFetchedResultsController.domain = authContext.mastodonAuthenticationBox.domain - selectedUserFetchedResultsController.additionalPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [ - MastodonUser.predicate(followingBy: authContext.mastodonAuthenticationBox.userID), - MastodonUser.predicate(followRequestedBy: authContext.mastodonAuthenticationBox.userID) - ]) - + // fetch recomment users Task { var userIDs: [MastodonUser.ID] = [] @@ -81,7 +69,6 @@ final class SuggestionAccountViewModel: NSObject { guard !userIDs.isEmpty else { return } userFetchedResultsController.userIDs = userIDs - selectedUserFetchedResultsController.userIDs = userIDs } // fetch relationship From 66ce4802ef359abc6fb5047b238d7e3718c22a3b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 12 May 2023 22:06:44 +0200 Subject: [PATCH 03/29] Cleanup/Style TableView (IOS-157) --- Mastodon.xcodeproj/project.pbxproj | 12 -- .../RecommendAccountSection.swift | 111 ---------------- .../SuggestionAccountCollectionViewCell.swift | 58 --------- .../SuggestionAccountViewController.swift | 53 ++------ .../SuggestionAccountViewModel+Diffable.swift | 2 +- ...estionAccountTableViewCell+ViewModel.swift | 122 +++++++++--------- .../SuggestionAccountTableViewCell.swift | 12 +- 7 files changed, 73 insertions(+), 297 deletions(-) delete mode 100644 Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index eaa6de62f..15b8ff836 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -75,7 +75,6 @@ 2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */; }; 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; }; 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */; }; - 2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */; }; 2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */; }; 2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */; }; 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */; }; @@ -692,7 +691,6 @@ 2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadOldestState.swift"; sourceTree = ""; }; 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposeBagCollectable.swift; sourceTree = ""; }; 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITapGestureRecognizer.swift; sourceTree = ""; }; - 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountCollectionViewCell.swift; sourceTree = ""; }; 2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarProgressView.swift; sourceTree = ""; }; 2D59819A25E4A581000FB903 /* MastodonConfirmEmailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewController.swift; sourceTree = ""; }; 2D5981A025E4A593000FB903 /* MastodonConfirmEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonConfirmEmailViewModel.swift; sourceTree = ""; }; @@ -1572,14 +1570,6 @@ path = Button; sourceTree = ""; }; - 2D4AD89A2631659400613EFC /* CollectionViewCell */ = { - isa = PBXGroup; - children = ( - 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */, - ); - path = CollectionViewCell; - sourceTree = ""; - }; 2D59819925E4A55C000FB903 /* ConfirmEmail */ = { isa = PBXGroup; children = ( @@ -1681,7 +1671,6 @@ 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */, 2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */, DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */, - 2D4AD89A2631659400613EFC /* CollectionViewCell */, 2DAC9E43262FC9DE0062E1A6 /* TableViewCell */, ); path = SuggestionAccount; @@ -3841,7 +3830,6 @@ DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */, DBA5A53526F0A36A00CACBAA /* AddAccountTableViewCell.swift in Sources */, 2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */, - 2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */, DB98EB6927B21A7C0082E365 /* ReportResultActionTableViewCell.swift in Sources */, DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */, DB697DDF278F524F004EF2F7 /* DataSourceFacade+Profile.swift in Sources */, diff --git a/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift b/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift index e5aa0a605..7d14aa4af 100644 --- a/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift +++ b/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift @@ -19,117 +19,6 @@ enum RecommendAccountSection: Equatable, Hashable { case main } -//extension RecommendAccountSection { -// static func collectionViewDiffableDataSource( -// for collectionView: UICollectionView, -// dependency: NeedsDependency, -// delegate: SearchRecommendAccountsCollectionViewCellDelegate, -// managedObjectContext: NSManagedObjectContext -// ) -> UICollectionViewDiffableDataSource { -// UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak delegate] collectionView, indexPath, objectID -> UICollectionViewCell? in -// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SearchRecommendAccountsCollectionViewCell.self), for: indexPath) as! SearchRecommendAccountsCollectionViewCell -// managedObjectContext.performAndWait { -// let user = managedObjectContext.object(with: objectID) as! MastodonUser -// configure(cell: cell, user: user, dependency: dependency) -// } -// cell.delegate = delegate -// return cell -// } -// } -// -// static func configure( -// cell: SearchRecommendAccountsCollectionViewCell, -// user: MastodonUser, -// dependency: NeedsDependency -// ) { -// configureContent(cell: cell, user: user) -// -// if let currentMastodonUser = dependency.context.authenticationService.activeMastodonAuthentication.value?.user { -// configureFollowButton(with: user, currentMastodonUser: currentMastodonUser, followButton: cell.followButton) -// } -// -// Publishers.CombineLatest( -// ManagedObjectObserver.observe(object: user).eraseToAnyPublisher().mapError { $0 as Error }, -// dependency.context.authenticationService.activeMastodonAuthentication.setFailureType(to: Error.self) -// ) -// .receive(on: DispatchQueue.main) -// .sink { _ in -// // do nothing -// } receiveValue: { [weak cell] change, authentication in -// guard let cell = cell else { return } -// guard case .update(let object) = change.changeType, -// let user = object as? MastodonUser else { return } -// guard let currentMastodonUser = authentication?.user else { return } -// -// configureFollowButton(with: user, currentMastodonUser: currentMastodonUser, followButton: cell.followButton) -// } -// .store(in: &cell.disposeBag) -// -// } -// -// static func configureContent( -// cell: SearchRecommendAccountsCollectionViewCell, -// user: MastodonUser -// ) { -// do { -// let mastodonContent = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojis.asDictionary) -// let metaContent = try MastodonMetaContent.convert(document: mastodonContent) -// cell.displayNameLabel.configure(content: metaContent) -// } catch { -// let metaContent = PlaintextMetaContent(string: user.displayNameWithFallback) -// cell.displayNameLabel.configure(content: metaContent) -// } -// cell.acctLabel.text = "@" + user.acct -// cell.avatarImageView.af.setImage( -// withURL: user.avatarImageURLWithFallback(domain: user.domain), -// placeholderImage: UIImage.placeholder(color: .systemFill), -// imageTransition: .crossDissolve(0.2) -// ) -// cell.headerImageView.af.setImage( -// withURL: URL(string: user.header)!, -// placeholderImage: UIImage.placeholder(color: .systemFill), -// imageTransition: .crossDissolve(0.2) -// ) -// } -// -// static func configureFollowButton( -// with mastodonUser: MastodonUser, -// currentMastodonUser: MastodonUser, -// followButton: HighlightDimmableButton -// ) { -// let relationshipActionSet = relationShipActionSet(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser) -// followButton.setTitle(relationshipActionSet.title, for: .normal) -// } -// -// static func relationShipActionSet( -// mastodonUser: MastodonUser, -// currentMastodonUser: MastodonUser -// ) -> ProfileViewModel.RelationshipActionOptionSet { -// var relationshipActionSet = ProfileViewModel.RelationshipActionOptionSet([.follow]) -// let isFollowing = mastodonUser.followingBy.flatMap { $0.contains(currentMastodonUser) } ?? false -// if isFollowing { -// relationshipActionSet.insert(.following) -// } -// -// let isPending = mastodonUser.followRequestedBy.flatMap { $0.contains(currentMastodonUser) } ?? false -// if isPending { -// relationshipActionSet.insert(.pending) -// } -// -// let isBlocking = mastodonUser.blockingBy.flatMap { $0.contains(currentMastodonUser) } ?? false -// if isBlocking { -// relationshipActionSet.insert(.blocking) -// } -// -// let isBlockedBy = currentMastodonUser.blockingBy.flatMap { $0.contains(mastodonUser) } ?? false -// if isBlockedBy { -// relationshipActionSet.insert(.blocked) -// } -// return relationshipActionSet -// } -// -//} -// extension RecommendAccountSection { struct Configuration { diff --git a/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift b/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift deleted file mode 100644 index 296935521..000000000 --- a/Mastodon/Scene/SuggestionAccount/CollectionViewCell/SuggestionAccountCollectionViewCell.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// SuggestionAccountCollectionViewCell.swift -// Mastodon -// -// Created by sxiaojian on 2021/4/22. -// - -import CoreDataStack -import Foundation -import UIKit -import MastodonAsset -import MastodonLocalization - -class SuggestionAccountCollectionViewCell: UICollectionViewCell { - let imageView: UIImageView = { - let imageView = UIImageView() - imageView.tintColor = Asset.Colors.Label.tertiary.color - imageView.layer.cornerRadius = 4 - imageView.clipsToBounds = true - imageView.image = UIImage.placeholder(color: .systemFill) - return imageView - }() - - func configAsPlaceHolder() { - imageView.tintColor = Asset.Colors.Label.tertiary.color - imageView.image = UIImage.placeholder(color: .systemFill) - } - - func config(with mastodonUser: MastodonUser) { - imageView.af.setImage( - withURL: URL(string: mastodonUser.avatar)!, - placeholderImage: UIImage.placeholder(color: .systemFill), - imageTransition: .crossDissolve(0.2) - ) - } - - override func prepareForReuse() { - super.prepareForReuse() - } - - override init(frame: CGRect) { - super.init(frame: .zero) - configure() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - configure() - } -} - -extension SuggestionAccountCollectionViewCell { - private func configure() { - contentView.addSubview(imageView) - imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.pinToParent() - } -} diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 941b0f429..6d37a3f88 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -9,7 +9,6 @@ import Combine import CoreData import CoreDataStack import Foundation -import OSLog import UIKit import MastodonAsset import MastodonCore @@ -23,51 +22,19 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { var disposeBag = Set() var viewModel: SuggestionAccountViewModel! - - private static func createCollectionViewLayout() -> UICollectionViewLayout { - let itemSize = NSCollectionLayoutSize(widthDimension: .absolute(64), heightDimension: .absolute(64)) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item]) - - let section = NSCollectionLayoutSection(group: group) - section.contentInsets = NSDirectionalEdgeInsets(top: 24, leading: 0, bottom: 24, trailing: 0) - section.orthogonalScrollingBehavior = .continuous - section.contentInsetsReference = .readableContent - section.interGroupSpacing = 16 - - return UICollectionViewCompositionalLayout(section: section) - } - let tableView: UITableView = { - let tableView = ControlContainableTableView() + let tableView = UITableView(frame: .zero, style: .insetGrouped) tableView.register(SuggestionAccountTableViewCell.self, forCellReuseIdentifier: String(describing: SuggestionAccountTableViewCell.self)) - tableView.rowHeight = UITableView.automaticDimension - tableView.tableFooterView = UIView() - tableView.separatorStyle = .singleLine - tableView.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + tableView.separatorStyle = .none return tableView }() - deinit { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s:", (#file as NSString).lastPathComponent, #line, #function) - } -} + //TODO: Add "follow all"-footer-cell -extension SuggestionAccountViewController { override func viewDidLoad() { super.viewDidLoad() - setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) - ThemeService.shared.currentTheme - .receive(on: DispatchQueue.main) - .sink { [weak self] theme in - guard let self = self else { return } - self.setupBackgroundColor(theme: theme) - } - .store(in: &disposeBag) - title = L10n.Scene.SuggestionAccount.title navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: UIBarButtonItem.SystemItem.done, @@ -78,7 +45,7 @@ extension SuggestionAccountViewController { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.topAnchor), + tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), @@ -89,6 +56,11 @@ extension SuggestionAccountViewController { tableView: tableView, suggestionAccountTableViewCellDelegate: self ) + + view.backgroundColor = .secondarySystemBackground + tableView.backgroundColor = .secondarySystemBackground + navigationController?.navigationBar.prefersLargeTitles = true + navigationItem.largeTitleDisplayMode = .always } override func viewWillAppear(_ animated: Bool) { @@ -96,10 +68,6 @@ extension SuggestionAccountViewController { tableView.deselectRow(with: transitionCoordinator, animated: animated) } - - private func setupBackgroundColor(theme: Theme) { - view.backgroundColor = theme.systemBackgroundColor - } } // MARK: - UITableViewDelegate @@ -156,8 +124,5 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat extension SuggestionAccountViewController { @objc func doneButtonDidClick(_ sender: UIButton) { dismiss(animated: true, completion: nil) -// if viewModel.selectedAccounts.value.count > 0 { -// viewModel.delegate?.homeTimelineNeedRefresh.send() -// } } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift index 08ce749a4..b90240d66 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift @@ -21,7 +21,7 @@ extension SuggestionAccountViewModel { suggestionAccountTableViewCellDelegate: suggestionAccountTableViewCellDelegate ) ) - + userFetchedResultsController.$records .removeDuplicates() .receive(on: DispatchQueue.main) diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift index 6d93be1a9..828449c6d 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift @@ -16,70 +16,6 @@ import Meta extension SuggestionAccountTableViewCell { - class ViewModel { - var disposeBag = Set() - - @Published public var userIdentifier: UserIdentifier? // me - - @Published var avatarImageURL: URL? - @Published public var authorName: MetaContent? - @Published public var authorUsername: String? - - @Published var isFollowing = false - @Published var isPending = false - - func prepareForReuse() { - isFollowing = false - isPending = false - } - } - -} - -extension SuggestionAccountTableViewCell.ViewModel { - func bind(cell: SuggestionAccountTableViewCell) { - // avatar - $avatarImageURL.removeDuplicates() - .sink { url in - let configuration = AvatarImageView.Configuration(url: url) - cell.avatarButton.avatarImageView.configure(configuration: configuration) - cell.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12))) - } - .store(in: &disposeBag) - // name - $authorName - .sink { metaContent in - let metaContent = metaContent ?? PlaintextMetaContent(string: " ") - cell.titleLabel.configure(content: metaContent) - } - .store(in: &disposeBag) - // username - $authorUsername - .map { text -> String in - guard let text = text else { return "" } - return "@\(text)" - } - .sink { username in - cell.subTitleLabel.text = username - } - .store(in: &disposeBag) - // button - Publishers.CombineLatest( - $isFollowing, - $isPending - ) - .sink { isFollowing, isPending in - let isFollowState = isFollowing || isPending - let imageName = isFollowState ? "minus.circle.fill" : "plus.circle" - let image = UIImage(systemName: imageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .regular)) - cell.button.setImage(image, for: .normal) - cell.button.tintColor = isFollowState ? Asset.Colors.danger.color : Asset.Colors.Label.secondary.color - } - .store(in: &disposeBag) - } -} - -extension SuggestionAccountTableViewCell { func configure(user: MastodonUser) { // author avatar Publishers.CombineLatest( @@ -138,4 +74,62 @@ extension SuggestionAccountTableViewCell { .assign(to: \.isPending, on: viewModel) .store(in: &disposeBag) } + + class ViewModel { + var disposeBag = Set() + + @Published public var userIdentifier: UserIdentifier? // me + + @Published var avatarImageURL: URL? + @Published public var authorName: MetaContent? + @Published public var authorUsername: String? + + @Published var isFollowing = false + @Published var isPending = false + + func prepareForReuse() { + isFollowing = false + isPending = false + } + func bind(cell: SuggestionAccountTableViewCell) { + // avatar + $avatarImageURL.removeDuplicates() + .sink { url in + let configuration = AvatarImageView.Configuration(url: url) + cell.avatarButton.avatarImageView.configure(configuration: configuration) + cell.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12))) + } + .store(in: &disposeBag) + // name + $authorName + .sink { metaContent in + let metaContent = metaContent ?? PlaintextMetaContent(string: " ") + cell.titleLabel.configure(content: metaContent) + } + .store(in: &disposeBag) + // username + $authorUsername + .map { text -> String in + guard let text = text else { return "" } + return "@\(text)" + } + .sink { username in + cell.subTitleLabel.text = username + } + .store(in: &disposeBag) + // button + Publishers.CombineLatest( + $isFollowing, + $isPending + ) + .sink { isFollowing, isPending in + let isFollowState = isFollowing || isPending + let imageName = isFollowState ? "minus.circle.fill" : "plus.circle" + let image = UIImage(systemName: imageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .regular)) + cell.button.setImage(image, for: .normal) + cell.button.tintColor = isFollowState ? Asset.Colors.danger.color : Asset.Colors.Label.secondary.color + } + .store(in: &disposeBag) + } + } } diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift index d259ebed4..b65cdd193 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift @@ -5,7 +5,6 @@ // Created by sxiaojian on 2021/4/21. // -import os.log import Combine import CoreData import CoreDataStack @@ -23,9 +22,7 @@ protocol SuggestionAccountTableViewCellDelegate: AnyObject { } final class SuggestionAccountTableViewCell: UITableViewCell { - - let logger = Logger(subsystem: "SuggestionAccountTableViewCell", category: "View") - + var disposeBag = Set() weak var delegate: SuggestionAccountTableViewCellDelegate? @@ -35,7 +32,8 @@ final class SuggestionAccountTableViewCell: UITableViewCell { viewModel.bind(cell: self) return viewModel }() - + + //TODO: Replace this with user view let avatarButton = AvatarButton() let titleLabel = MetaLabel(style: .statusName) @@ -49,7 +47,6 @@ final class SuggestionAccountTableViewCell: UITableViewCell { let buttonContainer: UIView = { let view = UIView() - view.backgroundColor = .clear return view }() @@ -89,6 +86,8 @@ final class SuggestionAccountTableViewCell: UITableViewCell { extension SuggestionAccountTableViewCell { private func configure() { + + backgroundColor = .systemBackground let containerStackView = UIStackView() containerStackView.axis = .horizontal containerStackView.distribution = .fill @@ -147,7 +146,6 @@ extension SuggestionAccountTableViewCell { extension SuggestionAccountTableViewCell { @objc private func buttonDidPressed(_ sender: UIButton) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") delegate?.suggestionAccountTableViewCell(self, friendshipDidPressed: sender) } } From b3309b4bd0adc6cdadd5f7f8c4b4d6dfc63a9864 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 12 May 2023 22:07:37 +0200 Subject: [PATCH 04/29] Show only five accounts (IOS-157) --- .../Scene/SuggestionAccount/SuggestionAccountViewModel.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index 393474ce5..42f18ab4c 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -11,7 +11,6 @@ import CoreDataStack import GameplayKit import MastodonSDK import MastodonCore -import os.log import UIKit protocol SuggestionAccountViewModelDelegate: AnyObject { @@ -53,7 +52,7 @@ final class SuggestionAccountViewModel: NSObject { var userIDs: [MastodonUser.ID] = [] do { let response = try await context.apiService.suggestionAccountV2( - query: nil, + query: .init(limit: 5), authenticationBox: authContext.mastodonAuthenticationBox ) userIDs = response.value.map { $0.account.id } @@ -64,7 +63,7 @@ final class SuggestionAccountViewModel: NSObject { ) userIDs = response.value.map { $0.id } } catch { - os_log("%{public}s[%{public}ld], %{public}s: fetch recommendAccountV2 failed. %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription) + } guard !userIDs.isEmpty else { return } From 44f6fc9a5cc90343fc1c6ff1719feb2547c9e22b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 17 May 2023 11:35:02 +0200 Subject: [PATCH 05/29] Add request-follow/pending-states to follow-button (IOS-157, IOS-140) --- .../Search/SearchHistorySection.swift | 7 ++++- .../Diffable/Search/SearchResultSection.swift | 5 +++- Mastodon/Diffable/User/UserSection.swift | 6 ++++- .../Provider/DataSourceFacade+UserView.swift | 21 +++++++++++++++ ...toryUserCollectionViewCell+ViewModel.swift | 27 ++++++++++--------- .../UserTableViewCell+ViewModel.swift | 14 +++++++--- .../MastodonAuthenticationBox.swift | 1 + .../MastodonUI/View/Content/UserView.swift | 17 ++++++++++-- 8 files changed, 77 insertions(+), 21 deletions(-) diff --git a/Mastodon/Diffable/Search/SearchHistorySection.swift b/Mastodon/Diffable/Search/SearchHistorySection.swift index 5d1db9c55..813c5b59a 100644 --- a/Mastodon/Diffable/Search/SearchHistorySection.swift +++ b/Mastodon/Diffable/Search/SearchHistorySection.swift @@ -32,7 +32,12 @@ extension SearchHistorySection { guard let user = item.object(in: context.managedObjectContext) else { return } cell.configure( me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user, - viewModel: .init(value: user, followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher()), + viewModel: SearchHistoryUserCollectionViewCell.ViewModel( + value: user, + followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), + blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), + followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher() + ), delegate: configuration.searchHistorySectionHeaderCollectionReusableViewDelegate ) } diff --git a/Mastodon/Diffable/Search/SearchResultSection.swift b/Mastodon/Diffable/Search/SearchResultSection.swift index 2a6e92097..90560150e 100644 --- a/Mastodon/Diffable/Search/SearchResultSection.swift +++ b/Mastodon/Diffable/Search/SearchResultSection.swift @@ -53,7 +53,10 @@ extension SearchResultSection { authContext: authContext, tableView: tableView, cell: cell, - viewModel: .init(value: .user(user), followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher()), + viewModel: UserTableViewCell.ViewModel(value: .user(user), + followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), + blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), + followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()), configuration: configuration ) } diff --git a/Mastodon/Diffable/User/UserSection.swift b/Mastodon/Diffable/User/UserSection.swift index e45c1fa1f..cbad1ff72 100644 --- a/Mastodon/Diffable/User/UserSection.swift +++ b/Mastodon/Diffable/User/UserSection.swift @@ -48,7 +48,11 @@ extension UserSection { authContext: authContext, tableView: tableView, cell: cell, - viewModel: .init(value: .user(user), followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher()), + viewModel: UserTableViewCell.ViewModel(value: .user(user), + followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), + blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), + followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher() + ), configuration: configuration ) } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift index f0a1a51a4..8b1a5c84d 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift @@ -22,6 +22,17 @@ extension DataSourceFacade { if let userObject = user.object(in: dependency.context.managedObjectContext) { dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds.append(userObject.id) } + + case .request: + try await DataSourceFacade.responseToUserFollowAction( + dependency: dependency, + user: user + ) + + if let userObject = user.object(in: dependency.context.managedObjectContext) { + dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs.append(userObject.id) + } + case .unfollow: try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, @@ -39,6 +50,16 @@ extension DataSourceFacade { if let userObject = user.object(in: dependency.context.managedObjectContext) { dependency.authContext.mastodonAuthenticationBox.inMemoryCache.blockedUserIds.append(userObject.id) } + + case .pending: + try await DataSourceFacade.responseToUserFollowAction( + dependency: dependency, + user: user + ) + + if let userObject = user.object(in: dependency.context.managedObjectContext) { + dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs.removeAll(where: { $0 == userObject.id }) + } case .none, .loading: break //no-op } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift index c9f66206c..e31f050bd 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift @@ -16,10 +16,12 @@ extension SearchHistoryUserCollectionViewCell { let followedUsers: AnyPublisher<[String], Never> let blockedUsers: AnyPublisher<[String], Never> - - init(value: MastodonUser, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>) { + let followRequestedUsers: AnyPublisher<[String], Never> + + init(value: MastodonUser, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) { self.value = value self.followedUsers = followedUsers + self.followRequestedUsers = followRequestedUsers self.blockedUsers = blockedUsers } } @@ -45,25 +47,26 @@ extension SearchHistoryUserCollectionViewCell { userView.setButtonState(.loading) } - Publishers.CombineLatest( + Publishers.CombineLatest3( viewModel.followedUsers, + viewModel.followRequestedUsers, viewModel.blockedUsers ) .receive(on: DispatchQueue.main) - .sink { [weak self] followed, blocked in - if blocked.contains(where: { $0 == user.id }) { + .sink { [weak self] followed, requested, blocked in + if blocked.contains(user.id) { self?.userView.setButtonState(.blocked) - } else if followed.contains(where: { $0 == user.id }) { + } else if followed.contains(user.id) { self?.userView.setButtonState(.unfollow) - } else { + } else if requested.contains(user.id) { + self?.userView.setButtonState(.pending) + } else if user.locked { + self?.userView.setButtonState(.request) + } else if user != me { self?.userView.setButtonState(.follow) } - - self?.setNeedsLayout() - self?.setNeedsUpdateConstraints() - self?.layoutIfNeeded() } .store(in: &_disposeBag) - + } } diff --git a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift index 2defc8296..b83b8b47d 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift @@ -16,10 +16,12 @@ extension UserTableViewCell { let followedUsers: AnyPublisher<[String], Never> let blockedUsers: AnyPublisher<[String], Never> + let followRequestedUsers: AnyPublisher<[String], Never> - init(value: Value, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>) { + init(value: Value, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) { self.value = value self.followedUsers = followedUsers + self.followRequestedUsers = followRequestedUsers self.blockedUsers = blockedUsers } @@ -52,20 +54,24 @@ extension UserTableViewCell { userView.setButtonState(.loading) } - Publishers.CombineLatest( + Publishers.CombineLatest3( viewModel.followedUsers, + viewModel.followRequestedUsers, viewModel.blockedUsers ) .receive(on: DispatchQueue.main) - .sink { [weak self] followed, blocked in + .sink { [weak self] followed, requested, blocked in if blocked.contains(user.id) { self?.userView.setButtonState(.blocked) } else if followed.contains(user.id) { self?.userView.setButtonState(.unfollow) + } else if requested.contains(user.id) { + self?.userView.setButtonState(.pending) + } else if user.locked { + self?.userView.setButtonState(.request) } else if user != me { self?.userView.setButtonState(.follow) } - } .store(in: &disposeBag) diff --git a/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift b/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift index 85dc666f2..e35264ed1 100644 --- a/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift +++ b/MastodonSDK/Sources/MastodonCore/Authentication/MastodonAuthenticationBox.swift @@ -52,6 +52,7 @@ extension MastodonAuthenticationBox { public class MastodonAccountInMemoryCache { @Published public var followingUserIds: [String] = [] @Published public var blockedUserIds: [String] = [] + @Published public var followRequestedUserIDs: [String] = [] static var sharedCaches = [String: MastodonAccountInMemoryCache]() diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift index 3ee4c1973..2c6ed36b9 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift @@ -20,7 +20,7 @@ public protocol UserViewDelegate: AnyObject { public final class UserView: UIView { public enum ButtonState { - case none, loading, follow, unfollow, blocked + case none, loading, follow, request, pending, unfollow, blocked } private var currentButtonState: ButtonState = .none @@ -291,6 +291,7 @@ public extension UserView { prepareButtonStateLayout(for: state) switch state { + case .loading: followButton.isHidden = false followButton.setTitle(nil, for: .normal) @@ -301,7 +302,19 @@ public extension UserView { followButton.setTitle(L10n.Common.Controls.Friendship.follow, for: .normal) followButton.setBackgroundColor(Asset.Colors.Button.userFollow.color, for: .normal) followButton.setTitleColor(.white, for: .normal) - + + case .request: + followButton.isHidden = false + followButton.setTitle(L10n.Common.Controls.Friendship.request, for: .normal) + followButton.setBackgroundColor(Asset.Colors.Button.userFollow.color, for: .normal) + followButton.setTitleColor(.white, for: .normal) + + case .pending: + followButton.isHidden = false + followButton.setTitle(L10n.Common.Controls.Friendship.pending, for: .normal) + followButton.setTitleColor(Asset.Colors.Button.userFollowingTitle.color, for: .normal) + followButton.setBackgroundColor(Asset.Colors.Button.userFollowing.color, for: .normal) + case .unfollow: followButton.isHidden = false followButton.setTitle(L10n.Common.Controls.Friendship.following, for: .normal) From a30c77c7831a31c1885c2eee43835acc6157cc21 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 17 May 2023 17:23:19 +0200 Subject: [PATCH 06/29] Use UserView etc. to present suggested people to follow (IOS-157) --- .../RecommendAccountSection.swift | 1 - .../SuggestionAccountViewController.swift | 5 +- ...estionAccountTableViewCell+ViewModel.swift | 126 ++----------- .../SuggestionAccountTableViewCell.swift | 171 +++++------------- 4 files changed, 60 insertions(+), 243 deletions(-) diff --git a/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift b/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift index 7d14aa4af..8a913609d 100644 --- a/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift +++ b/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift @@ -40,7 +40,6 @@ extension RecommendAccountSection { cell.configure(user: user) } - cell.viewModel.userIdentifier = configuration.authContext.mastodonAuthenticationBox cell.delegate = configuration.suggestionAccountTableViewCellDelegate } return cell diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 6d37a3f88..6d520360b 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -31,7 +31,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { }() //TODO: Add "follow all"-footer-cell - override func viewDidLoad() { super.viewDidLoad() @@ -106,7 +105,6 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat switch item { case .account(let user): Task { @MainActor in - cell.startAnimating() do { try await DataSourceFacade.responseToUserFollowAction( dependency: self, @@ -115,8 +113,7 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat } catch { // do noting } - cell.stopAnimating() - } // end Task + } } } } diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift index 828449c6d..05ed2c13d 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift @@ -17,119 +17,21 @@ import Meta extension SuggestionAccountTableViewCell { func configure(user: MastodonUser) { - // author avatar - Publishers.CombineLatest( - user.publisher(for: \.avatar), - UserDefaults.shared.publisher(for: \.preferredStaticAvatar) - ) - .map { _ in user.avatarImageURL() } - .assign(to: \.avatarImageURL, on: viewModel) - .store(in: &disposeBag) - // author name - Publishers.CombineLatest( - user.publisher(for: \.displayName), - user.publisher(for: \.emojis) - ) - .map { _, emojis in - do { - let content = MastodonContent(content: user.displayNameWithFallback, emojis: emojis.asDictionary) - let metaContent = try MastodonMetaContent.convert(document: content) - return metaContent - } catch { - assertionFailure(error.localizedDescription) - return PlaintextMetaContent(string: user.displayNameWithFallback) - } - } - .assign(to: \.authorName, on: viewModel) - .store(in: &disposeBag) - // author username - user.publisher(for: \.acct) - .map { $0 as String? } - .assign(to: \.authorUsername, on: viewModel) - .store(in: &disposeBag) - // isFollowing - Publishers.CombineLatest( - viewModel.$userIdentifier, - user.publisher(for: \.followingBy) - ) - .map { userIdentifier, followingBy in - guard let userIdentifier = userIdentifier else { return false } - return followingBy.contains(where: { - $0.id == userIdentifier.userID && $0.domain == userIdentifier.domain - }) - } - .assign(to: \.isFollowing, on: viewModel) - .store(in: &disposeBag) - // isPending - Publishers.CombineLatest( - viewModel.$userIdentifier, - user.publisher(for: \.followRequestedBy) - ) - .map { userIdentifier, followRequestedBy in - guard let userIdentifier = userIdentifier else { return false } - return followRequestedBy.contains(where: { - $0.id == userIdentifier.userID && $0.domain == userIdentifier.domain - }) - } - .assign(to: \.isPending, on: viewModel) - .store(in: &disposeBag) - } + //TODO: Set Delegate + userView.configure(user: user, delegate: nil) + //TODO: Fix Button State + userView.setButtonState(.follow) - class ViewModel { - var disposeBag = Set() - - @Published public var userIdentifier: UserIdentifier? // me - - @Published var avatarImageURL: URL? - @Published public var authorName: MetaContent? - @Published public var authorUsername: String? - - @Published var isFollowing = false - @Published var isPending = false - - func prepareForReuse() { - isFollowing = false - isPending = false - } - func bind(cell: SuggestionAccountTableViewCell) { - // avatar - $avatarImageURL.removeDuplicates() - .sink { url in - let configuration = AvatarImageView.Configuration(url: url) - cell.avatarButton.avatarImageView.configure(configuration: configuration) - cell.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12))) - } - .store(in: &disposeBag) - // name - $authorName - .sink { metaContent in - let metaContent = metaContent ?? PlaintextMetaContent(string: " ") - cell.titleLabel.configure(content: metaContent) - } - .store(in: &disposeBag) - // username - $authorUsername - .map { text -> String in - guard let text = text else { return "" } - return "@\(text)" - } - .sink { username in - cell.subTitleLabel.text = username - } - .store(in: &disposeBag) - // button - Publishers.CombineLatest( - $isFollowing, - $isPending - ) - .sink { isFollowing, isPending in - let isFollowState = isFollowing || isPending - let imageName = isFollowState ? "minus.circle.fill" : "plus.circle" - let image = UIImage(systemName: imageName, withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .regular)) - cell.button.setImage(image, for: .normal) - cell.button.tintColor = isFollowState ? Asset.Colors.danger.color : Asset.Colors.Label.secondary.color + let metaContent: MetaContent = { + do { + let mastodonContent = MastodonContent(content: user.note ?? "", emojis: [:]) + return try MastodonMetaContent.convert(document: mastodonContent) + } catch { + assertionFailure() + return PlaintextMetaContent(string: user.note ?? "") } - .store(in: &disposeBag) - } + } () + + bioMetaLabel.configure(content: metaContent) } } diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift index b65cdd193..c721d3790 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift @@ -24,144 +24,63 @@ protocol SuggestionAccountTableViewCellDelegate: AnyObject { final class SuggestionAccountTableViewCell: UITableViewCell { var disposeBag = Set() - weak var delegate: SuggestionAccountTableViewCellDelegate? - public private(set) lazy var viewModel: ViewModel = { - let viewModel = ViewModel() - viewModel.bind(cell: self) - return viewModel - }() + let userView: UserView + let bioMetaLabel: MetaLabel + private let contentStackView: UIStackView - //TODO: Replace this with user view - let avatarButton = AvatarButton() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + + userView = UserView() + bioMetaLabel = MetaLabel() + bioMetaLabel.numberOfLines = 0 + bioMetaLabel.textAttributes = [ + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), + .foregroundColor: UIColor.secondaryLabel + ] + bioMetaLabel.linkAttributes = [ + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)), + .foregroundColor: Asset.Colors.brand.color + ] + bioMetaLabel.isUserInteractionEnabled = false + + contentStackView = UIStackView(arrangedSubviews: [userView, bioMetaLabel]) + contentStackView.translatesAutoresizingMaskIntoConstraints = false + contentStackView.alignment = .leading + contentStackView.axis = .vertical + + super.init(style: style, reuseIdentifier: reuseIdentifier) + + contentView.addSubview(contentStackView) + + backgroundColor = .systemBackground + + setupConstraints() + } - let titleLabel = MetaLabel(style: .statusName) - - let subTitleLabel: UILabel = { - let label = UILabel() - label.textColor = Asset.Colors.Label.secondary.color - label.font = .preferredFont(forTextStyle: .body) - return label - }() - - let buttonContainer: UIView = { - let view = UIView() - return view - }() - - let button: HighlightDimmableButton = { - let button = HighlightDimmableButton(type: .custom) - let image = UIImage(systemName: "plus.circle", withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .regular)) - button.setImage(image, for: .normal) - return button - }() - - let activityIndicatorView: UIActivityIndicatorView = { - let activityIndicatorView = UIActivityIndicatorView(style: .medium) - activityIndicatorView.hidesWhenStopped = true - return activityIndicatorView - }() + required init?(coder: NSCoder) { fatalError("We don't support ancient technology like Storyboards") } + + private func setupConstraints() { + let constraints = [ + contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + contentStackView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16), + ] + + NSLayoutConstraint.activate(constraints) + } override func prepareForReuse() { super.prepareForReuse() - + disposeBag.removeAll() - avatarButton.avatarImageView.prepareForReuse() - viewModel.prepareForReuse() - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - configure() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - configure() } -} + //MARK: - Action -extension SuggestionAccountTableViewCell { - - private func configure() { - - backgroundColor = .systemBackground - let containerStackView = UIStackView() - containerStackView.axis = .horizontal - containerStackView.distribution = .fill - containerStackView.spacing = 12 - containerStackView.layoutMargins = UIEdgeInsets(top: 12, left: 21, bottom: 12, right: 12) - containerStackView.isLayoutMarginsRelativeArrangement = true - containerStackView.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(containerStackView) - containerStackView.pinToParent() - - avatarButton.translatesAutoresizingMaskIntoConstraints = false - containerStackView.addArrangedSubview(avatarButton) - NSLayoutConstraint.activate([ - avatarButton.widthAnchor.constraint(equalToConstant: 42).priority(.required - 1), - avatarButton.heightAnchor.constraint(equalToConstant: 42).priority(.required - 1), - ]) - - let textStackView = UIStackView() - textStackView.axis = .vertical - textStackView.distribution = .fill - textStackView.alignment = .leading - textStackView.translatesAutoresizingMaskIntoConstraints = false - titleLabel.translatesAutoresizingMaskIntoConstraints = false - textStackView.addArrangedSubview(titleLabel) - subTitleLabel.translatesAutoresizingMaskIntoConstraints = false - textStackView.addArrangedSubview(subTitleLabel) - subTitleLabel.setContentHuggingPriority(.defaultLow - 1, for: .vertical) - - containerStackView.addArrangedSubview(textStackView) - textStackView.setContentHuggingPriority(.defaultLow - 1, for: .horizontal) - - buttonContainer.translatesAutoresizingMaskIntoConstraints = false - containerStackView.addArrangedSubview(buttonContainer) - NSLayoutConstraint.activate([ - buttonContainer.widthAnchor.constraint(equalToConstant: 24).priority(.required - 1), - buttonContainer.heightAnchor.constraint(equalToConstant: 42).priority(.required - 1), - ]) - buttonContainer.setContentHuggingPriority(.required - 1, for: .horizontal) - - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - button.translatesAutoresizingMaskIntoConstraints = false - activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - buttonContainer.addSubview(button) - buttonContainer.addSubview(activityIndicatorView) - NSLayoutConstraint.activate([ - buttonContainer.centerXAnchor.constraint(equalTo: activityIndicatorView.centerXAnchor), - buttonContainer.centerYAnchor.constraint(equalTo: activityIndicatorView.centerYAnchor), - buttonContainer.centerXAnchor.constraint(equalTo: button.centerXAnchor), - buttonContainer.centerYAnchor.constraint(equalTo: button.centerYAnchor), - ]) - - button.addTarget(self, action: #selector(SuggestionAccountTableViewCell.buttonDidPressed(_:)), for: .touchUpInside) - } - -} - -extension SuggestionAccountTableViewCell { @objc private func buttonDidPressed(_ sender: UIButton) { delegate?.suggestionAccountTableViewCell(self, friendshipDidPressed: sender) } } - -extension SuggestionAccountTableViewCell { - - func startAnimating() { - activityIndicatorView.isHidden = false - activityIndicatorView.startAnimating() - button.isHidden = true - } - - func stopAnimating() { - activityIndicatorView.stopAnimating() - activityIndicatorView.isHidden = true - button.isHidden = false - } - -} From 58e568646090c1aaf9444ad17a443b3fa3696703 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 19 May 2023 14:45:08 +0200 Subject: [PATCH 07/29] Extract follow-button into its own file (IOS-157) --- .../View/Content/FollowButton.swift | 31 +++++++++++++++++++ .../MastodonUI/View/Content/UserView.swift | 27 ---------------- 2 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonUI/View/Content/FollowButton.swift diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FollowButton.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FollowButton.swift new file mode 100644 index 000000000..ff82aa3cb --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FollowButton.swift @@ -0,0 +1,31 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonAsset + +public final class FollowButton: RoundedEdgesButton { + + public init() { + super.init(frame: .zero) + configureAppearance() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configureAppearance() { + setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal) + setTitleColor(Asset.Colors.Label.primaryReverse.color.withAlphaComponent(0.5), for: .highlighted) + switch traitCollection.userInterfaceStyle { + case .dark: + setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundDark.color), for: .normal) + setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedDark.color), for: .highlighted) + setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedDark.color), for: .disabled) + default: + setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundLight.color), for: .normal) + setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .highlighted) + setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .disabled) + } + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift index 2c6ed36b9..1593b889f 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift @@ -238,33 +238,6 @@ extension UserView { } -private final class FollowButton: RoundedEdgesButton { - - init() { - super.init(frame: .zero) - configureAppearance() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func configureAppearance() { - setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal) - setTitleColor(Asset.Colors.Label.primaryReverse.color.withAlphaComponent(0.5), for: .highlighted) - switch traitCollection.userInterfaceStyle { - case .dark: - setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundDark.color), for: .normal) - setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedDark.color), for: .highlighted) - setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedDark.color), for: .disabled) - default: - setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundLight.color), for: .normal) - setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .highlighted) - setBackgroundImage(.placeholder(color: Asset.Scene.Profile.RelationshipButton.backgroundHighlightedLight.color), for: .disabled) - } - } -} - public extension UserView { private func prepareButtonStateLayout(for state: ButtonState) { switch state { From cfc60e6721ca807acc57d1b28a539ed89e7ebc4c Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 19 May 2023 14:51:22 +0200 Subject: [PATCH 08/29] Add footer-view to follow all suggested accounts (IOS-157) - Also: Rename folder as this contains not only the cell anymore - This is just UI, follow-function is still missing --- Mastodon.xcodeproj/project.pbxproj | 10 ++- .../SuggestionAccountViewController.swift | 21 ++++++- ...estionAccountTableViewCell+ViewModel.swift | 0 .../SuggestionAccountTableViewCell.swift | 2 + .../SuggestionAccountTableViewFooter.swift | 61 +++++++++++++++++++ 5 files changed, 89 insertions(+), 5 deletions(-) rename Mastodon/Scene/SuggestionAccount/{TableViewCell => TableView-Components}/SuggestionAccountTableViewCell+ViewModel.swift (100%) rename Mastodon/Scene/SuggestionAccount/{TableViewCell => TableView-Components}/SuggestionAccountTableViewCell.swift (97%) create mode 100644 Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 15b8ff836..16e743e08 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -146,6 +146,7 @@ D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */; }; D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; }; D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; + D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */; }; D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; }; D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; }; D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */; }; @@ -795,6 +796,7 @@ D8A6FE6429325F5900666A47 /* StringsConvertor */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = StringsConvertor; sourceTree = ""; }; D8A6FE6529325F5900666A47 /* ios-infoPlist.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "ios-infoPlist.json"; sourceTree = ""; }; D8A6FE6629325F5900666A47 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; + D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewFooter.swift; sourceTree = ""; }; D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = ""; }; D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = ""; }; D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagIntentHandler.swift; sourceTree = ""; }; @@ -1671,18 +1673,19 @@ 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */, 2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */, DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */, - 2DAC9E43262FC9DE0062E1A6 /* TableViewCell */, + 2DAC9E43262FC9DE0062E1A6 /* TableView-Components */, ); path = SuggestionAccount; sourceTree = ""; }; - 2DAC9E43262FC9DE0062E1A6 /* TableViewCell */ = { + 2DAC9E43262FC9DE0062E1A6 /* TableView-Components */ = { isa = PBXGroup; children = ( 2DAC9E45262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift */, DBD5B1F527BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift */, + D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */, ); - path = TableViewCell; + path = "TableView-Components"; sourceTree = ""; }; 2DE0FAC62615F5D200CDF649 /* View */ = { @@ -3538,6 +3541,7 @@ DBE3CDCF261C42ED00430CC6 /* TimelineHeaderView.swift in Sources */, 62FD27D32893707B00B205C5 /* BookmarkViewController+DataSourceProvider.swift in Sources */, DB1D843426579931000346B3 /* TableViewControllerNavigateable.swift in Sources */, + D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */, 0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */, DB65C63727A2AF6C008BAC2E /* ReportItem.swift in Sources */, DB5B54B22833C24B00DEF8B2 /* RebloggedByViewController+DataSourceProvider.swift in Sources */, diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 6d520360b..1ea960086 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -25,8 +25,9 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { let tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .insetGrouped) - tableView.register(SuggestionAccountTableViewCell.self, forCellReuseIdentifier: String(describing: SuggestionAccountTableViewCell.self)) - tableView.separatorStyle = .none + tableView.register(SuggestionAccountTableViewCell.self, forCellReuseIdentifier: SuggestionAccountTableViewCell.reuseIdentifier) + // we're lazy, that's why we don't put the Footer in tableViewFooter + tableView.register(SuggestionAccountTableViewFooter.self, forHeaderFooterViewReuseIdentifier: SuggestionAccountTableViewFooter.reuseIdentifier) return tableView }() @@ -85,6 +86,15 @@ extension SuggestionAccountViewController: UITableViewDelegate { ) } } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + guard let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SuggestionAccountTableViewFooter.reuseIdentifier) as? SuggestionAccountTableViewFooter else { + return nil + } + + footerView.delegate = self + return footerView + } } // MARK: - AuthContextProvider @@ -123,3 +133,10 @@ extension SuggestionAccountViewController { dismiss(animated: true, completion: nil) } } + +extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDelegate { + func followAll(_ footerView: SuggestionAccountTableViewFooter) { + // get all five suggested accounts aka user + // follow all of them + } +} diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift similarity index 100% rename from Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell+ViewModel.swift rename to Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift diff --git a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift similarity index 97% rename from Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift rename to Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index c721d3790..21f2e74a3 100644 --- a/Mastodon/Scene/SuggestionAccount/TableViewCell/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -23,6 +23,8 @@ protocol SuggestionAccountTableViewCellDelegate: AnyObject { final class SuggestionAccountTableViewCell: UITableViewCell { + static let reuseIdentifier = "SuggestionAccountTableViewCell" + var disposeBag = Set() weak var delegate: SuggestionAccountTableViewCellDelegate? diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift new file mode 100644 index 000000000..294ba3285 --- /dev/null +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift @@ -0,0 +1,61 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonUI +import MastodonAsset + +protocol SuggestionAccountTableViewFooterDelegate: AnyObject { + func followAll(_ footerView: SuggestionAccountTableViewFooter) +} + +class SuggestionAccountTableViewFooter: UITableViewHeaderFooterView { + static let reuseIdentifier = "SuggestionAccountTableViewFooter" + + weak var delegate: SuggestionAccountTableViewFooterDelegate? + + let followAllButton: FollowButton + + override init(reuseIdentifier: String?) { + + //TODO: Check if we can use UIButton.configuration here instead? + followAllButton = FollowButton() + followAllButton.translatesAutoresizingMaskIntoConstraints = false + followAllButton.setTitle("Follow All", for: .normal) + followAllButton.setBackgroundColor(Asset.Colors.Button.userFollow.color, for: .normal) + followAllButton.setTitleColor(.white, for: .normal) + followAllButton.contentEdgeInsets = .init(horizontal: 20, vertical: 12) + followAllButton.cornerRadius = 10 + followAllButton.titleLabel?.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .boldSystemFont(ofSize: 15)) + + followAllButton.setContentCompressionResistancePriority(.required, for: .horizontal) + followAllButton.setContentHuggingPriority(.required, for: .horizontal) + + super.init(reuseIdentifier: reuseIdentifier) + + contentView.addSubview(followAllButton) + setupConstraints() + + followAllButton.addTarget(self, action: #selector(SuggestionAccountTableViewFooter.followAll(_:)), for: .touchUpInside) + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + let constraints = [ + followAllButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), + followAllButton.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: followAllButton.trailingAnchor), + contentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: followAllButton.bottomAnchor, constant: 16), + + followAllButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 96), + followAllButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 36), + ] + + NSLayoutConstraint.activate(constraints) + } + + //MARK: - Actions + @objc func followAll(_ sender: UIButton) { + delegate?.followAll(self) + } +} From 9b0e503603a067ecede2862b6c7680e2c29b7da6 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 19 May 2023 16:34:21 +0200 Subject: [PATCH 09/29] Minor UI-fixes (IOS-157) - NavigationBar - ContentInset for TableView --- .../SuggestionAccountViewController.swift | 8 +++++++- .../SuggestionAccountTableViewCell.swift | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 1ea960086..43a22f21d 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -28,11 +28,15 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { tableView.register(SuggestionAccountTableViewCell.self, forCellReuseIdentifier: SuggestionAccountTableViewCell.reuseIdentifier) // we're lazy, that's why we don't put the Footer in tableViewFooter tableView.register(SuggestionAccountTableViewFooter.self, forHeaderFooterViewReuseIdentifier: SuggestionAccountTableViewFooter.reuseIdentifier) + tableView.contentInset.top = 16 return tableView }() - //TODO: Add "follow all"-footer-cell override func viewDidLoad() { + + setupNavigationBarAppearance() + defer { setupNavigationBarBackgroundView() } + super.viewDidLoad() title = L10n.Scene.SuggestionAccount.title @@ -140,3 +144,5 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDeleg // follow all of them } } + +extension SuggestionAccountViewController: OnboardingViewControllerAppearance { } diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index 21f2e74a3..3e07bff6c 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -39,7 +39,7 @@ final class SuggestionAccountTableViewCell: UITableViewCell { bioMetaLabel.numberOfLines = 0 bioMetaLabel.textAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), - .foregroundColor: UIColor.secondaryLabel + .foregroundColor: UIColor.label ] bioMetaLabel.linkAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold)), From f1777376f44f25a4e1145d45646cf1d722bc1406 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 19 May 2023 16:39:51 +0200 Subject: [PATCH 10/29] Update title for suggestion-screen (IOS-157) --- Localization/app.json | 3 +-- .../Sources/MastodonLocalization/Generated/Strings.swift | 6 ++---- .../Resources/Base.lproj/Localizable.strings | 5 ++--- .../Resources/en.lproj/Localizable.strings | 5 ++--- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index fdac63c88..c6c1f4b27 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -464,8 +464,7 @@ } }, "suggestion_account": { - "title": "Find People to Follow", - "follow_explain": "When you follow someone, you’ll see their posts in your home feed." + "title": "Popular on Mastodon" }, "compose": { "title": { diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index ef29f91d0..a2530fc2c 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1484,10 +1484,8 @@ public enum L10n { } } public enum SuggestionAccount { - /// When you follow someone, you’ll see their posts in your home feed. - public static let followExplain = L10n.tr("Localizable", "Scene.SuggestionAccount.FollowExplain", fallback: "When you follow someone, you’ll see their posts in your home feed.") - /// Find People to Follow - public static let title = L10n.tr("Localizable", "Scene.SuggestionAccount.Title", fallback: "Find People to Follow") + /// Popular on Mastodon + public static let title = L10n.tr("Localizable", "Scene.SuggestionAccount.Title", fallback: "Popular on Mastodon") } public enum Thread { /// Post diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 6120302c9..09f28475d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -517,8 +517,7 @@ uploaded to Mastodon."; "Scene.Settings.Section.SpicyZone.Signout" = "Sign Out"; "Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone"; "Scene.Settings.Title" = "Settings"; -"Scene.SuggestionAccount.FollowExplain" = "When you follow someone, you’ll see their posts in your home feed."; -"Scene.SuggestionAccount.Title" = "Find People to Follow"; +"Scene.SuggestionAccount.Title" = "Popular on Mastodon"; "Scene.Thread.BackTitle" = "Post"; "Scene.Thread.Title" = "Post from %@"; "Scene.Welcome.Education.A11Y.WhatIsMastodon.Title" = "What is Mastodon?"; @@ -552,4 +551,4 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; -"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; \ No newline at end of file +"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 6120302c9..09f28475d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -517,8 +517,7 @@ uploaded to Mastodon."; "Scene.Settings.Section.SpicyZone.Signout" = "Sign Out"; "Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone"; "Scene.Settings.Title" = "Settings"; -"Scene.SuggestionAccount.FollowExplain" = "When you follow someone, you’ll see their posts in your home feed."; -"Scene.SuggestionAccount.Title" = "Find People to Follow"; +"Scene.SuggestionAccount.Title" = "Popular on Mastodon"; "Scene.Thread.BackTitle" = "Post"; "Scene.Thread.Title" = "Post from %@"; "Scene.Welcome.Education.A11Y.WhatIsMastodon.Title" = "What is Mastodon?"; @@ -552,4 +551,4 @@ uploaded to Mastodon."; "Widget.MultipleFollowers.ConfigurationDescription" = "Show number of followers for multiple accounts."; "Widget.MultipleFollowers.ConfigurationDisplayName" = "Multiple followers"; "Widget.MultipleFollowers.MockUser.AccountName" = "another@follower.social"; -"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; \ No newline at end of file +"Widget.MultipleFollowers.MockUser.DisplayName" = "Another follower"; From e4578686e3517e00f7fd1f37ff0c8f27888971e0 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 11:41:42 +0200 Subject: [PATCH 11/29] Follow all suggested user accounts (IOS-157) --- Mastodon.xcodeproj/project.pbxproj | 16 +------ .../RecommendAccountItem.swift | 0 .../RecommendAccountSection.swift | 0 .../SuggestionAccountViewController.swift | 44 +++++-------------- .../SuggestionAccountViewModel.swift | 16 +++++++ ...estionAccountTableViewCell+ViewModel.swift | 37 ---------------- .../SuggestionAccountTableViewCell.swift | 23 +++++++--- 7 files changed, 47 insertions(+), 89 deletions(-) rename Mastodon/{Diffable/RecommandAccount => Scene/SuggestionAccount}/RecommendAccountItem.swift (100%) rename Mastodon/{Diffable/RecommandAccount => Scene/SuggestionAccount}/RecommendAccountSection.swift (100%) delete mode 100644 Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 16e743e08..3e480ec54 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -439,7 +439,6 @@ DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B2F261440A50045B23D /* UITabBarController.swift */; }; DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */; }; DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376B1269302A4007FEC24 /* UITableViewCell.swift */; }; - DBD5B1F627BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F527BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift */; }; DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */; }; DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */; }; DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */; }; @@ -1164,7 +1163,6 @@ DBCC3B2F261440A50045B23D /* UITabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITabBarController.swift; sourceTree = ""; }; DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedProfileViewModel.swift; sourceTree = ""; }; DBD376B1269302A4007FEC24 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; - DBD5B1F527BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountTableViewCell+ViewModel.swift"; sourceTree = ""; }; DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+TableViewControllerNavigateable.swift"; sourceTree = ""; }; DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+StatusTableViewControllerNavigateable.swift"; sourceTree = ""; }; DBDFF18F2805543100557A48 /* DiscoveryPostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPostsViewController.swift; sourceTree = ""; }; @@ -1615,7 +1613,6 @@ DB4F097826A039B400D62E92 /* Onboarding */, DB0617F827855B170030EE79 /* User */, DB0617F927855B460030EE79 /* Profile */, - DB0FCB892796BE1E006C02E2 /* RecommandAccount */, DB4F097926A039C400D62E92 /* Status */, DB65C63527A2AF52008BAC2E /* Report */, DB0617F727855B010030EE79 /* Notification */, @@ -1670,6 +1667,8 @@ 2DAC9E36262FC20B0062E1A6 /* SuggestionAccount */ = { isa = PBXGroup; children = ( + 2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */, + DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */, 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */, 2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */, DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */, @@ -1682,7 +1681,6 @@ isa = PBXGroup; children = ( 2DAC9E45262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift */, - DBD5B1F527BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift */, D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */, ); path = "TableView-Components"; @@ -1921,15 +1919,6 @@ path = View; sourceTree = ""; }; - DB0FCB892796BE1E006C02E2 /* RecommandAccount */ = { - isa = PBXGroup; - children = ( - 2DE0FACD2615F7AD00CDF649 /* RecommendAccountSection.swift */, - DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */, - ); - path = RecommandAccount; - sourceTree = ""; - }; DB1D187125EF5BBD003F1F23 /* TableView */ = { isa = PBXGroup; children = ( @@ -3597,7 +3586,6 @@ DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */, DB6180F226391CF40018D199 /* MediaPreviewImageViewModel.swift in Sources */, 62FD27D12893707600B205C5 /* BookmarkViewController.swift in Sources */, - DBD5B1F627BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */, DB63F767279A5EB300455B82 /* NotificationTimelineViewModel.swift in Sources */, 2D607AD826242FC500B70763 /* NotificationViewModel.swift in Sources */, DB5B54AB2833C12A00DEF8B2 /* RebloggedByViewController.swift in Sources */, diff --git a/Mastodon/Diffable/RecommandAccount/RecommendAccountItem.swift b/Mastodon/Scene/SuggestionAccount/RecommendAccountItem.swift similarity index 100% rename from Mastodon/Diffable/RecommandAccount/RecommendAccountItem.swift rename to Mastodon/Scene/SuggestionAccount/RecommendAccountItem.swift diff --git a/Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift similarity index 100% rename from Mastodon/Diffable/RecommandAccount/RecommendAccountSection.swift rename to Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 43a22f21d..bd3df4fac 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -72,6 +72,12 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { tableView.deselectRow(with: transitionCoordinator, animated: animated) } + + //MARK: - Actions + + @objc func doneButtonDidClick(_ sender: UIButton) { + dismiss(animated: true, completion: nil) + } } // MARK: - UITableViewDelegate @@ -106,42 +112,16 @@ extension SuggestionAccountViewController: AuthContextProvider { var authContext: AuthContext { viewModel.authContext } } -// MARK: - SuggestionAccountTableViewCellDelegate -extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegate { - func suggestionAccountTableViewCell( - _ cell: SuggestionAccountTableViewCell, - friendshipDidPressed button: UIButton - ) { - guard let tableViewDiffableDataSource = viewModel.tableViewDiffableDataSource else { return } - guard let indexPath = tableView.indexPath(for: cell) else { return } - guard let item = tableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return } - - switch item { - case .account(let user): - Task { @MainActor in - do { - try await DataSourceFacade.responseToUserFollowAction( - dependency: self, - user: user - ) - } catch { - // do noting - } - } - } - } -} +// MARK: - UserTableViewCellDelegate +extension SuggestionAccountViewController: UserTableViewCellDelegate {} + +// MARK: - SuggestionAccountTableViewCellDelegate +extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegate { } -extension SuggestionAccountViewController { - @objc func doneButtonDidClick(_ sender: UIButton) { - dismiss(animated: true, completion: nil) - } -} extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDelegate { func followAll(_ footerView: SuggestionAccountTableViewFooter) { - // get all five suggested accounts aka user - // follow all of them + viewModel.followAllSuggestedAccounts(self) } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index 42f18ab4c..59bf7f55c 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -85,4 +85,20 @@ final class SuggestionAccountViewModel: NSObject { .store(in: &disposeBag) } + func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider ) { + + let userRecords = userFetchedResultsController.records.compactMap { + $0.object(in: dependency.context.managedObjectContext)?.asRecord + } + + userRecords.forEach { user in + Task { + try? await DataSourceFacade.responseToUserViewButtonAction( + dependency: dependency, + user: user, + buttonState: .follow + ) + } + } + } } diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift deleted file mode 100644 index 05ed2c13d..000000000 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// SuggestionAccountTableViewCell+ViewModel.swift -// Mastodon -// -// Created by MainasuK on 2022-2-16. -// - -import UIKit -import Combine -import CoreDataStack -import MastodonAsset -import MastodonCore -import MastodonUI -import MastodonMeta -import Meta - -extension SuggestionAccountTableViewCell { - - func configure(user: MastodonUser) { - //TODO: Set Delegate - userView.configure(user: user, delegate: nil) - //TODO: Fix Button State - userView.setButtonState(.follow) - - let metaContent: MetaContent = { - do { - let mastodonContent = MastodonContent(content: user.note ?? "", emojis: [:]) - return try MastodonMetaContent.convert(document: mastodonContent) - } catch { - assertionFailure() - return PlaintextMetaContent(string: user.note ?? "") - } - } () - - bioMetaLabel.configure(content: metaContent) - } -} diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index 3e07bff6c..f05f87a10 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -16,10 +16,9 @@ import MastodonMeta import MastodonAsset import MastodonLocalization import MastodonUI +import MastodonCore -protocol SuggestionAccountTableViewCellDelegate: AnyObject { - func suggestionAccountTableViewCell(_ cell: SuggestionAccountTableViewCell, friendshipDidPressed button: UIButton) -} +protocol SuggestionAccountTableViewCellDelegate: AnyObject, UserViewDelegate {} final class SuggestionAccountTableViewCell: UITableViewCell { @@ -80,9 +79,21 @@ final class SuggestionAccountTableViewCell: UITableViewCell { disposeBag.removeAll() } - //MARK: - Action + func configure(user: MastodonUser) { + userView.configure(user: user, delegate: delegate) + //TODO: Fix Button State + userView.setButtonState(.follow) - @objc private func buttonDidPressed(_ sender: UIButton) { - delegate?.suggestionAccountTableViewCell(self, friendshipDidPressed: sender) + let metaContent: MetaContent = { + do { + let mastodonContent = MastodonContent(content: user.note ?? "", emojis: [:]) + return try MastodonMetaContent.convert(document: mastodonContent) + } catch { + assertionFailure() + return PlaintextMetaContent(string: user.note ?? "") + } + } () + + bioMetaLabel.configure(content: metaContent) } } From 1cd7bc9105f1658843f35b2ac77a06e15b60f015 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 13:28:18 +0200 Subject: [PATCH 12/29] Make follow work (IOS-157) The secret is to set the delegate before getting the account. This doesn't feel right. --- Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift index 8a913609d..15d259d0e 100644 --- a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift +++ b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift @@ -35,12 +35,13 @@ extension RecommendAccountSection { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell switch item { case .account(let record): + cell.delegate = configuration.suggestionAccountTableViewCellDelegate context.managedObjectContext.performAndWait { guard let user = record.object(in: context.managedObjectContext) else { return } cell.configure(user: user) } - cell.delegate = configuration.suggestionAccountTableViewCellDelegate + } return cell } From a1315b90060b1d0bc507f3cbb712b02ed7fc5779 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 13:30:05 +0200 Subject: [PATCH 13/29] Re-introduce viewModel for each cell and set button-state (IOS-157) We need this viewModel for the button--state of the follow-button. There's still a runtime-issue that resets the button-state to follow. --- Mastodon.xcodeproj/project.pbxproj | 4 ++ .../RecommendAccountSection.swift | 7 +++- ...estionAccountTableViewCell+ViewModel.swift | 22 +++++++++++ .../SuggestionAccountTableViewCell.swift | 37 +++++++++++++++---- 4 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 3e480ec54..ed6afbc58 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -147,6 +147,7 @@ D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; }; D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */; }; + D8BEBCB62A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */; }; D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; }; D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; }; D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */; }; @@ -796,6 +797,7 @@ D8A6FE6529325F5900666A47 /* ios-infoPlist.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "ios-infoPlist.json"; sourceTree = ""; }; D8A6FE6629325F5900666A47 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewFooter.swift; sourceTree = ""; }; + D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountTableViewCell+ViewModel.swift"; sourceTree = ""; }; D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = ""; }; D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = ""; }; D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagIntentHandler.swift; sourceTree = ""; }; @@ -1681,6 +1683,7 @@ isa = PBXGroup; children = ( 2DAC9E45262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift */, + D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */, D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */, ); path = "TableView-Components"; @@ -3853,6 +3856,7 @@ DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */, DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */, DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */, + D8BEBCB62A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */, DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */, DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */, DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */, diff --git a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift index 15d259d0e..70c2a5077 100644 --- a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift +++ b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift @@ -38,7 +38,12 @@ extension RecommendAccountSection { cell.delegate = configuration.suggestionAccountTableViewCellDelegate context.managedObjectContext.performAndWait { guard let user = record.object(in: context.managedObjectContext) else { return } - cell.configure(user: user) + cell.configure(viewModel: + SuggestionAccountTableViewCell.ViewModel(user: user, + followedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), + blockedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), + followRequestedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()) + ) } diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift new file mode 100644 index 000000000..937cb2262 --- /dev/null +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift @@ -0,0 +1,22 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Combine +import MastodonUI +import CoreDataStack + +extension SuggestionAccountTableViewCell { + final class ViewModel { + let user: MastodonUser + + let followedUsers: AnyPublisher<[String], Never> + let blockedUsers: AnyPublisher<[String], Never> + let followRequestedUsers: AnyPublisher<[String], Never> + + init(user: MastodonUser, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) { + self.user = user + self.followedUsers = followedUsers + self.followRequestedUsers = followRequestedUsers + self.blockedUsers = blockedUsers + } + } +} diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index f05f87a10..9b88742ea 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -79,20 +79,43 @@ final class SuggestionAccountTableViewCell: UITableViewCell { disposeBag.removeAll() } - func configure(user: MastodonUser) { - userView.configure(user: user, delegate: delegate) - //TODO: Fix Button State - userView.setButtonState(.follow) + func configure(viewModel: SuggestionAccountTableViewCell.ViewModel) { + userView.configure(user: viewModel.user, delegate: delegate) + Publishers.CombineLatest3( + viewModel.followedUsers, + viewModel.followRequestedUsers, + viewModel.blockedUsers + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] followed, requested, blocked in + + guard let self else { return } + + if blocked.contains(viewModel.user.id) { + self.userView.setButtonState(.blocked) + } else if followed.contains(viewModel.user.id) { + self.userView.setButtonState(.unfollow) + } else if requested.contains(viewModel.user.id) { + self.userView.setButtonState(.pending) + } else if viewModel.user.locked { + self.userView.setButtonState(.request) + } else { + self.userView.setButtonState(.follow) + } + } + .store(in: &disposeBag) + let metaContent: MetaContent = { do { - let mastodonContent = MastodonContent(content: user.note ?? "", emojis: [:]) + //TODO: Add emojis + let mastodonContent = MastodonContent(content: viewModel.user.note ?? "", emojis: [:]) return try MastodonMetaContent.convert(document: mastodonContent) } catch { assertionFailure() - return PlaintextMetaContent(string: user.note ?? "") + return PlaintextMetaContent(string: viewModel.user.note ?? "") } - } () + }() bioMetaLabel.configure(content: metaContent) } From 1a1b2d44a4ce35d2cb4697f126920ea1882d4cd7 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 14:11:57 +0200 Subject: [PATCH 14/29] Download (and cache) requested follows (IOS-157) Thanks to @kimar for pointing this out! --- .../Provider/DataSourceFacade+Follow.swift | 2 +- .../API/APIService+FollowRequest.swift | 15 ++++++- .../Service/AuthenticationService.swift | 7 ++- .../Mastodon+API+Account+FollowRequest.swift | 45 +++++++++++++++++-- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift index 2c221bc9b..88503ae7b 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift @@ -32,7 +32,7 @@ extension DataSourceFacade { static func responseToUserFollowRequestAction( dependency: NeedsDependency & AuthContextProvider, notification: ManagedObjectRecord, - query: Mastodon.API.Account.FollowReqeustQuery + query: Mastodon.API.Account.FollowRequestQuery ) async throws { let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() await selectionFeedbackGenerator.selectionChanged() diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift index f0f0bfb7b..74650131d 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+FollowRequest.swift @@ -16,7 +16,7 @@ extension APIService { public func followRequest( userID: Mastodon.Entity.Account.ID, - query: Mastodon.API.Account.FollowReqeustQuery, + query: Mastodon.API.Account.FollowRequestQuery, authenticationBox: MastodonAuthenticationBox ) async throws -> Mastodon.Response.Content { let response = try await Mastodon.API.Account.followRequest( @@ -51,4 +51,17 @@ extension APIService { return response } + public func pendingFollowRequest( + userID: Mastodon.Entity.Account.ID, + authenticationBox: MastodonAuthenticationBox + ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { + let response = try await Mastodon.API.Account.pendingFollowRequest( + session: session, + domain: authenticationBox.domain, + userID: userID, + authorization: authenticationBox.userAuthorization + ).singleOutput() + + return response + } } diff --git a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift index 933fd1c6c..8faac74d9 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift @@ -45,7 +45,12 @@ public final class AuthenticationService: NSObject { let blockedIds = try await apiService.getBlocked( authenticationBox: authBox ).value.map { $0.id } - + + let followRequestIds = try await apiService.pendingFollowRequest(userID: authBox.userID, + authenticationBox: authBox) + .value.map { $0.id } + + authBox.inMemoryCache.followRequestedUserIDs = followRequestIds authBox.inMemoryCache.followingUserIds = followingIds authBox.inMemoryCache.blockedUserIds = blockedIds } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift index ddde4ffdc..fdf091c86 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account+FollowRequest.swift @@ -10,6 +10,11 @@ import Combine // MARK: - Account credentials extension Mastodon.API.Account { + + static func pendingFollowRequestEndpointURL(domain: String) -> URL { + return Mastodon.API.endpointURL(domain: domain) + .appendingPathComponent("follow_requests") + } static func acceptFollowRequestEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL { return Mastodon.API.endpointURL(domain: domain) @@ -25,7 +30,7 @@ extension Mastodon.API.Account { .appendingPathComponent("reject") } - /// Accept Follow + /// Pending Follow Requests /// /// /// - Since: 0.0.0 @@ -37,6 +42,38 @@ extension Mastodon.API.Account { /// - domain: Mastodon instance domain. e.g. "example.com" /// - userID: ID of the account in the database /// - authorization: User token + /// - Returns: `AnyPublisher` contains `[Account]` nested in the response + public static func pendingFollowRequest( + session: URLSession, + domain: String, + userID: Mastodon.Entity.Account.ID, + authorization: Mastodon.API.OAuth.Authorization + ) -> AnyPublisher, Error> { + let request = Mastodon.API.get( + url: pendingFollowRequestEndpointURL(domain: domain), + authorization: authorization + ) + return session.dataTaskPublisher(for: request) + .tryMap { data, response in + let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response) + return Mastodon.Response.Content(value: value, response: response) + } + .eraseToAnyPublisher() + } + + + /// Accept Follow + /// + /// + /// - Since: 0.0.0 + /// - Version: 3.3.0 + /// # Reference + /// [Document](https://docs.joinmastodon.org/methods/accounts/follow_requests/#allow) + /// - Parameters: + /// - session: `URLSession` + /// - domain: Mastodon instance domain. e.g. "example.com" + /// - userID: ID of the account in the database + /// - authorization: User token /// - Returns: `AnyPublisher` contains `Relationship` nested in the response public static func acceptFollowRequest( session: URLSession, @@ -63,7 +100,7 @@ extension Mastodon.API.Account { /// - Since: 0.0.0 /// - Version: 3.3.0 /// # Reference - /// [Document](https://docs.joinmastodon.org/methods/accounts/follow_requests/) + /// [Document](https://docs.joinmastodon.org/methods/accounts/follow_requests/#reject) /// - Parameters: /// - session: `URLSession` /// - domain: Mastodon instance domain. e.g. "example.com" @@ -92,7 +129,7 @@ extension Mastodon.API.Account { extension Mastodon.API.Account { - public enum FollowReqeustQuery { + public enum FollowRequestQuery { case accept case reject } @@ -101,7 +138,7 @@ extension Mastodon.API.Account { session: URLSession, domain: String, userID: Mastodon.Entity.Account.ID, - query: FollowReqeustQuery, + query: FollowRequestQuery, authorization: Mastodon.API.OAuth.Authorization ) -> AnyPublisher, Error> { switch query { From c4db4afcdbb9c0072aab04405271aec6220ece44 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 14:13:40 +0200 Subject: [PATCH 15/29] Kill some log-messages along the way (IOS-157) --- .../View/TableviewCell/StatusTableViewCell+ViewModel.swift | 1 - .../Share/View/TableviewCell/StatusTableViewCell.swift | 7 ------- 2 files changed, 8 deletions(-) diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift index 2702d726b..c3455fd0e 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell+ViewModel.swift @@ -33,7 +33,6 @@ extension StatusTableViewCell { if statusView.frame == .zero { // set status view width statusView.frame.size.width = tableView.frame.width - containerViewHorizontalMargin - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): did layout for new cell") } switch viewModel.value { diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index 5cf498fdb..16285ebeb 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -15,8 +15,6 @@ import MastodonUI final class StatusTableViewCell: UITableViewCell { static let marginForRegularHorizontalSizeClass: CGFloat = 64 - - let logger = Logger(subsystem: "StatusTableViewCell", category: "View") weak var delegate: StatusTableViewCellDelegate? var disposeBag = Set() @@ -44,11 +42,6 @@ final class StatusTableViewCell: UITableViewCell { super.init(coder: coder) _init() } - - deinit { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - } - } extension StatusTableViewCell { From e06d852d781ccaa8bf280029d77d87ddc7d3a75e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 14:29:50 +0200 Subject: [PATCH 16/29] Add emoji-support (IOS-157) --- .../TableView-Components/SuggestionAccountTableViewCell.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index 9b88742ea..3c0e334b9 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -108,8 +108,7 @@ final class SuggestionAccountTableViewCell: UITableViewCell { let metaContent: MetaContent = { do { - //TODO: Add emojis - let mastodonContent = MastodonContent(content: viewModel.user.note ?? "", emojis: [:]) + let mastodonContent = MastodonContent(content: viewModel.user.note ?? "", emojis: viewModel.user.emojis.asDictionary) return try MastodonMetaContent.convert(document: mastodonContent) } catch { assertionFailure() From 25e4b732ed7143d243c2dd338b249938fc1f368a Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 14:45:52 +0200 Subject: [PATCH 17/29] Sprinkle in some localization (IOS-157) --- Localization/app.json | 3 ++- .../Scene/SuggestionAccount/SuggestionAccountViewModel.swift | 2 +- .../SuggestionAccountTableViewFooter.swift | 3 ++- .../Sources/MastodonLocalization/Generated/Strings.swift | 2 ++ .../Resources/Base.lproj/Localizable.strings | 1 + .../Resources/en.lproj/Localizable.strings | 1 + 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index c6c1f4b27..52f0db07b 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -464,7 +464,8 @@ } }, "suggestion_account": { - "title": "Popular on Mastodon" + "title": "Popular on Mastodon", + "follow_all": "Follow all" }, "compose": { "title": { diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index 59bf7f55c..64ab6189b 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -47,7 +47,7 @@ final class SuggestionAccountViewModel: NSObject { userFetchedResultsController.domain = authContext.mastodonAuthenticationBox.domain - // fetch recomment users + // fetch recommended users Task { var userIDs: [MastodonUser.ID] = [] do { diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift index 294ba3285..cf0408977 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewFooter.swift @@ -3,6 +3,7 @@ import UIKit import MastodonUI import MastodonAsset +import MastodonLocalization protocol SuggestionAccountTableViewFooterDelegate: AnyObject { func followAll(_ footerView: SuggestionAccountTableViewFooter) @@ -20,7 +21,7 @@ class SuggestionAccountTableViewFooter: UITableViewHeaderFooterView { //TODO: Check if we can use UIButton.configuration here instead? followAllButton = FollowButton() followAllButton.translatesAutoresizingMaskIntoConstraints = false - followAllButton.setTitle("Follow All", for: .normal) + followAllButton.setTitle(L10n.Scene.SuggestionAccount.followAll, for: .normal) followAllButton.setBackgroundColor(Asset.Colors.Button.userFollow.color, for: .normal) followAllButton.setTitleColor(.white, for: .normal) followAllButton.contentEdgeInsets = .init(horizontal: 20, vertical: 12) diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index a2530fc2c..259af8fb9 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -1484,6 +1484,8 @@ public enum L10n { } } public enum SuggestionAccount { + /// Follow All + public static let followAll = L10n.tr("Localizable", "Scene.SuggestionAccount.FollowAll", fallback: "Follow All") /// Popular on Mastodon public static let title = L10n.tr("Localizable", "Scene.SuggestionAccount.Title", fallback: "Popular on Mastodon") } diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 09f28475d..55cf7ec74 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -518,6 +518,7 @@ uploaded to Mastodon."; "Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone"; "Scene.Settings.Title" = "Settings"; "Scene.SuggestionAccount.Title" = "Popular on Mastodon"; +"Scene.SuggestionAccount.FollowAll" = "Follow All"; "Scene.Thread.BackTitle" = "Post"; "Scene.Thread.Title" = "Post from %@"; "Scene.Welcome.Education.A11Y.WhatIsMastodon.Title" = "What is Mastodon?"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings index 09f28475d..55cf7ec74 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/en.lproj/Localizable.strings @@ -518,6 +518,7 @@ uploaded to Mastodon."; "Scene.Settings.Section.SpicyZone.Title" = "The Spicy Zone"; "Scene.Settings.Title" = "Settings"; "Scene.SuggestionAccount.Title" = "Popular on Mastodon"; +"Scene.SuggestionAccount.FollowAll" = "Follow All"; "Scene.Thread.BackTitle" = "Post"; "Scene.Thread.Title" = "Post from %@"; "Scene.Welcome.Education.A11Y.WhatIsMastodon.Title" = "What is Mastodon?"; From 11bab5e337db4d388de89a7c7c5e6a37cf1e9021 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 22 May 2023 16:14:06 +0200 Subject: [PATCH 18/29] Make button-state work (IOS-157) Reason for button-state not working/updating feels like to be a weird combination of Combine, UIKit, Snapshots, CoreData and me being stupid and not getting a hang on it. --- Mastodon.xcodeproj/project.pbxproj | 4 -- .../RecommendAccountSection.swift | 11 +++-- .../SuggestionAccountViewModel+Diffable.swift | 41 ------------------- .../SuggestionAccountViewModel.swift | 31 +++++++++++++- ...estionAccountTableViewCell+ViewModel.swift | 8 ++-- .../SuggestionAccountTableViewCell.swift | 34 +++++---------- 6 files changed, 50 insertions(+), 79 deletions(-) delete mode 100644 Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index ed6afbc58..7179d5f87 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -415,7 +415,6 @@ DBB45B5927B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */; }; DBB45B5B27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5A27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift */; }; DBB45B6027B50A4F002DC5A7 /* RecommendAccountItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */; }; - DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */; }; DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; }; DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */; }; DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525352611ECEB002F1F29 /* UserTimelineViewController.swift */; }; @@ -1132,7 +1131,6 @@ DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewVideoViewModel.swift; sourceTree = ""; }; DBB45B5A27B3A109002DC5A7 /* MediaPreviewTransitionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewTransitionViewController.swift; sourceTree = ""; }; DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendAccountItem.swift; sourceTree = ""; }; - DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountViewModel+Diffable.swift"; sourceTree = ""; }; DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = ""; }; DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = ""; }; DBB525352611ECEB002F1F29 /* UserTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTimelineViewController.swift; sourceTree = ""; }; @@ -1673,7 +1671,6 @@ DBB45B5F27B50A4F002DC5A7 /* RecommendAccountItem.swift */, 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */, 2DAC9E3D262FC2400062E1A6 /* SuggestionAccountViewModel.swift */, - DBB45B6127B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift */, 2DAC9E43262FC9DE0062E1A6 /* TableView-Components */, ); path = SuggestionAccount; @@ -3602,7 +3599,6 @@ DBDFF1952805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift in Sources */, DB03A795272A981400EE37C5 /* ContentSplitViewController.swift in Sources */, DBDFF19C28055BD600557A48 /* DiscoveryViewModel.swift in Sources */, - DBB45B6227B51112002DC5A7 /* SuggestionAccountViewModel+Diffable.swift in Sources */, DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */, DB3E6FF32806D97400B035AE /* DiscoveryNewsViewModel+State.swift in Sources */, DB6746ED278F45F0008A6B94 /* AutoGenerateProtocolRelayDelegate.swift in Sources */, diff --git a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift index 70c2a5077..c3f477a96 100644 --- a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift +++ b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift @@ -39,14 +39,13 @@ extension RecommendAccountSection { context.managedObjectContext.performAndWait { guard let user = record.object(in: context.managedObjectContext) else { return } cell.configure(viewModel: - SuggestionAccountTableViewCell.ViewModel(user: user, - followedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), - blockedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), - followRequestedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()) + SuggestionAccountTableViewCell.ViewModel( + user: user, + followedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds, + blockedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.blockedUserIds, + followRequestedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs) ) } - - } return cell } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift deleted file mode 100644 index b90240d66..000000000 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// SuggestionAccountViewModel+Diffable.swift -// Mastodon -// -// Created by MainasuK on 2022-2-10. -// - -import UIKit - -extension SuggestionAccountViewModel { - - func setupDiffableDataSource( - tableView: UITableView, - suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate - ) { - tableViewDiffableDataSource = RecommendAccountSection.tableViewDiffableDataSource( - tableView: tableView, - context: context, - configuration: RecommendAccountSection.Configuration( - authContext: authContext, - suggestionAccountTableViewCellDelegate: suggestionAccountTableViewCellDelegate - ) - ) - - userFetchedResultsController.$records - .removeDuplicates() - .receive(on: DispatchQueue.main) - .sink { [weak self] records in - guard let self = self else { return } - guard let tableViewDiffableDataSource = self.tableViewDiffableDataSource else { return } - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - let items: [RecommendAccountItem] = records.map { RecommendAccountItem.account($0) } - snapshot.appendItems(items, toSection: .main) - - tableViewDiffableDataSource.applySnapshotUsingReloadData(snapshot, completion: nil) - } - .store(in: &disposeBag) - } -} diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index 64ab6189b..038ee887d 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -85,7 +85,36 @@ final class SuggestionAccountViewModel: NSObject { .store(in: &disposeBag) } - func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider ) { + func setupDiffableDataSource( + tableView: UITableView, + suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate + ) { + tableViewDiffableDataSource = RecommendAccountSection.tableViewDiffableDataSource( + tableView: tableView, + context: context, + configuration: RecommendAccountSection.Configuration( + authContext: authContext, + suggestionAccountTableViewCellDelegate: suggestionAccountTableViewCellDelegate + ) + ) + + userFetchedResultsController.$records + .receive(on: DispatchQueue.main) + .sink { [weak self] records in + guard let self = self else { return } + guard let tableViewDiffableDataSource = self.tableViewDiffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + let items: [RecommendAccountItem] = records.map { RecommendAccountItem.account($0) } + snapshot.appendItems(items, toSection: .main) + + tableViewDiffableDataSource.applySnapshotUsingReloadData(snapshot) + } + .store(in: &disposeBag) + } + + func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider) { let userRecords = userFetchedResultsController.records.compactMap { $0.object(in: dependency.context.managedObjectContext)?.asRecord diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift index 937cb2262..1b4fcb34c 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell+ViewModel.swift @@ -8,11 +8,11 @@ extension SuggestionAccountTableViewCell { final class ViewModel { let user: MastodonUser - let followedUsers: AnyPublisher<[String], Never> - let blockedUsers: AnyPublisher<[String], Never> - let followRequestedUsers: AnyPublisher<[String], Never> + let followedUsers: [String] + let blockedUsers: [String] + let followRequestedUsers: [String] - init(user: MastodonUser, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) { + init(user: MastodonUser, followedUsers: [String], blockedUsers: [String], followRequestedUsers: [String]) { self.user = user self.followedUsers = followedUsers self.followRequestedUsers = followRequestedUsers diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index 3c0e334b9..a1c78eca6 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -82,30 +82,18 @@ final class SuggestionAccountTableViewCell: UITableViewCell { func configure(viewModel: SuggestionAccountTableViewCell.ViewModel) { userView.configure(user: viewModel.user, delegate: delegate) - Publishers.CombineLatest3( - viewModel.followedUsers, - viewModel.followRequestedUsers, - viewModel.blockedUsers - ) - .receive(on: DispatchQueue.main) - .sink { [weak self] followed, requested, blocked in - - guard let self else { return } - - if blocked.contains(viewModel.user.id) { - self.userView.setButtonState(.blocked) - } else if followed.contains(viewModel.user.id) { - self.userView.setButtonState(.unfollow) - } else if requested.contains(viewModel.user.id) { - self.userView.setButtonState(.pending) - } else if viewModel.user.locked { - self.userView.setButtonState(.request) - } else { - self.userView.setButtonState(.follow) - } + if viewModel.blockedUsers.contains(viewModel.user.id) { + self.userView.setButtonState(.blocked) + } else if viewModel.followedUsers.contains(viewModel.user.id) { + self.userView.setButtonState(.unfollow) + } else if viewModel.followRequestedUsers.contains(viewModel.user.id) { + self.userView.setButtonState(.pending) + } else if viewModel.user.locked { + self.userView.setButtonState(.request) + } else { + self.userView.setButtonState(.follow) } - .store(in: &disposeBag) - + let metaContent: MetaContent = { do { let mastodonContent = MastodonContent(content: viewModel.user.note ?? "", emojis: viewModel.user.emojis.asDictionary) From e20b2e4e11516a9b760eb50a9bb2e58ca42a412f Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 12:23:50 +0200 Subject: [PATCH 19/29] Present suggestions when user visits onboarding and doesn't follow people (IOS-157) --- .../HomeTimelineViewController.swift | 17 ++++++++++++++--- .../HomeTimeline/HomeTimelineViewModel.swift | 3 ++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 7e22c346f..442c5e351 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -240,6 +240,19 @@ extension HomeTimelineViewController { .sink { [weak self] isEmpty in if isEmpty { self?.showEmptyView() + + let userDoesntFollowPeople: Bool + if let managedObjectContext = self?.context.managedObjectContext, + let me = self?.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user { + userDoesntFollowPeople = me.followersCount == 0 + } else { + userDoesntFollowPeople = true + } + + if (self?.viewModel.presentedSuggestions == false) && userDoesntFollowPeople { + self?.findPeopleButtonPressed(self) + self?.viewModel.presentedSuggestions = true + } } else { self?.emptyView.removeFromSuperview() } @@ -285,8 +298,6 @@ extension HomeTimelineViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - viewModel.viewDidAppear.send() - if let timestamp = viewModel.lastAutomaticFetchTimestamp { let now = Date() if now.timeIntervalSince(timestamp) > 60 { @@ -376,7 +387,7 @@ extension HomeTimelineViewController { extension HomeTimelineViewController { - @objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) { + @objc private func findPeopleButtonPressed(_ sender: Any?) { let suggestionAccountViewModel = SuggestionAccountViewModel(context: context, authContext: viewModel.authContext) suggestionAccountViewModel.delegate = viewModel _ = coordinator.present( diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index 7319409e5..663a38724 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -30,7 +30,8 @@ final class HomeTimelineViewModel: NSObject { let fetchedResultsController: FeedFetchedResultsController let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel let listBatchFetchViewModel = ListBatchFetchViewModel() - let viewDidAppear = PassthroughSubject() + + var presentedSuggestions = false @Published var lastAutomaticFetchTimestamp: Date? = nil @Published var scrollPositionRecord: ScrollPositionRecord? = nil From 2b59def31e65f9beca2367099a8dc5cbad70a264 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 12:28:00 +0200 Subject: [PATCH 20/29] Don't set title for profile-screen when modal (IOS-157) aka when account-suggestion-screen --- .../Header/ProfileHeaderViewController.swift | 2 -- Mastodon/Scene/Profile/ProfileViewController.swift | 8 +++++++- .../SuggestionAccountViewController.swift | 13 ++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 3bb6971bb..5f8e878a1 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -280,8 +280,6 @@ extension ProfileHeaderViewController { } func updateHeaderScrollProgress(_ progress: CGFloat, throttle: CGFloat) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: progress: %.2f", ((#file as NSString).lastPathComponent), #line, #function, progress) - // set title view offset let nameTextFieldInWindow = profileHeaderView.nameTextField.superview!.convert(profileHeaderView.nameTextField.frame, to: nil) let nameTextFieldTopToNavigationBarBottomOffset = containerSafeAreaInset.top - nameTextFieldInWindow.origin.y diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 6ebeec898..f8a865bc4 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -289,6 +289,12 @@ extension ProfileViewController { bindPager() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + navigationController?.navigationBar.prefersLargeTitles = false + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -374,7 +380,7 @@ extension ProfileViewController { profileHeaderViewController.profileHeaderView.viewModel.$name .receive(on: DispatchQueue.main) .sink { [weak self] name in - guard let self = self else { return } + guard let self = self, isModal == false else { return } self.navigationItem.title = name } .store(in: &disposeBag) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index bd3df4fac..c9f54b363 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -33,10 +33,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { }() override func viewDidLoad() { - - setupNavigationBarAppearance() - defer { setupNavigationBarBackgroundView() } - super.viewDidLoad() title = L10n.Scene.SuggestionAccount.title @@ -63,13 +59,14 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { view.backgroundColor = .secondarySystemBackground tableView.backgroundColor = .secondarySystemBackground - navigationController?.navigationBar.prefersLargeTitles = true - navigationItem.largeTitleDisplayMode = .always } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - + + navigationController?.navigationBar.prefersLargeTitles = true + navigationItem.largeTitleDisplayMode = .automatic + tableView.deselectRow(with: transitionCoordinator, animated: animated) } @@ -124,5 +121,3 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDeleg viewModel.followAllSuggestedAccounts(self) } } - -extension SuggestionAccountViewController: OnboardingViewControllerAppearance { } From ce897a0e05791e321d18487d9495b567b66736cd Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 12:36:23 +0200 Subject: [PATCH 21/29] Fix navigation-bar-style (IOS-157) --- .../SuggestionAccount/SuggestionAccountViewController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index c9f54b363..ed0ba0b6b 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -35,6 +35,10 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { override func viewDidLoad() { super.viewDidLoad() + setupNavigationBarAppearance() + defer { setupNavigationBarBackgroundView() } + + title = L10n.Scene.SuggestionAccount.title navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: UIBarButtonItem.SystemItem.done, @@ -121,3 +125,5 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDeleg viewModel.followAllSuggestedAccounts(self) } } + +extension SuggestionAccountViewController: OnboardingViewControllerAppearance { } From 9c19bf2985b1f4cfd27d78e08aea75f3af1f2bf6 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 12:45:13 +0200 Subject: [PATCH 22/29] Fix build (IOS-157) --- Mastodon/Scene/Profile/ProfileViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index f8a865bc4..42961ba64 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -380,7 +380,7 @@ extension ProfileViewController { profileHeaderViewController.profileHeaderView.viewModel.$name .receive(on: DispatchQueue.main) .sink { [weak self] name in - guard let self = self, isModal == false else { return } + guard let self = self, self.isModal == false else { return } self.navigationItem.title = name } .store(in: &disposeBag) From 5daf944384bd57d18f91737f9f3c0fd6459750c9 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 12:55:24 +0200 Subject: [PATCH 23/29] Dismiss after following all (IOS-157) --- .../SuggestionAccountViewController.swift | 6 ++++- .../SuggestionAccountViewModel.swift | 24 ++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index ed0ba0b6b..fa83c2696 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -122,7 +122,11 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDelegate { func followAll(_ footerView: SuggestionAccountTableViewFooter) { - viewModel.followAllSuggestedAccounts(self) + viewModel.followAllSuggestedAccounts(self) { + OperationQueue.main.addOperation { + self.dismiss(animated: true) + } + } } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index 038ee887d..f75506f8a 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -114,20 +114,26 @@ final class SuggestionAccountViewModel: NSObject { .store(in: &disposeBag) } - func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider) { + func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider, completion: (() -> Void)? = nil) { let userRecords = userFetchedResultsController.records.compactMap { $0.object(in: dependency.context.managedObjectContext)?.asRecord } - userRecords.forEach { user in - Task { - try? await DataSourceFacade.responseToUserViewButtonAction( - dependency: dependency, - user: user, - buttonState: .follow - ) - } + Task { + await withTaskGroup(of: Void.self, body: { taskGroup in + for user in userRecords { + taskGroup.addTask { + try? await DataSourceFacade.responseToUserViewButtonAction( + dependency: dependency, + user: user, + buttonState: .follow + ) + } + } + }) + + completion?() } } } From 1a0ab4607cf1f4ba7a572664ddc6bd4585f8bc21 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 13:13:34 +0200 Subject: [PATCH 24/29] Refresh hometimeline (IOS-157) --- .../SuggestionAccount/SuggestionAccountViewController.swift | 1 + .../SuggestionAccount/SuggestionAccountViewModel.swift | 1 + .../FeedFetchedResultsController.swift | 6 ------ 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index fa83c2696..eddbe250c 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -77,6 +77,7 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { //MARK: - Actions @objc func doneButtonDidClick(_ sender: UIButton) { + viewModel.delegate?.homeTimelineNeedRefresh.send() dismiss(animated: true, completion: nil) } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index f75506f8a..a57d71df0 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -133,6 +133,7 @@ final class SuggestionAccountViewModel: NSObject { } }) + delegate?.homeTimelineNeedRefresh.send() completion?() } } diff --git a/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift b/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift index 27cc5a08d..2ba2ec5cb 100644 --- a/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift +++ b/MastodonSDK/Sources/MastodonCore/FetchedResultsController/FeedFetchedResultsController.swift @@ -83,11 +83,6 @@ final public class FeedFetchedResultsController: NSObject { } .store(in: &disposeBag) } - - deinit { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - } - } // MARK: - NSFetchedResultsControllerDelegate @@ -96,7 +91,6 @@ extension FeedFetchedResultsController: NSFetchedResultsControllerDelegate { _ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference ) { - os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) let snapshot = snapshot as NSDiffableDataSourceSnapshot self._objectIDs.send(snapshot.itemIdentifiers) } From 63a79b1cfe9031eba2b42321236dbb06af379942 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 16:22:20 +0200 Subject: [PATCH 25/29] Fix Suggestion-screen on iPad (IOS-157) Follow-button wasn't attached to the right side --- .../SuggestionAccountTableViewCell.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index a1c78eca6..513bf63b6 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -34,7 +34,10 @@ final class SuggestionAccountTableViewCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { userView = UserView() + userView.translatesAutoresizingMaskIntoConstraints = false + bioMetaLabel = MetaLabel() + bioMetaLabel.translatesAutoresizingMaskIntoConstraints = false bioMetaLabel.numberOfLines = 0 bioMetaLabel.textAttributes = [ .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular)), @@ -68,6 +71,9 @@ final class SuggestionAccountTableViewCell: UITableViewCell { contentStackView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16), + + userView.widthAnchor.constraint(equalTo: contentStackView.widthAnchor), + bioMetaLabel.widthAnchor.constraint(equalTo: contentStackView.widthAnchor), ] NSLayoutConstraint.activate(constraints) From 71d8cfb485e16f6556f6258798dc9d9933a72973 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 16:23:02 +0200 Subject: [PATCH 26/29] Fix name for development (IOS-157) --- .../Sources/MastodonCore/Service/API/APIService+App.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+App.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+App.swift index 82d814294..530d4288a 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+App.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+App.swift @@ -14,7 +14,7 @@ import MastodonSDK extension APIService { #if DEBUG - private static let clientName = "Skimming" + private static let clientName = "Mastodon for iOS (Development)" #else private static let clientName = "Mastodon for iOS" #endif From 8b7e4915900cc7a80b164c8d09dac0a873792dfb Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 23 May 2023 16:34:16 +0200 Subject: [PATCH 27/29] Fix search-button on empty-view (IOS-157) Wasn't tappable on iPhone --- Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 442c5e351..77ff7408c 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -371,6 +371,7 @@ extension HomeTimelineViewController { bottomPaddingView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ topPaddingView.heightAnchor.constraint(equalTo: bottomPaddingView.heightAnchor, multiplier: 0.8), + manuallySearchButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 20), ]) let buttonContainerStackView = UIStackView() @@ -385,6 +386,7 @@ extension HomeTimelineViewController { } } +//MARK: - Actions extension HomeTimelineViewController { @objc private func findPeopleButtonPressed(_ sender: Any?) { @@ -398,13 +400,11 @@ extension HomeTimelineViewController { } @objc private func manuallySearchButtonPressed(_ sender: UIButton) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) let searchDetailViewModel = SearchDetailViewModel(authContext: viewModel.authContext) _ = coordinator.present(scene: .searchDetail(viewModel: searchDetailViewModel), from: self, transition: .modal(animated: true, completion: nil)) } @objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let setting = context.settingService.currentSetting.value else { return } let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting) _ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) From 7a9c002fe83a6761b3090c3cbcfb92740c5af646 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 25 May 2023 15:30:45 +0200 Subject: [PATCH 28/29] Simplify overly complicated init (IOS-157) Thank you @kimar! --- MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift index 1593b889f..28125d304 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift @@ -100,12 +100,7 @@ public final class UserView: UIView { return label }() - private let followButtonWrapper = { - let wrapper = UIView() - - return wrapper - }() - + private let followButtonWrapper = UIView() private let followButton: FollowButton = { let button = FollowButton() button.cornerRadius = 10 From 3597e69f3c9e6d9b33961b7144adb3ca29564355 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 25 May 2023 15:31:03 +0200 Subject: [PATCH 29/29] Use same way to run stuff on main everywhere (IOS-157) --- .../Scene/Onboarding/Login/MastodonLoginViewController.swift | 2 +- Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift | 2 +- Mastodon/Scene/Profile/ProfileViewController.swift | 2 +- .../SuggestionAccount/SuggestionAccountViewController.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift index 01ee5acfd..8f1cbecf2 100644 --- a/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift +++ b/Mastodon/Scene/Onboarding/Login/MastodonLoginViewController.swift @@ -301,7 +301,7 @@ extension MastodonLoginViewController: MastodonLoginViewModelDelegate { dataSource?.apply(snapshot, animatingDifferences: false) - OperationQueue.main.addOperation { + DispatchQueue.main.async { let numberOfResults = viewModel.filteredServers.count self.contentView.updateCorners(numberOfResults: numberOfResults) } diff --git a/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift b/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift index 6dfd81c59..9f1781657 100644 --- a/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift +++ b/Mastodon/Scene/Onboarding/PickServer/PickServerSection.swift @@ -65,7 +65,7 @@ extension PickServerSection { }() if let proxiedThumbnail = server.proxiedThumbnail, let thumbnailUrl = URL(string: proxiedThumbnail) { cell.thumbnailImageView.af.setImage(withURL: thumbnailUrl, completion: { _ in - OperationQueue.main.addOperation { + DispatchQueue.main.async { cell.thumbnailImageView.isHidden = false } }) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 42961ba64..033e54615 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -428,7 +428,7 @@ extension ProfileViewController { } } receiveValue: { [weak self] menu in guard let self = self else { return } - OperationQueue.main.addOperation { + DispatchQueue.main.async { self.moreMenuBarButtonItem.menu = menu } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index eddbe250c..4962d3e71 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -124,7 +124,7 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDelegate { func followAll(_ footerView: SuggestionAccountTableViewFooter) { viewModel.followAllSuggestedAccounts(self) { - OperationQueue.main.addOperation { + DispatchQueue.main.async { self.dismiss(animated: true) } }