Add more prominent follow button to profile pages

This commit is contained in:
Shadowfacts 2022-12-22 17:26:50 -05:00
parent 86143c5887
commit 46cecde014
2 changed files with 115 additions and 6 deletions

View File

@ -32,6 +32,7 @@ class ProfileHeaderView: UIView {
@IBOutlet weak var avatarContainerView: UIView!
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var moreButton: VisualEffectImageButton!
@IBOutlet weak var followButton: UIButton!
@IBOutlet weak var displayNameLabel: EmojiLabel!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var lockImageView: UIImageView!
@ -47,6 +48,11 @@ class ProfileHeaderView: UIView {
private var headerRequest: ImageCache.Request?
private var isGrayscale = false
private var followButtonMode = FollowButtonMode.follow {
didSet {
followButton.configuration = followButtonMode.makeConfig(showsActivityIndicator: false)
}
}
private var cancellables = [AnyCancellable]()
@ -72,6 +78,8 @@ class ProfileHeaderView: UIView {
moreButton.showsMenuAsPrimaryAction = true
moreButton.isContextMenuInteractionEnabled = true
followButton.addInteraction(UIPointerInteraction(delegate: self))
displayNameLabel.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: .systemFont(ofSize: 24, weight: .semibold))
displayNameLabel.adjustsFontForContentSizeCategory = true
@ -150,8 +158,11 @@ class ProfileHeaderView: UIView {
noteTextView.setTextFromHtml(account.note)
noteTextView.setEmojis(account.emojis, identifier: account.id)
// don't show relationship label for the user's own account
if accountID != mastodonController.account?.id {
if accountID == mastodonController.account?.id {
followButton.isHidden = true
} else {
followButton.isHidden = false
// while fetching the most up-to-date, show the current data (if any)
updateRelationship()
@ -183,6 +194,9 @@ class ProfileHeaderView: UIView {
private func updateRelationship() {
guard let mastodonController = mastodonController,
let relationship = mastodonController.persistentContainer.relationship(forAccount: accountID) else {
relationshipLabel.isHidden = true
followButton.isEnabled = false
followButtonMode = .follow
return
}
@ -202,6 +216,20 @@ class ProfileHeaderView: UIView {
} else {
relationshipLabel.isHidden = true
}
if relationship.blocking || relationship.domainBlocking {
followButton.isEnabled = false
followButtonMode = .blocked
} else {
followButton.isEnabled = true
if relationship.following {
followButtonMode = .unfollow
} else if relationship.requested {
followButtonMode = .cancelRequest
} else {
followButtonMode = .follow
}
}
}
@objc private func updateUIForPreferences() {
@ -282,11 +310,77 @@ class ProfileHeaderView: UIView {
delegate?.showLoadingLargeImage(url: header, cache: .headers, description: nil, animatingFrom: headerImageView)
}
@IBAction func followPressed() {
let req: Request<Relationship>
let action: String
switch followButtonMode {
case .follow:
req = Account.follow(accountID)
action = "Following"
case .unfollow, .cancelRequest:
req = Account.unfollow(accountID)
action = followButtonMode == .unfollow ? "Unfollowing" : "Cancelling Request"
case .blocked:
return
}
followButton.configuration = followButtonMode.makeConfig(showsActivityIndicator: true)
followButton.isEnabled = false
Task {
do {
let (relationship, _) = try await mastodonController.run(req)
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
// don't need to update the button, since the relationship observer will do so anyways
} catch {
followButton.isEnabled = true
followButton.configuration = followButtonMode.makeConfig(showsActivityIndicator: false)
if let toastable = delegate?.toastableViewController {
let config = ToastConfiguration(from: error, with: "Error \(action)", in: toastable) { toast in
toast.dismissToast(animated: true)
self.followPressed()
}
toastable.showToast(configuration: config, animated: true)
}
}
}
}
}
extension ProfileHeaderView {
enum FollowButtonMode {
case follow, unfollow, cancelRequest, blocked
func makeConfig(showsActivityIndicator: Bool) -> UIButton.Configuration {
var config = UIButton.Configuration.plain()
config.showsActivityIndicator = showsActivityIndicator
config.baseForegroundColor = .label
config.imagePadding = 4
config.cornerStyle = .capsule
var backgroundConfig = UIBackgroundConfiguration.clear()
backgroundConfig.visualEffect = UIBlurEffect(style: .systemThickMaterial)
config.background = backgroundConfig
switch self {
case .follow:
config.title = "Follow"
config.image = UIImage(systemName: "person.badge.plus")
case .unfollow:
config.title = "Unfollow"
config.image = UIImage(systemName: "person.badge.minus")
case .cancelRequest:
config.title = "Cancel Request"
config.image = UIImage(systemName: "person.badge.clock")
case .blocked:
config.title = "Blocked"
config.image = UIImage(systemName: "circle.slash")
}
return config
}
}
}
extension ProfileHeaderView: UIPointerInteractionDelegate {
func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
let preview = UITargetedPreview(view: moreButton)
let preview = UITargetedPreview(view: interaction.view!)
return UIPointerStyle(effect: .lift(preview), shape: .none)
}
}

View File

@ -59,6 +59,17 @@
<userDefinedRuntimeAttribute type="image" keyPath="image" value="ellipsis" catalog="system"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cr8-p9-xkc">
<rect key="frame" x="265" y="158" width="101" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="8Ww-Yo-g7G"/>
</constraints>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" image="person.badge.plus" catalog="system" title="Follow" imagePadding="4"/>
<connections>
<action selector="followPressed" destination="iN0-l3-epB" eventType="touchUpInside" id="OM3-lq-Z14"/>
</connections>
</button>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="u4P-3i-gEq">
<rect key="frame" x="16" y="266" width="398" height="596"/>
<subviews>
@ -130,6 +141,7 @@
<constraint firstItem="wT9-2J-uSY" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="WNS-AR-ff2"/>
<constraint firstItem="bRJ-Xf-kc9" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" constant="-8" id="ZB4-ys-9zP"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="vcl-Gl-kXl" secondAttribute="trailing" constant="16" id="e38-Od-kPg"/>
<constraint firstItem="cr8-p9-xkc" firstAttribute="trailing" secondItem="bRJ-Xf-kc9" secondAttribute="leading" constant="-8" id="f1L-S8-l6H"/>
<constraint firstItem="u4P-3i-gEq" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="hgl-UR-o3W"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="dgG-dR-lSv" secondAttribute="trailing" id="j0d-hY-815"/>
<constraint firstItem="5ja-fK-Fqz" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="jPG-WM-9km"/>
@ -137,12 +149,14 @@
<constraint firstAttribute="trailing" secondItem="u4P-3i-gEq" secondAttribute="trailing" id="ph6-NT-A02"/>
<constraint firstItem="u4P-3i-gEq" firstAttribute="top" secondItem="wT9-2J-uSY" secondAttribute="bottom" priority="999" constant="8" id="tKQ-6d-Z55"/>
<constraint firstItem="u4P-3i-gEq" firstAttribute="top" secondItem="jwU-EH-hmC" secondAttribute="bottom" priority="999" constant="8" id="xDD-rx-gC0"/>
<constraint firstItem="cr8-p9-xkc" firstAttribute="centerY" secondItem="bRJ-Xf-kc9" secondAttribute="centerY" id="xjr-Hn-Tuk"/>
</constraints>
<connections>
<outlet property="avatarContainerView" destination="wT9-2J-uSY" id="yEm-h7-tfq"/>
<outlet property="avatarImageView" destination="TkY-oK-if4" id="bSJ-7z-j4w"/>
<outlet property="displayNameLabel" destination="vcl-Gl-kXl" id="64n-a9-my0"/>
<outlet property="fieldsView" destination="vKC-m1-Sbs" id="FeE-jh-lYH"/>
<outlet property="followButton" destination="cr8-p9-xkc" id="E1n-gh-mCl"/>
<outlet property="headerImageView" destination="dgG-dR-lSv" id="HXT-v4-2iX"/>
<outlet property="lockImageView" destination="KNY-GD-beC" id="9EJ-iM-Eos"/>
<outlet property="moreButton" destination="bRJ-Xf-kc9" id="zIN-pz-L7y"/>
@ -151,12 +165,13 @@
<outlet property="usernameLabel" destination="1C3-Pd-QiL" id="57b-LQ-3pM"/>
<outlet property="vStack" destination="u4P-3i-gEq" id="EUC-d2-cQC"/>
</connections>
<point key="canvasLocation" x="-590" y="117"/>
<point key="canvasLocation" x="-591.304347826087" y="116.51785714285714"/>
</view>
</objects>
<resources>
<image name="ellipsis" catalog="system" width="128" height="37"/>
<image name="lock.fill" catalog="system" width="125" height="128"/>
<image name="person.badge.plus" catalog="system" width="128" height="125"/>
<systemColor name="labelColor">
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>