Add following/unfollowing accounts

This commit is contained in:
Shadowfacts 2018-09-24 08:49:39 -04:00
parent a10b990073
commit d345cd5372
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
7 changed files with 119 additions and 40 deletions

View File

@ -138,8 +138,8 @@ public class Client {
return Request<[Status]>(method: .get, path: "/api/v1/favourites")
}
public func getRelationships(accounts: [Account]? = nil) -> Request<[Relationship]> {
return Request<[Relationship]>(method: .get, path: "/api/v1/accounts/relationships", queryParameters: "id" => accounts?.map { $0.id })
public func getRelationships(accounts: [String]? = nil) -> Request<[Relationship]> {
return Request<[Relationship]>(method: .get, path: "/api/v1/accounts/relationships", queryParameters: "id" => accounts)
}
public func getInstance() -> Request<Instance> {

View File

@ -63,12 +63,12 @@ public class Account: Decodable {
return request
}
public static func follow(_ account: Account) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(account.id)/follow")
public static func follow(_ accountID: String) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/follow")
}
public static func unfollow(_ account: Account) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(account.id)/unfollow")
public static func unfollow(_ accountID: String) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unfollow")
}
public static func block(_ account: Account) -> Request<Relationship> {

View File

@ -12,7 +12,7 @@ public class Relationship: Decodable {
public let id: String
public let following: Bool
public let followedBy: Bool
public let blocked: Bool
public let blocking: Bool
public let muting: Bool
public let mutingNotifications: Bool
public let followRequested: Bool
@ -23,7 +23,7 @@ public class Relationship: Decodable {
case id
case following
case followedBy = "followed_by"
case blocked
case blocking
case muting
case mutingNotifications = "muting_notifications"
case followRequested = "requested"

View File

@ -13,6 +13,7 @@ class MastodonCache {
private static let statuses = NSCache<NSString, Status>()
private static let accounts = NSCache<NSString, Account>()
private static let relationships = NSCache<NSString, Relationship>()
// MARK: - Statuses
static func status(for id: String) -> Status? {
@ -76,4 +77,34 @@ class MastodonCache {
accounts.forEach(add)
}
// MARK: - Relationships
static func relationship(for id: String) -> Relationship? {
return relationships.object(forKey: id as NSString)
}
static func set(relationship: Relationship, id: String) {
relationships.setObject(relationship, forKey: id as NSString)
}
static func relationship(for id: String, completion: @escaping (Relationship?) -> Void) {
let request = MastodonController.shared.client.getRelationships(accounts: [id])
MastodonController.shared.client.run(request) { response in
guard case let .success(relationships, _) = response,
let relationship = relationships.first else {
completion(nil)
return
}
set(relationship: relationship, id: relationship.id)
completion(relationship)
}
}
static func add(relationship: Relationship) {
set(relationship: relationship, id: relationship.id)
}
static func addAll(relationships: [Relationship]) {
relationships.forEach(add)
}
}

View File

@ -187,7 +187,32 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
self.sendMessageMentioning()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true)
MastodonCache.relationship(for: account.id) { (relationship) in
guard let relationship = relationship else {
DispatchQueue.main.async {
self.present(alert, animated: true)
}
return
}
let title = relationship.following ? "Unfollow": "Follow"
DispatchQueue.main.async {
alert.addAction(UIAlertAction(title: title, style: .default, handler: { (_) in
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
let request = (relationship.following ? Account.unfollow : Account.follow)(account.id)
MastodonController.shared.client.run(request, completion: { (response) in
if case let .success(relationship, _) = response {
MastodonCache.add(relationship: relationship)
} else {
// todo: display error message
UINotificationFeedbackGenerator().notificationOccurred(.error)
fatalError()
}
})
}))
self.present(alert, animated: true)
}
}
}
}

View File

@ -18,13 +18,14 @@ protocol ProfileHeaderTableViewCellDelegate: StatusTableViewCellDelegate {
class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
var delegate: ProfileHeaderTableViewCellDelegate?
@IBOutlet weak var displayNameLabel: UILabel!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var noteLabel: HTMLContentLabel!
@IBOutlet weak var headerImageView: UIImageView!
@IBOutlet weak var avatarContainerView: UIView!
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var headerImageView: UIImageView!
@IBOutlet weak var displayNameLabel: UILabel!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var followsYouLabel: UILabel!
@IBOutlet weak var noteLabel: HTMLContentLabel!
var accountID: String!
@ -77,6 +78,16 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
// todo: HTML parsing
noteLabel.text = account.note
noteLabel.delegate = self
if let relationship = MastodonCache.relationship(for: accountID) {
followsYouLabel.isHidden = !relationship.followedBy
} else {
MastodonCache.relationship(for: accountID) { relationship in
DispatchQueue.main.async {
self.followsYouLabel.isHidden = !(relationship?.followedBy ?? false)
}
}
}
}
override func prepareForReuse() {

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -12,7 +12,7 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ProfileHeaderTableViewCell" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="270"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="296"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Fw7-OL-iy5">
@ -21,24 +21,6 @@
<constraint firstAttribute="height" constant="150" id="y43-4B-slK"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LjK-72-Bez">
<rect key="frame" x="144" y="158" width="215" height="24"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Note" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="I0n-aP-dJP" customClass="HTMLContentLabel" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="16" y="215" width="343" height="39"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MIj-OR-NOR">
<rect key="frame" x="144" y="190" width="215" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="KyB-ey-l11">
<rect key="frame" x="16" y="90" width="120" height="120"/>
<subviews>
@ -59,6 +41,35 @@
<constraint firstItem="tH8-sR-DHC" firstAttribute="centerY" secondItem="KyB-ey-l11" secondAttribute="centerY" id="nYu-RE-MfA"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LjK-72-Bez">
<rect key="frame" x="144" y="158" width="215" height="24"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MIj-OR-NOR">
<rect key="frame" x="144" y="190" width="215" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="DfO-uD-UNI">
<rect key="frame" x="16" y="218" width="343" height="70"/>
<subviews>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Follows you" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a32-1a-xXZ">
<rect key="frame" x="0.0" y="0.0" width="75.5" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Note" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="I0n-aP-dJP" customClass="HTMLContentLabel" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="37" height="70"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<button opaque="NO" alpha="0.59999999999999998" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qiv-gB-kiX">
<rect key="frame" x="309" y="117" width="50" height="25"/>
<constraints>
@ -75,22 +86,22 @@
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Fw7-OL-iy5" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="0fI-0y-cXG"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="DfO-uD-UNI" secondAttribute="trailing" constant="16" id="ASp-mh-SFv"/>
<constraint firstItem="KyB-ey-l11" firstAttribute="centerY" secondItem="Fw7-OL-iy5" secondAttribute="bottom" id="AXr-6X-FJ8"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="MIj-OR-NOR" secondAttribute="trailing" constant="16" id="AwT-Vi-CLa"/>
<constraint firstItem="LjK-72-Bez" firstAttribute="leading" secondItem="KyB-ey-l11" secondAttribute="trailing" constant="8" id="CIO-tn-hJC"/>
<constraint firstItem="I0n-aP-dJP" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="JlW-us-7Wd"/>
<constraint firstItem="LjK-72-Bez" firstAttribute="top" secondItem="Fw7-OL-iy5" secondAttribute="bottom" constant="8" id="Kvl-sz-Lv3"/>
<constraint firstItem="DfO-uD-UNI" firstAttribute="top" secondItem="KyB-ey-l11" secondAttribute="bottom" constant="8" id="Ljh-8x-yI3"/>
<constraint firstItem="Fw7-OL-iy5" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="LqH-lE-AIe"/>
<constraint firstItem="KyB-ey-l11" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="NN7-5B-k1Q"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="I0n-aP-dJP" secondAttribute="trailing" constant="16" id="aaC-xJ-vvs"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="DfO-uD-UNI" secondAttribute="bottom" constant="8" id="aj3-iF-QBe"/>
<constraint firstItem="Fw7-OL-iy5" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="d1j-6d-hBb"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="LjK-72-Bez" secondAttribute="trailing" constant="16" id="hn9-c3-iNH"/>
<constraint firstItem="MIj-OR-NOR" firstAttribute="leading" secondItem="KyB-ey-l11" secondAttribute="trailing" constant="8" id="iG7-yZ-9u3"/>
<constraint firstItem="MIj-OR-NOR" firstAttribute="top" secondItem="LjK-72-Bez" secondAttribute="bottom" constant="8" id="nMM-6t-bjX"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="I0n-aP-dJP" secondAttribute="bottom" constant="16" id="nPu-bH-pLA"/>
<constraint firstAttribute="trailing" secondItem="qiv-gB-kiX" secondAttribute="trailing" constant="16" id="nYG-p6-Ezm"/>
<constraint firstItem="qiv-gB-kiX" firstAttribute="bottom" secondItem="Fw7-OL-iy5" secondAttribute="bottom" constant="-8" id="pg7-L7-u2f"/>
<constraint firstItem="I0n-aP-dJP" firstAttribute="top" secondItem="MIj-OR-NOR" secondAttribute="bottom" constant="8" id="upb-uc-3BZ"/>
<constraint firstItem="DfO-uD-UNI" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="pqd-E3-Aw4"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
@ -98,11 +109,12 @@
<outlet property="avatarContainerView" destination="KyB-ey-l11" id="45s-jV-l8L"/>
<outlet property="avatarImageView" destination="tH8-sR-DHC" id="6ll-yL-g1o"/>
<outlet property="displayNameLabel" destination="LjK-72-Bez" id="nIU-ey-H1C"/>
<outlet property="followsYouLabel" destination="a32-1a-xXZ" id="phY-0L-NnN"/>
<outlet property="headerImageView" destination="Fw7-OL-iy5" id="6sv-E5-D73"/>
<outlet property="noteLabel" destination="I0n-aP-dJP" id="7yW-mE-jxY"/>
<outlet property="usernameLabel" destination="MIj-OR-NOR" id="e1I-N7-rKx"/>
</connections>
<point key="canvasLocation" x="40.799999999999997" y="98.950524737631198"/>
<point key="canvasLocation" x="40.799999999999997" y="110.64467766116942"/>
</view>
</objects>
<resources>