Add following/unfollowing accounts
This commit is contained in:
parent
a10b990073
commit
d345cd5372
|
@ -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> {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,12 +19,13 @@ 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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue