forked from shadowfacts/Tusker
Show link cards on statuses
This commit is contained in:
parent
80b3585b71
commit
39b244384b
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class Card: Decodable {
|
public class Card: Codable {
|
||||||
public let url: URL
|
public let url: URL
|
||||||
public let title: String
|
public let title: String
|
||||||
public let description: String
|
public let description: String
|
||||||
|
@ -21,6 +21,7 @@ public class Card: Decodable {
|
||||||
public let html: String?
|
public let html: String?
|
||||||
public let width: Int?
|
public let width: Int?
|
||||||
public let height: Int?
|
public let height: Int?
|
||||||
|
public let blurhash: String?
|
||||||
|
|
||||||
public required init(from decoder: Decoder) throws {
|
public required init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
@ -29,14 +30,26 @@ public class Card: Decodable {
|
||||||
self.title = try container.decode(String.self, forKey: .title)
|
self.title = try container.decode(String.self, forKey: .title)
|
||||||
self.description = try container.decode(String.self, forKey: .description)
|
self.description = try container.decode(String.self, forKey: .description)
|
||||||
self.kind = try container.decode(Kind.self, forKey: .kind)
|
self.kind = try container.decode(Kind.self, forKey: .kind)
|
||||||
self.image = try? container.decode(URL.self, forKey: .image)
|
self.image = try? container.decodeIfPresent(URL.self, forKey: .image)
|
||||||
self.authorName = try? container.decode(String.self, forKey: .authorName)
|
self.authorName = try? container.decodeIfPresent(String.self, forKey: .authorName)
|
||||||
self.authorURL = try? container.decode(URL.self, forKey: .authorURL)
|
self.authorURL = try? container.decodeIfPresent(URL.self, forKey: .authorURL)
|
||||||
self.providerName = try? container.decode(String.self, forKey: .providerName)
|
self.providerName = try? container.decodeIfPresent(String.self, forKey: .providerName)
|
||||||
self.providerURL = try? container.decode(URL.self, forKey: .providerURL)
|
self.providerURL = try? container.decodeIfPresent(URL.self, forKey: .providerURL)
|
||||||
self.html = try? container.decode(String.self, forKey: .html)
|
self.html = try? container.decodeIfPresent(String.self, forKey: .html)
|
||||||
self.width = try? container.decode(Int.self, forKey: .width)
|
self.width = try? container.decodeIfPresent(Int.self, forKey: .width)
|
||||||
self.height = try? container.decode(Int.self, forKey: .height)
|
self.height = try? container.decodeIfPresent(Int.self, forKey: .height)
|
||||||
|
self.blurhash = try? container.decodeIfPresent(String.self, forKey: .blurhash)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
try container.encode(url, forKey: .url)
|
||||||
|
try container.encode(title, forKey: .title)
|
||||||
|
try container.encode(description, forKey: .description)
|
||||||
|
try container.encode(kind, forKey: .kind)
|
||||||
|
try container.encode(image, forKey: .image)
|
||||||
|
try container.encode(blurhash, forKey: .blurhash)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
@ -52,14 +65,14 @@ public class Card: Decodable {
|
||||||
case html
|
case html
|
||||||
case width
|
case width
|
||||||
case height
|
case height
|
||||||
|
case blurhash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Card {
|
extension Card {
|
||||||
public enum Kind: String, Decodable {
|
public enum Kind: String, Codable {
|
||||||
case link
|
case link
|
||||||
case photo
|
case photo
|
||||||
case video
|
case video
|
||||||
case rich
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,6 +244,7 @@
|
||||||
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */; };
|
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */; };
|
||||||
D6C143E025354E34007DC240 /* EmojiPickerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */; };
|
D6C143E025354E34007DC240 /* EmojiPickerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */; };
|
||||||
D6C143FD25354FD0007DC240 /* EmojiCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */; };
|
D6C143FD25354FD0007DC240 /* EmojiCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */; };
|
||||||
|
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
||||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
||||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
||||||
|
@ -579,6 +580,7 @@
|
||||||
D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeContentWarningTextField.swift; sourceTree = "<group>"; };
|
D6C143D9253510F4007DC240 /* ComposeContentWarningTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeContentWarningTextField.swift; sourceTree = "<group>"; };
|
||||||
D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCollectionViewController.swift; sourceTree = "<group>"; };
|
D6C143DF25354E34007DC240 /* EmojiPickerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6C143FB25354FD0007DC240 /* EmojiCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCardView.swift; sourceTree = "<group>"; };
|
||||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerNavigationDelegate.swift; sourceTree = "<group>"; };
|
||||||
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
||||||
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Children.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1061,6 +1063,7 @@
|
||||||
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */,
|
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */,
|
||||||
D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */,
|
D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */,
|
||||||
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */,
|
D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */,
|
||||||
|
D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */,
|
||||||
);
|
);
|
||||||
path = Status;
|
path = Status;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1945,6 +1948,7 @@
|
||||||
D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */,
|
D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */,
|
||||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||||
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
||||||
|
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */,
|
||||||
D6412B0524B0227D00F5412E /* ProfileViewController.swift in Sources */,
|
D6412B0524B0227D00F5412E /* ProfileViewController.swift in Sources */,
|
||||||
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */,
|
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */,
|
||||||
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */,
|
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */,
|
||||||
|
|
|
@ -21,6 +21,7 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||||
@NSManaged public var applicationName: String?
|
@NSManaged public var applicationName: String?
|
||||||
@NSManaged private var attachmentsData: Data?
|
@NSManaged private var attachmentsData: Data?
|
||||||
@NSManaged private var bookmarkedInternal: Bool
|
@NSManaged private var bookmarkedInternal: Bool
|
||||||
|
@NSManaged public var cardData: Data?
|
||||||
@NSManaged public var content: String
|
@NSManaged public var content: String
|
||||||
@NSManaged public var createdAt: Date
|
@NSManaged public var createdAt: Date
|
||||||
@NSManaged private var emojisData: Data?
|
@NSManaged private var emojisData: Data?
|
||||||
|
@ -56,6 +57,9 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||||
@LazilyDecoding(arrayFrom: \StatusMO.mentionsData)
|
@LazilyDecoding(arrayFrom: \StatusMO.mentionsData)
|
||||||
public var mentions: [Mention]
|
public var mentions: [Mention]
|
||||||
|
|
||||||
|
@LazilyDecoding(from: \StatusMO.cardData, fallback: nil)
|
||||||
|
public var card: Card?
|
||||||
|
|
||||||
public var pinned: Bool? { pinnedInternal }
|
public var pinned: Bool? { pinnedInternal }
|
||||||
public var bookmarked: Bool? { bookmarkedInternal }
|
public var bookmarked: Bool? { bookmarkedInternal }
|
||||||
|
|
||||||
|
@ -105,6 +109,7 @@ extension StatusMO {
|
||||||
self.applicationName = status.application?.name
|
self.applicationName = status.application?.name
|
||||||
self.attachments = status.attachments
|
self.attachments = status.attachments
|
||||||
self.bookmarkedInternal = status.bookmarked ?? false
|
self.bookmarkedInternal = status.bookmarked ?? false
|
||||||
|
self.card = status.card
|
||||||
self.content = status.content
|
self.content = status.content
|
||||||
self.createdAt = status.createdAt
|
self.createdAt = status.createdAt
|
||||||
self.emojis = status.emojis
|
self.emojis = status.emojis
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17507" systemVersion="19G2021" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17510.1" systemVersion="19G2021" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Account" representedClassName="AccountMO" syncable="YES">
|
<entity name="Account" representedClassName="AccountMO" syncable="YES">
|
||||||
<attribute name="acct" attributeType="String"/>
|
<attribute name="acct" attributeType="String"/>
|
||||||
<attribute name="avatar" attributeType="URI"/>
|
<attribute name="avatar" attributeType="URI"/>
|
||||||
|
@ -44,6 +44,7 @@
|
||||||
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
||||||
<attribute name="attachmentsData" attributeType="Binary"/>
|
<attribute name="attachmentsData" attributeType="Binary"/>
|
||||||
<attribute name="bookmarkedInternal" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="bookmarkedInternal" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="cardData" optional="YES" attributeType="Binary"/>
|
||||||
<attribute name="content" attributeType="String"/>
|
<attribute name="content" attributeType="String"/>
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="emojisData" attributeType="Binary" customClassName="[Data]"/>
|
<attribute name="emojisData" attributeType="Binary" customClassName="[Data]"/>
|
||||||
|
@ -74,7 +75,7 @@
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="343"/>
|
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="343"/>
|
||||||
<element name="Status" positionX="-63" positionY="-18" width="128" height="418"/>
|
|
||||||
<element name="Relationship" positionX="63" positionY="135" width="128" height="208"/>
|
<element name="Relationship" positionX="63" positionY="135" width="128" height="208"/>
|
||||||
|
<element name="Status" positionX="-63" positionY="-18" width="128" height="433"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
|
@ -32,6 +32,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
@IBOutlet weak var contentWarningLabel: EmojiLabel!
|
@IBOutlet weak var contentWarningLabel: EmojiLabel!
|
||||||
@IBOutlet weak var collapseButton: UIButton!
|
@IBOutlet weak var collapseButton: UIButton!
|
||||||
@IBOutlet weak var contentTextView: StatusContentTextView!
|
@IBOutlet weak var contentTextView: StatusContentTextView!
|
||||||
|
@IBOutlet weak var cardView: StatusCardView!
|
||||||
@IBOutlet weak var attachmentsView: AttachmentsContainerView!
|
@IBOutlet weak var attachmentsView: AttachmentsContainerView!
|
||||||
@IBOutlet weak var replyButton: UIButton!
|
@IBOutlet weak var replyButton: UIButton!
|
||||||
@IBOutlet weak var favoriteButton: UIButton!
|
@IBOutlet weak var favoriteButton: UIButton!
|
||||||
|
@ -143,6 +144,10 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
updateUI(account: account)
|
updateUI(account: account)
|
||||||
updateUIForPreferences(account: account, status: status)
|
updateUIForPreferences(account: account, status: status)
|
||||||
|
|
||||||
|
cardView.card = status.card
|
||||||
|
cardView.isHidden = status.card == nil
|
||||||
|
cardView.navigationDelegate = navigationDelegate
|
||||||
|
|
||||||
attachmentsView.updateUI(status: status)
|
attachmentsView.updateUI(status: status)
|
||||||
attachmentsView.isAccessibilityElement = status.attachments.count > 0
|
attachmentsView.isAccessibilityElement = status.attachments.count > 0
|
||||||
attachmentsView.accessibilityLabel = String(format: NSLocalizedString("%d attachments", comment: "status attachments count accessibility label"), status.attachments.count)
|
attachmentsView.accessibilityLabel = String(format: NSLocalizedString("%d attachments", comment: "status attachments count accessibility label"), status.attachments.count)
|
||||||
|
@ -265,6 +270,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
self.collapsed = collapsed
|
self.collapsed = collapsed
|
||||||
|
|
||||||
contentTextView.isHidden = collapsed
|
contentTextView.isHidden = collapsed
|
||||||
|
cardView.isHidden = cardView.card == nil || collapsed
|
||||||
attachmentsView.isHidden = attachmentsView.attachments.count == 0 || collapsed
|
attachmentsView.isHidden = attachmentsView.attachments.count == 0 || collapsed
|
||||||
|
|
||||||
let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up")!
|
let buttonImage = UIImage(systemName: collapsed ? "chevron.down" : "chevron.up")!
|
||||||
|
|
|
@ -96,6 +96,13 @@
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
</textView>
|
</textView>
|
||||||
|
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QqC-GR-TLC" customClass="StatusCardView" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="176" width="343" height="0.0"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" priority="999" constant="65" id="Tdo-Hv-ITE"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IF9-9U-Gk0" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IF9-9U-Gk0" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="176" width="343" height="0.0"/>
|
<rect key="frame" x="0.0" y="176" width="343" height="0.0"/>
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
|
||||||
|
@ -205,6 +212,7 @@
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
|
<constraint firstItem="QqC-GR-TLC" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="2WL-jD-I09"/>
|
||||||
<constraint firstItem="Cnd-Fj-B7l" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="2hS-RG-81T"/>
|
<constraint firstItem="Cnd-Fj-B7l" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="2hS-RG-81T"/>
|
||||||
<constraint firstItem="z0g-HN-gS0" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="4TF-2Z-mdf"/>
|
<constraint firstItem="z0g-HN-gS0" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="4TF-2Z-mdf"/>
|
||||||
<constraint firstItem="IF9-9U-Gk0" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="8A8-wi-7sg"/>
|
<constraint firstItem="IF9-9U-Gk0" firstAttribute="width" secondItem="GuG-Qd-B8I" secondAttribute="width" id="8A8-wi-7sg"/>
|
||||||
|
@ -227,6 +235,7 @@
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="attachmentsView" destination="IF9-9U-Gk0" id="Oxw-sJ-MJE"/>
|
<outlet property="attachmentsView" destination="IF9-9U-Gk0" id="Oxw-sJ-MJE"/>
|
||||||
<outlet property="avatarImageView" destination="mB9-HO-1vf" id="0R0-rt-Osh"/>
|
<outlet property="avatarImageView" destination="mB9-HO-1vf" id="0R0-rt-Osh"/>
|
||||||
|
<outlet property="cardView" destination="QqC-GR-TLC" id="CWR-fH-IfE"/>
|
||||||
<outlet property="collapseButton" destination="8r8-O8-Agh" id="0es-Hi-bpt"/>
|
<outlet property="collapseButton" destination="8r8-O8-Agh" id="0es-Hi-bpt"/>
|
||||||
<outlet property="contentTextView" destination="z0g-HN-gS0" id="atk-1f-83e"/>
|
<outlet property="contentTextView" destination="z0g-HN-gS0" id="atk-1f-83e"/>
|
||||||
<outlet property="contentWarningLabel" destination="cwQ-mR-L1b" id="5sm-PC-FIN"/>
|
<outlet property="contentWarningLabel" destination="cwQ-mR-L1b" id="5sm-PC-FIN"/>
|
||||||
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
//
|
||||||
|
// StatusCardView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 10/25/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
import SafariServices
|
||||||
|
|
||||||
|
class StatusCardView: UIView {
|
||||||
|
|
||||||
|
weak var navigationDelegate: TuskerNavigationDelegate?
|
||||||
|
|
||||||
|
var card: Card? {
|
||||||
|
didSet {
|
||||||
|
if let card = card {
|
||||||
|
self.updateUI(card: card)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let activeBackgroundColor = UIColor.secondarySystemFill
|
||||||
|
private let inactiveBackgroundColor = UIColor.secondarySystemBackground
|
||||||
|
|
||||||
|
private var imageRequest: ImageCache.Request?
|
||||||
|
|
||||||
|
private var titleLabel: UILabel!
|
||||||
|
private var descriptionLabel: UILabel!
|
||||||
|
private var imageView: UIImageView!
|
||||||
|
private var placeholderImageView: UIImageView!
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
commonInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
commonInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func commonInit() {
|
||||||
|
self.clipsToBounds = true
|
||||||
|
self.layer.cornerRadius = 6.5
|
||||||
|
self.layer.borderWidth = 1
|
||||||
|
self.layer.borderColor = UIColor.lightGray.cgColor
|
||||||
|
self.backgroundColor = inactiveBackgroundColor
|
||||||
|
|
||||||
|
self.addInteraction(UIContextMenuInteraction(delegate: self))
|
||||||
|
|
||||||
|
titleLabel = UILabel()
|
||||||
|
titleLabel.font = UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline).withSymbolicTraits(.traitBold)!, size: 0)
|
||||||
|
titleLabel.numberOfLines = 2
|
||||||
|
|
||||||
|
descriptionLabel = UILabel()
|
||||||
|
descriptionLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .caption1), size: 0)
|
||||||
|
descriptionLabel.numberOfLines = 2
|
||||||
|
descriptionLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
|
||||||
|
|
||||||
|
let vStack = UIStackView(arrangedSubviews: [
|
||||||
|
titleLabel,
|
||||||
|
descriptionLabel
|
||||||
|
])
|
||||||
|
vStack.axis = .vertical
|
||||||
|
vStack.alignment = .leading
|
||||||
|
vStack.distribution = .fill
|
||||||
|
vStack.spacing = 0
|
||||||
|
|
||||||
|
imageView = UIImageView()
|
||||||
|
imageView.contentMode = .scaleAspectFill
|
||||||
|
imageView.clipsToBounds = true
|
||||||
|
|
||||||
|
let hStack = UIStackView(arrangedSubviews: [
|
||||||
|
imageView,
|
||||||
|
vStack
|
||||||
|
])
|
||||||
|
hStack.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
hStack.axis = .horizontal
|
||||||
|
hStack.alignment = .center
|
||||||
|
hStack.distribution = .fill
|
||||||
|
hStack.spacing = 4
|
||||||
|
|
||||||
|
addSubview(hStack)
|
||||||
|
|
||||||
|
placeholderImageView = UIImageView(image: UIImage(systemName: "doc.text"))
|
||||||
|
placeholderImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
placeholderImageView.contentMode = .scaleAspectFit
|
||||||
|
placeholderImageView.tintColor = .gray
|
||||||
|
|
||||||
|
addSubview(placeholderImageView)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
imageView.heightAnchor.constraint(equalTo: heightAnchor),
|
||||||
|
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
|
||||||
|
|
||||||
|
vStack.heightAnchor.constraint(equalTo: heightAnchor, constant: -8),
|
||||||
|
|
||||||
|
hStack.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
hStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -4),
|
||||||
|
hStack.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
hStack.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
|
|
||||||
|
placeholderImageView.widthAnchor.constraint(equalToConstant: 30),
|
||||||
|
placeholderImageView.heightAnchor.constraint(equalToConstant: 30),
|
||||||
|
placeholderImageView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
||||||
|
placeholderImageView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateUI(card: Card) {
|
||||||
|
self.imageView.image = nil
|
||||||
|
|
||||||
|
if let image = card.image {
|
||||||
|
placeholderImageView.isHidden = true
|
||||||
|
|
||||||
|
imageRequest = ImageCache.attachments.get(image, completion: { (data) in
|
||||||
|
guard let data = data, let image = UIImage(data: data) else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.imageView.image = image
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if imageRequest != nil {
|
||||||
|
loadBlurHash()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
placeholderImageView.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
titleLabel.text = title
|
||||||
|
titleLabel.isHidden = title.isEmpty
|
||||||
|
|
||||||
|
let description = card.description.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
descriptionLabel.text = description
|
||||||
|
descriptionLabel.isHidden = description.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadBlurHash() {
|
||||||
|
guard let card = card, let hash = card.blurhash else { return }
|
||||||
|
|
||||||
|
let imageViewSize = self.imageView.bounds.size
|
||||||
|
|
||||||
|
// todo: merge this code with AttachmentView, use a single DispatchQueue
|
||||||
|
DispatchQueue.global(qos: .default).async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
let size: CGSize
|
||||||
|
if let width = card.width, let height = card.height {
|
||||||
|
size = CGSize(width: width, height: height)
|
||||||
|
} else {
|
||||||
|
size = imageViewSize
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let preview = UIImage(blurHash: hash, size: size) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self = self,
|
||||||
|
self.card?.url == card.url,
|
||||||
|
self.imageView.image == nil else { return }
|
||||||
|
|
||||||
|
self.imageView.image = preview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
backgroundColor = activeBackgroundColor
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
backgroundColor = inactiveBackgroundColor
|
||||||
|
setNeedsDisplay()
|
||||||
|
|
||||||
|
if let card = card, let delegate = navigationDelegate {
|
||||||
|
delegate.selected(url: card.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
backgroundColor = inactiveBackgroundColor
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusCardView: MenuPreviewProvider {
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusCardView: UIContextMenuInteractionDelegate {
|
||||||
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
|
guard let card = card else { return nil }
|
||||||
|
|
||||||
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
|
return SFSafariViewController(url: card.url)
|
||||||
|
} actionProvider: { (_) in
|
||||||
|
let actions = self.actionsForURL(card.url, sourceView: self)
|
||||||
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
|
if let viewController = animator.previewViewController,
|
||||||
|
let delegate = navigationDelegate {
|
||||||
|
animator.preferredCommitStyle = .pop
|
||||||
|
animator.addCompletion {
|
||||||
|
if let customPresenting = viewController as? CustomPreviewPresenting {
|
||||||
|
customPresenting.presentFromPreview(presenter: delegate)
|
||||||
|
} else {
|
||||||
|
delegate.show(viewController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,6 +114,13 @@
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
</textView>
|
</textView>
|
||||||
|
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LKo-VB-XWl" customClass="StatusCardView" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="169.5" width="277" height="0.0"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" priority="999" constant="65" id="khY-jm-CPn"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="169.5" width="277" height="0.0"/>
|
<rect key="frame" x="0.0" y="169.5" width="277" height="0.0"/>
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
|
||||||
|
@ -238,6 +245,7 @@
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="attachmentsView" destination="nbq-yr-2mA" id="SVm-zl-mPb"/>
|
<outlet property="attachmentsView" destination="nbq-yr-2mA" id="SVm-zl-mPb"/>
|
||||||
<outlet property="avatarImageView" destination="QMP-j2-HLn" id="xfS-v8-Gzu"/>
|
<outlet property="avatarImageView" destination="QMP-j2-HLn" id="xfS-v8-Gzu"/>
|
||||||
|
<outlet property="cardView" destination="LKo-VB-XWl" id="6X5-8P-Ata"/>
|
||||||
<outlet property="collapseButton" destination="O0E-Vf-XYR" id="nWd-gg-st8"/>
|
<outlet property="collapseButton" destination="O0E-Vf-XYR" id="nWd-gg-st8"/>
|
||||||
<outlet property="contentTextView" destination="waJ-f5-LKv" id="hrR-Zg-gLY"/>
|
<outlet property="contentTextView" destination="waJ-f5-LKv" id="hrR-Zg-gLY"/>
|
||||||
<outlet property="contentWarningLabel" destination="inI-Og-YiU" id="C7a-eK-qcx"/>
|
<outlet property="contentWarningLabel" destination="inI-Og-YiU" id="C7a-eK-qcx"/>
|
||||||
|
|
Loading…
Reference in New Issue