Add following/unfollowing accounts

This commit is contained in:
Shadowfacts 2018-09-24 08:49:39 -04:00
parent e971c7f61c
commit bcb9e979a0
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") return Request<[Status]>(method: .get, path: "/api/v1/favourites")
} }
public func getRelationships(accounts: [Account]? = nil) -> Request<[Relationship]> { public func getRelationships(accounts: [String]? = nil) -> Request<[Relationship]> {
return Request<[Relationship]>(method: .get, path: "/api/v1/accounts/relationships", queryParameters: "id" => accounts?.map { $0.id }) return Request<[Relationship]>(method: .get, path: "/api/v1/accounts/relationships", queryParameters: "id" => accounts)
} }
public func getInstance() -> Request<Instance> { public func getInstance() -> Request<Instance> {

View File

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

View File

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

View File

@ -13,6 +13,7 @@ class MastodonCache {
private static let statuses = NSCache<NSString, Status>() private static let statuses = NSCache<NSString, Status>()
private static let accounts = NSCache<NSString, Account>() private static let accounts = NSCache<NSString, Account>()
private static let relationships = NSCache<NSString, Relationship>()
// MARK: - Statuses // MARK: - Statuses
static func status(for id: String) -> Status? { static func status(for id: String) -> Status? {
@ -76,4 +77,34 @@ class MastodonCache {
accounts.forEach(add) 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() self.sendMessageMentioning()
})) }))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 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 { class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
var delegate: ProfileHeaderTableViewCellDelegate? var delegate: ProfileHeaderTableViewCellDelegate?
@IBOutlet weak var displayNameLabel: UILabel! @IBOutlet weak var headerImageView: UIImageView!
@IBOutlet weak var usernameLabel: UILabel!
@IBOutlet weak var noteLabel: HTMLContentLabel!
@IBOutlet weak var avatarContainerView: UIView! @IBOutlet weak var avatarContainerView: UIView!
@IBOutlet weak var avatarImageView: UIImageView! @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! var accountID: String!
@ -77,6 +78,16 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
// todo: HTML parsing // todo: HTML parsing
noteLabel.text = account.note noteLabel.text = account.note
noteLabel.delegate = self 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() { override func prepareForReuse() {

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <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="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -12,7 +12,7 @@
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ProfileHeaderTableViewCell" customModule="Tusker" customModuleProvider="target"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Fw7-OL-iy5"> <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"/> <constraint firstAttribute="height" constant="150" id="y43-4B-slK"/>
</constraints> </constraints>
</imageView> </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"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="KyB-ey-l11">
<rect key="frame" x="16" y="90" width="120" height="120"/> <rect key="frame" x="16" y="90" width="120" height="120"/>
<subviews> <subviews>
@ -59,6 +41,35 @@
<constraint firstItem="tH8-sR-DHC" firstAttribute="centerY" secondItem="KyB-ey-l11" secondAttribute="centerY" id="nYu-RE-MfA"/> <constraint firstItem="tH8-sR-DHC" firstAttribute="centerY" secondItem="KyB-ey-l11" secondAttribute="centerY" id="nYu-RE-MfA"/>
</constraints> </constraints>
</view> </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"> <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"/> <rect key="frame" x="309" y="117" width="50" height="25"/>
<constraints> <constraints>
@ -75,22 +86,22 @@
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstItem="Fw7-OL-iy5" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="0fI-0y-cXG"/> <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="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="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="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="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="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="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="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="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="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="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 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="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> </constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/> <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
@ -98,11 +109,12 @@
<outlet property="avatarContainerView" destination="KyB-ey-l11" id="45s-jV-l8L"/> <outlet property="avatarContainerView" destination="KyB-ey-l11" id="45s-jV-l8L"/>
<outlet property="avatarImageView" destination="tH8-sR-DHC" id="6ll-yL-g1o"/> <outlet property="avatarImageView" destination="tH8-sR-DHC" id="6ll-yL-g1o"/>
<outlet property="displayNameLabel" destination="LjK-72-Bez" id="nIU-ey-H1C"/> <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="headerImageView" destination="Fw7-OL-iy5" id="6sv-E5-D73"/>
<outlet property="noteLabel" destination="I0n-aP-dJP" id="7yW-mE-jxY"/> <outlet property="noteLabel" destination="I0n-aP-dJP" id="7yW-mE-jxY"/>
<outlet property="usernameLabel" destination="MIj-OR-NOR" id="e1I-N7-rKx"/> <outlet property="usernameLabel" destination="MIj-OR-NOR" id="e1I-N7-rKx"/>
</connections> </connections>
<point key="canvasLocation" x="40.799999999999997" y="98.950524737631198"/> <point key="canvasLocation" x="40.799999999999997" y="110.64467766116942"/>
</view> </view>
</objects> </objects>
<resources> <resources>