Add more prominent follow button to profile pages
This commit is contained in:
parent
86143c5887
commit
46cecde014
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue