From e2a05cd747343db0f77d645e67e3f86ef8cbee6e Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Tue, 25 Apr 2023 12:48:53 +0200 Subject: [PATCH] Implement follow/unfollow/block in UserView (IOS-140) --- Mastodon.xcodeproj/project.pbxproj | 4 + .../Diffable/Search/SearchResultSection.swift | 1 + Mastodon/Diffable/User/UserSection.swift | 3 + .../Provider/DataSourceFacade+UserView.swift | 30 ++++++++ .../FamiliarFollowersViewController.swift | 15 +++- .../Follower/FollowerListViewController.swift | 14 +++- .../FollowingListViewController.swift | 14 +++- .../FavoritedByViewController.swift | 15 +++- .../RebloggedByViewController.swift | 15 +++- .../Profile/UserLIst/UserListViewModel.swift | 1 - ...toryUserCollectionViewCell+ViewModel.swift | 2 +- .../View/Content/UserView+Configuration.swift | 5 +- .../UserTableViewCell+ViewModel.swift | 14 +++- .../TableviewCell/UserTableViewCell.swift | 6 +- .../Button/userBlocked.colorset/Contents.json | 20 +++++ .../Button/userFollow.colorset/Contents.json | 20 +++++ .../userFollowing.colorset/Contents.json | 20 +++++ .../MastodonAsset/Generated/Assets.swift | 3 + .../View/Content/UserView+ViewModel.swift | 2 + .../MastodonUI/View/Content/UserView.swift | 77 +++++++++++++++---- 20 files changed, 253 insertions(+), 28 deletions(-) create mode 100644 Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userBlocked.colorset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollow.colorset/Contents.json create mode 100644 MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollowing.colorset/Contents.json diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 17a4dce35..b4af407f4 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 164F0EBC267D4FE400249499 /* BoopSound.caf in Resources */ = {isa = PBXBuildFile; fileRef = 164F0EBB267D4FE400249499 /* BoopSound.caf */; }; 18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; }; 27D701F5292FC2D60031BCBB /* DataSourceFacade+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */; }; + 2A1BF99529F7E68400FA1BA5 /* DataSourceFacade+UserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1BF99429F7E68400FA1BA5 /* DataSourceFacade+UserView.swift */; }; 2A1FE47C2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */; }; 2A1FE47E2938C11200784BF1 /* Collection+IsNotEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */; }; 2A33062D2987DBFA001D4C51 /* FollowersCountHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A33062C2987DBFA001D4C51 /* FollowersCountHistory.swift */; }; @@ -613,6 +614,7 @@ 164F0EBB267D4FE400249499 /* BoopSound.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = BoopSound.caf; sourceTree = ""; }; 1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk - debug.xcconfig"; sourceTree = ""; }; 27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+URL.swift"; sourceTree = ""; }; + 2A1BF99429F7E68400FA1BA5 /* DataSourceFacade+UserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+UserView.swift"; sourceTree = ""; }; 2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowedTagsViewModel+DiffableDataSource.swift"; sourceTree = ""; }; 2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+IsNotEmpty.swift"; sourceTree = ""; }; 2A33062C2987DBFA001D4C51 /* FollowersCountHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersCountHistory.swift; sourceTree = ""; }; @@ -2390,6 +2392,7 @@ DB63F7532799491600455B82 /* DataSourceFacade+SearchHistory.swift */, DB159C2A27A17BAC0068DC77 /* DataSourceFacade+Media.swift */, 2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */, + 2A1BF99429F7E68400FA1BA5 /* DataSourceFacade+UserView.swift */, DB697DD5278F4C29004EF2F7 /* DataSourceProvider.swift */, DB697DDA278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift */, DB023D2927A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift */, @@ -3598,6 +3601,7 @@ DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */, DB0FCB9C27980AB6006C02E2 /* HashtagTimelineViewController+DataSourceProvider.swift in Sources */, DB63F76F279A7D1100455B82 /* NotificationTableViewCell.swift in Sources */, + 2A1BF99529F7E68400FA1BA5 /* DataSourceFacade+UserView.swift in Sources */, DB0FCB8C2796BF8D006C02E2 /* SearchViewModel+Diffable.swift in Sources */, DBEFCD76282A143F00C0ABEA /* ReportStatusViewController.swift in Sources */, DBDFF1952805561700557A48 /* DiscoveryPostsViewModel+Diffable.swift in Sources */, diff --git a/Mastodon/Diffable/Search/SearchResultSection.swift b/Mastodon/Diffable/Search/SearchResultSection.swift index e4dcad891..d6d378f31 100644 --- a/Mastodon/Diffable/Search/SearchResultSection.swift +++ b/Mastodon/Diffable/Search/SearchResultSection.swift @@ -122,6 +122,7 @@ extension SearchResultSection { configuration: Configuration ) { cell.configure( + meUserID: context.authenticationService.mastodonAuthenticationBoxes.first?.userID, tableView: tableView, viewModel: viewModel, delegate: configuration.userTableViewCellDelegate diff --git a/Mastodon/Diffable/User/UserSection.swift b/Mastodon/Diffable/User/UserSection.swift index 20812b7e8..9663c4341 100644 --- a/Mastodon/Diffable/User/UserSection.swift +++ b/Mastodon/Diffable/User/UserSection.swift @@ -42,6 +42,7 @@ extension UserSection { context.managedObjectContext.performAndWait { guard let user = record.object(in: context.managedObjectContext) else { return } configure( + context: context, tableView: tableView, cell: cell, viewModel: .init(value: .user(user)), @@ -66,6 +67,7 @@ extension UserSection { extension UserSection { static func configure( + context: AppContext, tableView: UITableView, cell: UserTableViewCell, viewModel: UserTableViewCell.ViewModel, @@ -73,6 +75,7 @@ extension UserSection { ) { cell.configure( + meUserID: context.authenticationService.mastodonAuthenticationBoxes.first?.userID, tableView: tableView, viewModel: viewModel, delegate: configuration.userTableViewCellDelegate diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift new file mode 100644 index 000000000..53b57baed --- /dev/null +++ b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift @@ -0,0 +1,30 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation +import MastodonUI +import CoreDataStack +import MastodonCore +import MastodonSDK + +extension DataSourceFacade { + static func responseToUserViewButtonAction( + dependency: NeedsDependency & AuthContextProvider, + user: ManagedObjectRecord, + buttonState: UserView.ButtonState + ) async throws { + switch buttonState { + case .follow, .unfollow: + try await DataSourceFacade.responseToUserFollowAction( + dependency: dependency, + user: user + ) + case .blocked: + try await DataSourceFacade.responseToUserBlockAction( + dependency: dependency, + user: user + ) + case .none: + break //no-op + } + } +} diff --git a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift index 3595303d9..762eb2d9d 100644 --- a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift +++ b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift @@ -10,6 +10,8 @@ import UIKit import Combine import MastodonCore import MastodonLocalization +import MastodonUI +import CoreDataStack final class FamiliarFollowersViewController: UIViewController, NeedsDependency { @@ -91,4 +93,15 @@ extension FamiliarFollowersViewController: UITableViewDelegate, AutoGenerateTabl } // MARK: - UserTableViewCellDelegate -extension FamiliarFollowersViewController: UserTableViewCellDelegate { } +extension FamiliarFollowersViewController: UserTableViewCellDelegate { + func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: MastodonUser) { + Task { + try await DataSourceFacade.responseToUserViewButtonAction( + dependency: self, + user: user.asRecord, + buttonState: state + ) + tableView.reloadData() + } + } +} diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift index c111d13d0..3945e68b5 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift @@ -12,6 +12,7 @@ import Combine import MastodonCore import MastodonUI import MastodonLocalization +import CoreDataStack final class FollowerListViewController: UIViewController, NeedsDependency { @@ -118,4 +119,15 @@ extension FollowerListViewController: UITableViewDelegate, AutoGenerateTableView } // MARK: - UserTableViewCellDelegate -extension FollowerListViewController: UserTableViewCellDelegate { } +extension FollowerListViewController: UserTableViewCellDelegate { + func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: MastodonUser) { + Task { + try await DataSourceFacade.responseToUserViewButtonAction( + dependency: self, + user: user.asRecord, + buttonState: state + ) + tableView.reloadData() + } + } +} diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift index 3fa65918a..06223778a 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift @@ -12,6 +12,7 @@ import Combine import MastodonLocalization import MastodonCore import MastodonUI +import CoreDataStack final class FollowingListViewController: UIViewController, NeedsDependency { @@ -116,4 +117,15 @@ extension FollowingListViewController: UITableViewDelegate, AutoGenerateTableVie } // MARK: - UserTableViewCellDelegate -extension FollowingListViewController: UserTableViewCellDelegate { } +extension FollowingListViewController: UserTableViewCellDelegate { + func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: MastodonUser) { + Task { + try await DataSourceFacade.responseToUserViewButtonAction( + dependency: self, + user: user.asRecord, + buttonState: state + ) + tableView.reloadData() + } + } +} diff --git a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift index 07316cd3d..75a553077 100644 --- a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift +++ b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift @@ -11,6 +11,8 @@ import GameplayKit import Combine import MastodonCore import MastodonLocalization +import MastodonUI +import CoreDataStack final class FavoritedByViewController: UIViewController, NeedsDependency { @@ -107,4 +109,15 @@ extension FavoritedByViewController: UITableViewDelegate, AutoGenerateTableViewD } // MARK: - UserTableViewCellDelegate -extension FavoritedByViewController: UserTableViewCellDelegate { } +extension FavoritedByViewController: UserTableViewCellDelegate { + func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: MastodonUser) { + Task { + try await DataSourceFacade.responseToUserViewButtonAction( + dependency: self, + user: user.asRecord, + buttonState: state + ) + tableView.reloadData() + } + } +} diff --git a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift index a45c491fc..3d31dccd9 100644 --- a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift +++ b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift @@ -11,6 +11,8 @@ import GameplayKit import Combine import MastodonCore import MastodonLocalization +import MastodonUI +import CoreDataStack final class RebloggedByViewController: UIViewController, NeedsDependency { @@ -107,4 +109,15 @@ extension RebloggedByViewController: UITableViewDelegate, AutoGenerateTableViewD } // MARK: - UserTableViewCellDelegate -extension RebloggedByViewController: UserTableViewCellDelegate { } +extension RebloggedByViewController: UserTableViewCellDelegate { + func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: MastodonUser) { + Task { + try await DataSourceFacade.responseToUserViewButtonAction( + dependency: self, + user: user.asRecord, + buttonState: state + ) + tableView.reloadData() + } + } +} diff --git a/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift b/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift index 2a0ed0271..1272d066b 100644 --- a/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift +++ b/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift @@ -53,7 +53,6 @@ final class UserListViewModel { ) // end init } - } extension UserListViewModel { diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift index d4cb86eb0..d10a369c1 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/Cell/SearchHistoryUserCollectionViewCell+ViewModel.swift @@ -22,6 +22,6 @@ extension SearchHistoryUserCollectionViewCell { func configure( viewModel: ViewModel ) { - userView.configure(user: viewModel.value) + userView.configure(user: viewModel.value, delegate: nil) } } diff --git a/Mastodon/Scene/Share/View/Content/UserView+Configuration.swift b/Mastodon/Scene/Share/View/Content/UserView+Configuration.swift index 2e5e9e925..6deeb0a2a 100644 --- a/Mastodon/Scene/Share/View/Content/UserView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/UserView+Configuration.swift @@ -15,7 +15,10 @@ import MastodonCore import Meta extension UserView { - public func configure(user: MastodonUser) { + public func configure(user: MastodonUser, delegate: UserViewDelegate?) { + self.delegate = delegate + viewModel.user = user + Publishers.CombineLatest( user.publisher(for: \.avatar), UserDefaults.shared.publisher(for: \.preferredStaticAvatar) diff --git a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift index 3ec85fa4a..990cf1260 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell+ViewModel.swift @@ -26,13 +26,25 @@ extension UserTableViewCell { extension UserTableViewCell { func configure( + meUserID: MastodonUser.ID?, tableView: UITableView, viewModel: ViewModel, delegate: UserTableViewCellDelegate? ) { switch viewModel.value { case .user(let user): - userView.configure(user: user) + userView.configure(user: user, delegate: delegate) + + if user.id == meUserID { + userView.setButtonState(.none) + } else if user.blockingBy.contains(where: { $0.id == meUserID }) { + userView.setButtonState(.blocked) + } else if user.followingBy.contains(where: { $0.id == meUserID }) { + userView.setButtonState(.unfollow) + } else { + userView.setButtonState(.follow) + } + } self.delegate = delegate diff --git a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift index 9dcec469f..31b90f755 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/UserTableViewCell.swift @@ -13,7 +13,7 @@ import MastodonLocalization import MastodonUI import MastodonSDK -protocol UserTableViewCellDelegate: AnyObject { } +protocol UserTableViewCellDelegate: UserViewDelegate, AnyObject { } final class UserTableViewCell: UITableViewCell { @@ -21,7 +21,9 @@ final class UserTableViewCell: UITableViewCell { let userView: UserView = { let view = UserView() -// view.setButtonState(.follow) + [UserView.ButtonState.follow, UserView.ButtonState.unfollow, UserView.ButtonState.blocked].randomElement().map { + view.setButtonState($0) + } return view }() diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userBlocked.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userBlocked.colorset/Contents.json new file mode 100644 index 000000000..def3fb832 --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userBlocked.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.150", + "blue" : "0x30", + "green" : "0x3B", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollow.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollow.colorset/Contents.json new file mode 100644 index 000000000..8c0cc8eed --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollow.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.800", + "green" : "0.227", + "red" : "0.337" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollowing.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollowing.colorset/Contents.json new file mode 100644 index 000000000..20dd015dc --- /dev/null +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Colors/Button/userFollowing.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.150", + "blue" : "0.988", + "green" : "0.173", + "red" : "0.337" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift index 8a60031db..244e21ad4 100644 --- a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift +++ b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift @@ -57,6 +57,9 @@ public enum Asset { public static let inactive = ColorAsset(name: "Colors/Button/inactive") public static let tagFollow = ColorAsset(name: "Colors/Button/tagFollow") public static let tagUnfollow = ColorAsset(name: "Colors/Button/tagUnfollow") + public static let userBlocked = ColorAsset(name: "Colors/Button/userBlocked") + public static let userFollow = ColorAsset(name: "Colors/Button/userFollow") + public static let userFollowing = ColorAsset(name: "Colors/Button/userFollowing") } public enum Icon { public static let plus = ColorAsset(name: "Colors/Icon/plus") diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/UserView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/UserView+ViewModel.swift index f61e7a2e6..85259866b 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/UserView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/UserView+ViewModel.swift @@ -5,6 +5,7 @@ // Created by MainasuK on 2022-1-19. // +import CoreDataStack import os.log import UIKit import Combine @@ -26,6 +27,7 @@ extension UserView { @Published public var authorUsername: String? @Published public var authorFollowers: Int? @Published public var authorVerifiedLink: String? + @Published public var user: MastodonUser? } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift index cc64db73c..f45d683ab 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/UserView.swift @@ -9,7 +9,13 @@ import UIKit import Combine import MetaTextKit import MastodonAsset +import MastodonLocalization import os +import CoreDataStack + +public protocol UserViewDelegate: AnyObject { + func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: MastodonUser) +} public final class UserView: UIView { @@ -17,6 +23,10 @@ public final class UserView: UIView { case none, follow, unfollow, blocked } + private var currentButtonState: ButtonState = .none + + public weak var delegate: UserViewDelegate? + public var disposeBag = Set() public private(set) lazy var viewModel: ViewModel = { @@ -92,7 +102,6 @@ public final class UserView: UIView { private let followButton: UIButton = { let button = FollowButton() button.cornerRadius = 10 - button.setTitle("Follow", for: .normal) button.isHidden = true button.translatesAutoresizingMaskIntoConstraints = false button.setContentCompressionResistancePriority(.required, for: .horizontal) @@ -105,22 +114,7 @@ public final class UserView: UIView { return button }() - - public func setButtonState(_ state: ButtonState) { - switch state { - case .follow, .unfollow, .blocked: - verifiedStackView.axis = .vertical - verifiedStackView.alignment = .leading - verifiedStackCenterSpacerView.isHidden = true - followButton.isHidden = false - case .none: - verifiedStackView.axis = .horizontal - verifiedStackView.alignment = .leading - verifiedStackCenterSpacerView.isHidden = false - followButton.isHidden = true - } - } - + public func prepareForReuse() { disposeBag.removeAll() @@ -128,6 +122,7 @@ public final class UserView: UIView { viewModel.authorAvatarImageURL = nil avatarButton.avatarImageView.cancelTask() + setButtonState(.none) } public override init(frame: CGRect) { @@ -246,3 +241,51 @@ private final class FollowButton: RoundedEdgesButton { } } } + +public extension UserView { + private func prepareButtonStateLayout(for state: ButtonState) { + switch state { + case .none: + verifiedStackView.axis = .horizontal + verifiedStackView.alignment = .leading + verifiedStackCenterSpacerView.isHidden = false + followButton.isHidden = true + default: + verifiedStackView.axis = .vertical + verifiedStackView.alignment = .leading + verifiedStackCenterSpacerView.isHidden = true + followButton.isHidden = false + } + } + + @objc private func didTapButton() { + guard let user = viewModel.user else { return } + delegate?.userView(self, didTapButtonWith: currentButtonState, for: user) + } + + func setButtonState(_ state: ButtonState) { + currentButtonState = state + prepareButtonStateLayout(for: state) + + switch state { + case .follow: + followButton.setTitle(L10n.Common.Controls.Friendship.follow, for: .normal) + followButton.setBackgroundColor(Asset.Colors.Button.userFollow.color, for: .normal) + followButton.setTitleColor(.white, for: .normal) + case .unfollow: + followButton.setTitle(L10n.Common.Controls.Friendship.following, for: .normal) + followButton.setBackgroundColor(Asset.Colors.Button.userFollowing.color, for: .normal) + followButton.setTitleColor(Asset.Colors.Button.userFollow.color, for: .normal) + case .blocked: + followButton.setTitle(L10n.Common.Controls.Friendship.blocked, for: .normal) + followButton.setBackgroundColor(Asset.Colors.Button.userBlocked.color, for: .normal) + followButton.setTitleColor(.systemRed, for: .normal) + + case .none: + break + } + + followButton.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) + followButton.titleLabel?.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .boldSystemFont(ofSize: 15)) + } +}