forked from zelo72/mastodon-ios
Merge branch 'develop' into release/0.4.0
# Conflicts: # Mastodon/Scene/Share/View/Content/StatusView.swift
This commit is contained in:
commit
36273467c1
|
@ -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="18154" systemVersion="20D75" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="18154" systemVersion="20E232" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Application" representedClassName=".Application" syncable="YES">
|
<entity name="Application" representedClassName=".Application" syncable="YES">
|
||||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||||
<attribute name="name" attributeType="String"/>
|
<attribute name="name" attributeType="String"/>
|
||||||
|
@ -45,7 +45,6 @@
|
||||||
<attribute name="staticURL" attributeType="String"/>
|
<attribute name="staticURL" attributeType="String"/>
|
||||||
<attribute name="url" attributeType="String"/>
|
<attribute name="url" attributeType="String"/>
|
||||||
<attribute name="visibleInPicker" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="visibleInPicker" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="emojis" inverseEntity="Status"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="History" representedClassName=".History" syncable="YES">
|
<entity name="History" representedClassName=".History" syncable="YES">
|
||||||
<attribute name="accounts" optional="YES" attributeType="String"/>
|
<attribute name="accounts" optional="YES" attributeType="String"/>
|
||||||
|
@ -87,7 +86,7 @@
|
||||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="userID" attributeType="String"/>
|
<attribute name="userID" attributeType="String"/>
|
||||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
|
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
|
||||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
|
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="inNotifications" inverseEntity="Status"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
<uniquenessConstraint>
|
<uniquenessConstraint>
|
||||||
<constraint value="id"/>
|
<constraint value="id"/>
|
||||||
|
@ -102,6 +101,7 @@
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="displayName" attributeType="String"/>
|
<attribute name="displayName" attributeType="String"/>
|
||||||
<attribute name="domain" attributeType="String"/>
|
<attribute name="domain" attributeType="String"/>
|
||||||
|
<attribute name="emojisData" optional="YES" attributeType="Binary"/>
|
||||||
<attribute name="followersCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="followersCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="header" attributeType="String"/>
|
<attribute name="header" attributeType="String"/>
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="votersCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="votersCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="votesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="votesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<relationship name="options" toMany="YES" deletionRule="Nullify" destinationEntity="PollOption" inverseName="poll" inverseEntity="PollOption"/>
|
<relationship name="options" toMany="YES" deletionRule="Cascade" destinationEntity="PollOption" inverseName="poll" inverseEntity="PollOption"/>
|
||||||
<relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="poll" inverseEntity="Status"/>
|
<relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="poll" inverseEntity="Status"/>
|
||||||
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePolls" inverseEntity="MastodonUser"/>
|
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePolls" inverseEntity="MastodonUser"/>
|
||||||
</entity>
|
</entity>
|
||||||
|
@ -197,6 +197,7 @@
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="domain" attributeType="String"/>
|
<attribute name="domain" attributeType="String"/>
|
||||||
|
<attribute name="emojisData" optional="YES" attributeType="Binary"/>
|
||||||
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="id" attributeType="String"/>
|
<attribute name="id" attributeType="String"/>
|
||||||
<attribute name="identifier" attributeType="String"/>
|
<attribute name="identifier" attributeType="String"/>
|
||||||
|
@ -216,16 +217,16 @@
|
||||||
<relationship name="application" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Application" inverseName="status" inverseEntity="Application"/>
|
<relationship name="application" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Application" inverseName="status" inverseEntity="Application"/>
|
||||||
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="statuses" inverseEntity="MastodonUser"/>
|
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="statuses" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="bookmarkedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
<relationship name="bookmarkedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="emojis" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Emoji" inverseName="status" inverseEntity="Emoji"/>
|
|
||||||
<relationship name="favouritedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
<relationship name="favouritedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="homeTimelineIndexes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="HomeTimelineIndex" inverseName="status" inverseEntity="HomeTimelineIndex"/>
|
<relationship name="homeTimelineIndexes" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="HomeTimelineIndex" inverseName="status" inverseEntity="HomeTimelineIndex"/>
|
||||||
|
<relationship name="inNotifications" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MastodonNotification" inverseName="status" inverseEntity="MastodonNotification"/>
|
||||||
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="status" inverseEntity="Attachment"/>
|
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="status" inverseEntity="Attachment"/>
|
||||||
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Mention" inverseName="status" inverseEntity="Mention"/>
|
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Mention" inverseName="status" inverseEntity="Mention"/>
|
||||||
<relationship name="mutedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
<relationship name="mutedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedStatus" inverseEntity="MastodonUser"/>
|
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedStatus" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="status" inverseEntity="Poll"/>
|
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="status" inverseEntity="Poll"/>
|
||||||
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>
|
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>
|
||||||
<relationship name="reblogFrom" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
<relationship name="reblogFrom" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
||||||
<relationship name="rebloggedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
<relationship name="rebloggedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
||||||
<relationship name="replyFrom" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="replyTo" inverseEntity="Status"/>
|
<relationship name="replyFrom" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="replyTo" inverseEntity="Status"/>
|
||||||
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
||||||
|
@ -267,19 +268,19 @@
|
||||||
<element name="Application" positionX="0" positionY="0" width="128" height="104"/>
|
<element name="Application" positionX="0" positionY="0" width="128" height="104"/>
|
||||||
<element name="Attachment" positionX="0" positionY="0" width="128" height="254"/>
|
<element name="Attachment" positionX="0" positionY="0" width="128" height="254"/>
|
||||||
<element name="DomainBlock" positionX="45" positionY="162" width="128" height="89"/>
|
<element name="DomainBlock" positionX="45" positionY="162" width="128" height="89"/>
|
||||||
<element name="Emoji" positionX="0" positionY="0" width="128" height="149"/>
|
<element name="Emoji" positionX="0" positionY="0" width="128" height="134"/>
|
||||||
<element name="History" positionX="0" positionY="0" width="128" height="119"/>
|
<element name="History" positionX="0" positionY="0" width="128" height="119"/>
|
||||||
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
|
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
|
||||||
<element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/>
|
<element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/>
|
||||||
<element name="MastodonNotification" positionX="9" positionY="162" width="128" height="164"/>
|
<element name="MastodonNotification" positionX="9" positionY="162" width="128" height="164"/>
|
||||||
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="659"/>
|
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="689"/>
|
||||||
<element name="Mention" positionX="0" positionY="0" width="128" height="149"/>
|
<element name="Mention" positionX="0" positionY="0" width="128" height="149"/>
|
||||||
<element name="Poll" positionX="0" positionY="0" width="128" height="194"/>
|
<element name="Poll" positionX="0" positionY="0" width="128" height="194"/>
|
||||||
<element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
|
<element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
|
||||||
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
|
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
|
||||||
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="104"/>
|
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="104"/>
|
||||||
<element name="Setting" positionX="72" positionY="162" width="128" height="119"/>
|
<element name="Setting" positionX="72" positionY="162" width="128" height="119"/>
|
||||||
<element name="Status" positionX="0" positionY="0" width="128" height="584"/>
|
<element name="Status" positionX="0" positionY="0" width="128" height="599"/>
|
||||||
<element name="Subscription" positionX="81" positionY="171" width="128" height="179"/>
|
<element name="Subscription" positionX="81" positionY="171" width="128" height="179"/>
|
||||||
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="14"/>
|
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="14"/>
|
||||||
<element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
|
<element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
|
||||||
|
|
|
@ -25,6 +25,9 @@ final public class MastodonUser: NSManagedObject {
|
||||||
@NSManaged public private(set) var headerStatic: String?
|
@NSManaged public private(set) var headerStatic: String?
|
||||||
@NSManaged public private(set) var note: String?
|
@NSManaged public private(set) var note: String?
|
||||||
@NSManaged public private(set) var url: String?
|
@NSManaged public private(set) var url: String?
|
||||||
|
|
||||||
|
@NSManaged public private(set) var emojisData: Data?
|
||||||
|
|
||||||
@NSManaged public private(set) var statusesCount: NSNumber
|
@NSManaged public private(set) var statusesCount: NSNumber
|
||||||
@NSManaged public private(set) var followingCount: NSNumber
|
@NSManaged public private(set) var followingCount: NSNumber
|
||||||
@NSManaged public private(set) var followersCount: NSNumber
|
@NSManaged public private(set) var followersCount: NSNumber
|
||||||
|
@ -88,6 +91,8 @@ extension MastodonUser {
|
||||||
user.headerStatic = property.headerStatic
|
user.headerStatic = property.headerStatic
|
||||||
user.note = property.note
|
user.note = property.note
|
||||||
user.url = property.url
|
user.url = property.url
|
||||||
|
user.emojisData = property.emojisData
|
||||||
|
|
||||||
user.statusesCount = NSNumber(value: property.statusesCount)
|
user.statusesCount = NSNumber(value: property.statusesCount)
|
||||||
user.followingCount = NSNumber(value: property.followingCount)
|
user.followingCount = NSNumber(value: property.followingCount)
|
||||||
user.followersCount = NSNumber(value: property.followersCount)
|
user.followersCount = NSNumber(value: property.followersCount)
|
||||||
|
@ -151,6 +156,11 @@ extension MastodonUser {
|
||||||
self.url = url
|
self.url = url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public func update(emojisData: Data?) {
|
||||||
|
if self.emojisData != emojisData {
|
||||||
|
self.emojisData = emojisData
|
||||||
|
}
|
||||||
|
}
|
||||||
public func update(statusesCount: Int) {
|
public func update(statusesCount: Int) {
|
||||||
if self.statusesCount.intValue != statusesCount {
|
if self.statusesCount.intValue != statusesCount {
|
||||||
self.statusesCount = NSNumber(value: statusesCount)
|
self.statusesCount = NSNumber(value: statusesCount)
|
||||||
|
@ -270,6 +280,7 @@ extension MastodonUser {
|
||||||
public let headerStatic: String?
|
public let headerStatic: String?
|
||||||
public let note: String?
|
public let note: String?
|
||||||
public let url: String?
|
public let url: String?
|
||||||
|
public let emojisData: Data?
|
||||||
public let statusesCount: Int
|
public let statusesCount: Int
|
||||||
public let followingCount: Int
|
public let followingCount: Int
|
||||||
public let followersCount: Int
|
public let followersCount: Int
|
||||||
|
@ -292,6 +303,7 @@ extension MastodonUser {
|
||||||
headerStatic: String?,
|
headerStatic: String?,
|
||||||
note: String?,
|
note: String?,
|
||||||
url: String?,
|
url: String?,
|
||||||
|
emojisData: Data?,
|
||||||
statusesCount: Int,
|
statusesCount: Int,
|
||||||
followingCount: Int,
|
followingCount: Int,
|
||||||
followersCount: Int,
|
followersCount: Int,
|
||||||
|
@ -313,6 +325,7 @@ extension MastodonUser {
|
||||||
self.headerStatic = headerStatic
|
self.headerStatic = headerStatic
|
||||||
self.note = note
|
self.note = note
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.emojisData = emojisData
|
||||||
self.statusesCount = statusesCount
|
self.statusesCount = statusesCount
|
||||||
self.followingCount = followingCount
|
self.followingCount = followingCount
|
||||||
self.followersCount = followersCount
|
self.followersCount = followersCount
|
||||||
|
|
|
@ -24,6 +24,8 @@ public final class Status: NSManagedObject {
|
||||||
@NSManaged public private(set) var spoilerText: String?
|
@NSManaged public private(set) var spoilerText: String?
|
||||||
@NSManaged public private(set) var application: Application?
|
@NSManaged public private(set) var application: Application?
|
||||||
|
|
||||||
|
@NSManaged public private(set) var emojisData: Data?
|
||||||
|
|
||||||
// Informational
|
// Informational
|
||||||
@NSManaged public private(set) var reblogsCount: NSNumber
|
@NSManaged public private(set) var reblogsCount: NSNumber
|
||||||
@NSManaged public private(set) var favouritesCount: NSNumber
|
@NSManaged public private(set) var favouritesCount: NSNumber
|
||||||
|
@ -54,12 +56,13 @@ public final class Status: NSManagedObject {
|
||||||
// one-to-many relationship
|
// one-to-many relationship
|
||||||
@NSManaged public private(set) var reblogFrom: Set<Status>?
|
@NSManaged public private(set) var reblogFrom: Set<Status>?
|
||||||
@NSManaged public private(set) var mentions: Set<Mention>?
|
@NSManaged public private(set) var mentions: Set<Mention>?
|
||||||
@NSManaged public private(set) var emojis: Set<Emoji>?
|
|
||||||
@NSManaged public private(set) var tags: Set<Tag>?
|
@NSManaged public private(set) var tags: Set<Tag>?
|
||||||
@NSManaged public private(set) var homeTimelineIndexes: Set<HomeTimelineIndex>?
|
@NSManaged public private(set) var homeTimelineIndexes: Set<HomeTimelineIndex>?
|
||||||
@NSManaged public private(set) var mediaAttachments: Set<Attachment>?
|
@NSManaged public private(set) var mediaAttachments: Set<Attachment>?
|
||||||
@NSManaged public private(set) var replyFrom: Set<Status>?
|
@NSManaged public private(set) var replyFrom: Set<Status>?
|
||||||
|
|
||||||
|
@NSManaged public private(set) var inNotifications: Set<MastodonNotification>?
|
||||||
|
|
||||||
@NSManaged public private(set) var updatedAt: Date
|
@NSManaged public private(set) var updatedAt: Date
|
||||||
@NSManaged public private(set) var deletedAt: Date?
|
@NSManaged public private(set) var deletedAt: Date?
|
||||||
@NSManaged public private(set) var revealedAt: Date?
|
@NSManaged public private(set) var revealedAt: Date?
|
||||||
|
@ -77,7 +80,6 @@ extension Status {
|
||||||
replyTo: Status?,
|
replyTo: Status?,
|
||||||
poll: Poll?,
|
poll: Poll?,
|
||||||
mentions: [Mention]?,
|
mentions: [Mention]?,
|
||||||
emojis: [Emoji]?,
|
|
||||||
tags: [Tag]?,
|
tags: [Tag]?,
|
||||||
mediaAttachments: [Attachment]?,
|
mediaAttachments: [Attachment]?,
|
||||||
favouritedBy: MastodonUser?,
|
favouritedBy: MastodonUser?,
|
||||||
|
@ -100,6 +102,8 @@ extension Status {
|
||||||
status.sensitive = property.sensitive
|
status.sensitive = property.sensitive
|
||||||
status.spoilerText = property.spoilerText
|
status.spoilerText = property.spoilerText
|
||||||
status.application = application
|
status.application = application
|
||||||
|
|
||||||
|
status.emojisData = property.emojisData
|
||||||
|
|
||||||
status.reblogsCount = property.reblogsCount
|
status.reblogsCount = property.reblogsCount
|
||||||
status.favouritesCount = property.favouritesCount
|
status.favouritesCount = property.favouritesCount
|
||||||
|
@ -121,9 +125,6 @@ extension Status {
|
||||||
if let mentions = mentions {
|
if let mentions = mentions {
|
||||||
status.mutableSetValue(forKey: #keyPath(Status.mentions)).addObjects(from: mentions)
|
status.mutableSetValue(forKey: #keyPath(Status.mentions)).addObjects(from: mentions)
|
||||||
}
|
}
|
||||||
if let emojis = emojis {
|
|
||||||
status.mutableSetValue(forKey: #keyPath(Status.emojis)).addObjects(from: emojis)
|
|
||||||
}
|
|
||||||
if let tags = tags {
|
if let tags = tags {
|
||||||
status.mutableSetValue(forKey: #keyPath(Status.tags)).addObjects(from: tags)
|
status.mutableSetValue(forKey: #keyPath(Status.tags)).addObjects(from: tags)
|
||||||
}
|
}
|
||||||
|
@ -148,6 +149,12 @@ extension Status {
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func update(emojisData: Data?) {
|
||||||
|
if self.emojisData != emojisData {
|
||||||
|
self.emojisData = emojisData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func update(reblogsCount: NSNumber) {
|
public func update(reblogsCount: NSNumber) {
|
||||||
if self.reblogsCount.intValue != reblogsCount.intValue {
|
if self.reblogsCount.intValue != reblogsCount.intValue {
|
||||||
self.reblogsCount = reblogsCount
|
self.reblogsCount = reblogsCount
|
||||||
|
@ -248,6 +255,8 @@ extension Status {
|
||||||
public let sensitive: Bool
|
public let sensitive: Bool
|
||||||
public let spoilerText: String?
|
public let spoilerText: String?
|
||||||
|
|
||||||
|
public let emojisData: Data?
|
||||||
|
|
||||||
public let reblogsCount: NSNumber
|
public let reblogsCount: NSNumber
|
||||||
public let favouritesCount: NSNumber
|
public let favouritesCount: NSNumber
|
||||||
public let repliesCount: NSNumber?
|
public let repliesCount: NSNumber?
|
||||||
|
@ -269,6 +278,7 @@ extension Status {
|
||||||
visibility: String?,
|
visibility: String?,
|
||||||
sensitive: Bool,
|
sensitive: Bool,
|
||||||
spoilerText: String?,
|
spoilerText: String?,
|
||||||
|
emojisData: Data?,
|
||||||
reblogsCount: NSNumber,
|
reblogsCount: NSNumber,
|
||||||
favouritesCount: NSNumber,
|
favouritesCount: NSNumber,
|
||||||
repliesCount: NSNumber?,
|
repliesCount: NSNumber?,
|
||||||
|
@ -288,6 +298,7 @@ extension Status {
|
||||||
self.visibility = visibility
|
self.visibility = visibility
|
||||||
self.sensitive = sensitive
|
self.sensitive = sensitive
|
||||||
self.spoilerText = spoilerText
|
self.spoilerText = spoilerText
|
||||||
|
self.emojisData = emojisData
|
||||||
self.reblogsCount = reblogsCount
|
self.reblogsCount = reblogsCount
|
||||||
self.favouritesCount = favouritesCount
|
self.favouritesCount = favouritesCount
|
||||||
self.repliesCount = repliesCount
|
self.repliesCount = repliesCount
|
||||||
|
|
|
@ -29,12 +29,16 @@
|
||||||
"confirm": "Sign Out"
|
"confirm": "Sign Out"
|
||||||
},
|
},
|
||||||
"block_domain": {
|
"block_domain": {
|
||||||
"message": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
"title": "Are you really, really sure you want to block the entire %s? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||||
"block_entire_domain": "Block entire domain"
|
"block_entire_domain": "Block entire domain"
|
||||||
},
|
},
|
||||||
"save_photo_failure": {
|
"save_photo_failure": {
|
||||||
"title": "Save Photo Failure",
|
"title": "Save Photo Failure",
|
||||||
"message": "Please enable photo libaray access permission to save photo."
|
"message": "Please enable photo libaray access permission to save photo."
|
||||||
|
},
|
||||||
|
"delete_post": {
|
||||||
|
"title": "Are you sure you want to delete this post?",
|
||||||
|
"delete": "Delete"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
|
@ -67,7 +71,8 @@
|
||||||
"report_user": "Report %s",
|
"report_user": "Report %s",
|
||||||
"block_domain": "Block %s",
|
"block_domain": "Block %s",
|
||||||
"unblock_domain": "Unblock %s",
|
"unblock_domain": "Unblock %s",
|
||||||
"settings": "Settings"
|
"settings": "Settings",
|
||||||
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"user_reblogged": "%s reblogged",
|
"user_reblogged": "%s reblogged",
|
||||||
|
|
|
@ -400,6 +400,7 @@
|
||||||
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; };
|
||||||
DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */; };
|
DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */; };
|
||||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
||||||
|
DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; };
|
||||||
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
|
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
|
||||||
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; };
|
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; };
|
||||||
DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; };
|
DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */; };
|
||||||
|
@ -935,7 +936,6 @@
|
||||||
DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = "<group>"; };
|
DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = "<group>"; };
|
||||||
DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerResultLoader.swift; sourceTree = "<group>"; };
|
DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerResultLoader.swift; sourceTree = "<group>"; };
|
||||||
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = "<group>"; };
|
DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = "<group>"; };
|
||||||
DB9A488326034BD7008B817C /* APIService+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status.swift"; sourceTree = "<group>"; };
|
|
||||||
DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+PublishState.swift"; sourceTree = "<group>"; };
|
DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+PublishState.swift"; sourceTree = "<group>"; };
|
||||||
DB9A488F26035963008B817C /* APIService+Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Media.swift"; sourceTree = "<group>"; };
|
DB9A488F26035963008B817C /* APIService+Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Media.swift"; sourceTree = "<group>"; };
|
||||||
DB9A48952603685D008B817C /* MastodonAttachmentService+UploadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAttachmentService+UploadState.swift"; sourceTree = "<group>"; };
|
DB9A48952603685D008B817C /* MastodonAttachmentService+UploadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAttachmentService+UploadState.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -962,6 +962,7 @@
|
||||||
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = "<group>"; };
|
DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = "<group>"; };
|
||||||
DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = "<group>"; };
|
DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = "<group>"; };
|
||||||
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
|
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
DBAFB7342645463500371D5F /* Emojis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emojis.swift; sourceTree = "<group>"; };
|
||||||
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSegmentedViewController.swift; sourceTree = "<group>"; };
|
||||||
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
|
DBB525202611EBD6002F1F29 /* ProfilePagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewController.swift; sourceTree = "<group>"; };
|
||||||
DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = "<group>"; };
|
DBB5252F2611EBF3002F1F29 /* ProfilePagingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePagingViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1594,6 +1595,7 @@
|
||||||
DB6D9F6E2635807F008423CD /* Setting.swift */,
|
DB6D9F6E2635807F008423CD /* Setting.swift */,
|
||||||
DB6D9F4826353FD6008423CD /* Subscription.swift */,
|
DB6D9F4826353FD6008423CD /* Subscription.swift */,
|
||||||
DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */,
|
DB6D9F4F2635761F008423CD /* SubscriptionAlerts.swift */,
|
||||||
|
DBAFB7342645463500371D5F /* Emojis.swift */,
|
||||||
);
|
);
|
||||||
path = CoreDataStack;
|
path = CoreDataStack;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1735,7 +1737,6 @@
|
||||||
DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */,
|
DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */,
|
||||||
DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */,
|
DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */,
|
||||||
DB49A62A25FF36C700B98345 /* APIService+CustomEmoji.swift */,
|
DB49A62A25FF36C700B98345 /* APIService+CustomEmoji.swift */,
|
||||||
DB9A488326034BD7008B817C /* APIService+Status.swift */,
|
|
||||||
2D61254C262547C200299647 /* APIService+Notification.swift */,
|
2D61254C262547C200299647 /* APIService+Notification.swift */,
|
||||||
DB9A488F26035963008B817C /* APIService+Media.swift */,
|
DB9A488F26035963008B817C /* APIService+Media.swift */,
|
||||||
2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */,
|
2D34D9D026148D9E0081BFC0 /* APIService+Recommend.swift */,
|
||||||
|
@ -3199,6 +3200,7 @@
|
||||||
DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */,
|
DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */,
|
||||||
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */,
|
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */,
|
||||||
DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */,
|
DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */,
|
||||||
|
DBAFB7352645463500371D5F /* Emojis.swift in Sources */,
|
||||||
DBCC3B89261454BA0045B23D /* CGImage.swift in Sources */,
|
DBCC3B89261454BA0045B23D /* CGImage.swift in Sources */,
|
||||||
DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */,
|
DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */,
|
||||||
DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToStatusContentCollectionViewCell.swift in Sources */,
|
DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToStatusContentCollectionViewCell.swift in Sources */,
|
||||||
|
@ -3913,8 +3915,8 @@
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift";
|
repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = exactVersion;
|
||||||
minimumVersion = 4.0.0;
|
version = 5.0.1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = {
|
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = {
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
"repositoryURL": "https://github.com/TwidereProject/ActiveLabel.swift",
|
"repositoryURL": "https://github.com/TwidereProject/ActiveLabel.swift",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "d6cf96e0ca4f2269021bcf8f11381ab57897f84a",
|
"revision": "40e104063d825d1125ef4b8eeb6460eba8a57483",
|
||||||
"version": "4.0.0"
|
"version": "5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
|
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "bbc4bc4def7eb05a7ba8e1219f80ee9be327334e",
|
"revision": "15d199e84677303a7004ed2c5ecaa1a90f3863f8",
|
||||||
"version": "6.2.1"
|
"version": "6.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -68,7 +68,8 @@ extension ComposeStatusSection {
|
||||||
}()
|
}()
|
||||||
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
|
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
|
||||||
// set text
|
// set text
|
||||||
cell.statusView.activeTextLabel.configure(content: status.content)
|
//status.emoji
|
||||||
|
cell.statusView.activeTextLabel.configure(content: status.content, emojiDict: [:])
|
||||||
// set date
|
// set date
|
||||||
cell.statusView.dateLabel.text = status.createdAt.shortTimeAgoSinceNow
|
cell.statusView.dateLabel.text = status.createdAt.shortTimeAgoSinceNow
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,21 @@ extension StatusSection {
|
||||||
requestUserID: String,
|
requestUserID: String,
|
||||||
statusItemAttribute: Item.StatusAttribute
|
statusItemAttribute: Item.StatusAttribute
|
||||||
) {
|
) {
|
||||||
|
// safely cancel the listenser when deleted
|
||||||
|
ManagedObjectObserver.observe(object: status.reblog ?? status)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { _ in
|
||||||
|
// do nothing
|
||||||
|
} receiveValue: { [weak cell] change in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
guard let changeType = change.changeType else { return }
|
||||||
|
if case .delete = changeType {
|
||||||
|
cell.disposeBag.removeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
|
|
||||||
// set header
|
// set header
|
||||||
StatusSection.configureHeader(cell: cell, status: status)
|
StatusSection.configureHeader(cell: cell, status: status)
|
||||||
ManagedObjectObserver.observe(object: status)
|
ManagedObjectObserver.observe(object: status)
|
||||||
|
@ -158,10 +173,11 @@ extension StatusSection {
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
// set name username
|
// set name username
|
||||||
cell.statusView.nameLabel.text = {
|
let nameText: String = {
|
||||||
let author = (status.reblog ?? status).author
|
let author = (status.reblog ?? status).author
|
||||||
return author.displayName.isEmpty ? author.username : author.displayName
|
return author.displayName.isEmpty ? author.username : author.displayName
|
||||||
}()
|
}()
|
||||||
|
cell.statusView.nameLabel.configure(content: nameText, emojiDict: (status.reblog ?? status).author.emojiDict)
|
||||||
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
|
cell.statusView.usernameLabel.text = "@" + (status.reblog ?? status).author.acct
|
||||||
// set avatar
|
// set avatar
|
||||||
if let reblog = status.reblog {
|
if let reblog = status.reblog {
|
||||||
|
@ -176,7 +192,10 @@ extension StatusSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set text
|
// set text
|
||||||
cell.statusView.activeTextLabel.configure(content: (status.reblog ?? status).content)
|
cell.statusView.activeTextLabel.configure(
|
||||||
|
content: (status.reblog ?? status).content,
|
||||||
|
emojiDict: (status.reblog ?? status).emojiDict
|
||||||
|
)
|
||||||
|
|
||||||
// prepare media attachments
|
// prepare media attachments
|
||||||
let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
|
let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
|
||||||
|
@ -569,15 +588,16 @@ extension StatusSection {
|
||||||
if status.reblog != nil {
|
if status.reblog != nil {
|
||||||
cell.statusView.headerContainerView.isHidden = false
|
cell.statusView.headerContainerView.isHidden = false
|
||||||
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage)
|
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage)
|
||||||
cell.statusView.headerInfoLabel.text = {
|
let headerText: String = {
|
||||||
let author = status.author
|
let author = status.author
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||||
return L10n.Common.Controls.Status.userReblogged(name)
|
return L10n.Common.Controls.Status.userReblogged(name)
|
||||||
}()
|
}()
|
||||||
|
cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.author.emojiDict)
|
||||||
} else if status.inReplyToID != nil {
|
} else if status.inReplyToID != nil {
|
||||||
cell.statusView.headerContainerView.isHidden = false
|
cell.statusView.headerContainerView.isHidden = false
|
||||||
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage)
|
cell.statusView.headerIconLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage)
|
||||||
cell.statusView.headerInfoLabel.text = {
|
let headerText: String = {
|
||||||
guard let replyTo = status.replyTo else {
|
guard let replyTo = status.replyTo else {
|
||||||
return L10n.Common.Controls.Status.userRepliedTo("-")
|
return L10n.Common.Controls.Status.userRepliedTo("-")
|
||||||
}
|
}
|
||||||
|
@ -585,6 +605,7 @@ extension StatusSection {
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||||
return L10n.Common.Controls.Status.userRepliedTo(name)
|
return L10n.Common.Controls.Status.userRepliedTo(name)
|
||||||
}()
|
}()
|
||||||
|
cell.statusView.headerInfoLabel.configure(content: headerText, emojiDict: status.replyTo?.author.emojiDict ?? [:])
|
||||||
} else {
|
} else {
|
||||||
cell.statusView.headerContainerView.isHidden = true
|
cell.statusView.headerContainerView.isHidden = true
|
||||||
}
|
}
|
||||||
|
@ -781,7 +802,6 @@ extension StatusSection {
|
||||||
}
|
}
|
||||||
let author = status.authorForUserProvider
|
let author = status.authorForUserProvider
|
||||||
let isMyself = authenticationBox.userID == author.id
|
let isMyself = authenticationBox.userID == author.id
|
||||||
let canReport = !isMyself
|
|
||||||
let isInSameDomain = authenticationBox.domain == author.domainFromAcct
|
let isInSameDomain = authenticationBox.domain == author.domainFromAcct
|
||||||
let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
let isMuting = (author.mutingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
||||||
let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
let isBlocking = (author.blockingBy ?? Set()).map(\.id).contains(authenticationBox.userID)
|
||||||
|
|
|
@ -14,6 +14,8 @@ extension ActiveLabel {
|
||||||
|
|
||||||
enum Style {
|
enum Style {
|
||||||
case `default`
|
case `default`
|
||||||
|
case statusHeader
|
||||||
|
case statusName
|
||||||
case profileField
|
case profileField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +27,7 @@ extension ActiveLabel {
|
||||||
mentionColor = Asset.Colors.Label.highlight.color
|
mentionColor = Asset.Colors.Label.highlight.color
|
||||||
hashtagColor = Asset.Colors.Label.highlight.color
|
hashtagColor = Asset.Colors.Label.highlight.color
|
||||||
URLColor = Asset.Colors.Label.highlight.color
|
URLColor = Asset.Colors.Label.highlight.color
|
||||||
|
emojiPlaceholderColor = .systemFill
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||||
#endif
|
#endif
|
||||||
|
@ -33,6 +36,14 @@ extension ActiveLabel {
|
||||||
case .default:
|
case .default:
|
||||||
font = .preferredFont(forTextStyle: .body)
|
font = .preferredFont(forTextStyle: .body)
|
||||||
textColor = Asset.Colors.Label.primary.color
|
textColor = Asset.Colors.Label.primary.color
|
||||||
|
case .statusHeader:
|
||||||
|
font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium), maximumPointSize: 17)
|
||||||
|
textColor = Asset.Colors.Label.secondary.color
|
||||||
|
numberOfLines = 1
|
||||||
|
case .statusName:
|
||||||
|
font = .systemFont(ofSize: 17, weight: .semibold)
|
||||||
|
textColor = Asset.Colors.Label.primary.color
|
||||||
|
numberOfLines = 1
|
||||||
case .profileField:
|
case .profileField:
|
||||||
font = .preferredFont(forTextStyle: .body)
|
font = .preferredFont(forTextStyle: .body)
|
||||||
textColor = Asset.Colors.Label.primary.color
|
textColor = Asset.Colors.Label.primary.color
|
||||||
|
@ -44,9 +55,10 @@ extension ActiveLabel {
|
||||||
|
|
||||||
extension ActiveLabel {
|
extension ActiveLabel {
|
||||||
/// status content
|
/// status content
|
||||||
func configure(content: String) {
|
func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) {
|
||||||
activeEntities.removeAll()
|
activeEntities.removeAll()
|
||||||
if let parseResult = try? MastodonStatusContent.parse(status: content) {
|
|
||||||
|
if let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) {
|
||||||
text = parseResult.trimmed
|
text = parseResult.trimmed
|
||||||
activeEntities = parseResult.activeEntities
|
activeEntities = parseResult.activeEntities
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,8 +67,8 @@ extension ActiveLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// account note
|
/// account note
|
||||||
func configure(note: String) {
|
func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) {
|
||||||
configure(content: note)
|
configure(content: note, emojiDict: emojiDict)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// Emojis.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-5-7.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
protocol EmojiContinaer {
|
||||||
|
var emojisData: Data? { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EmojiContinaer {
|
||||||
|
|
||||||
|
static func encode(emojis: [Mastodon.Entity.Emoji]) -> Data? {
|
||||||
|
return try? JSONEncoder().encode(emojis)
|
||||||
|
}
|
||||||
|
|
||||||
|
var emojis: [Mastodon.Entity.Emoji]? {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
return emojisData.flatMap { try? decoder.decode([Mastodon.Entity.Emoji].self, from: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var emojiDict: MastodonStatusContent.EmojiDict {
|
||||||
|
var dict = MastodonStatusContent.EmojiDict()
|
||||||
|
for emoji in emojis ?? [] {
|
||||||
|
guard let url = URL(string: emoji.url) else { continue }
|
||||||
|
dict[emoji.shortcode] = url
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ extension MastodonUser.Property {
|
||||||
headerStatic: entity.headerStatic,
|
headerStatic: entity.headerStatic,
|
||||||
note: entity.note,
|
note: entity.note,
|
||||||
url: entity.url,
|
url: entity.url,
|
||||||
|
emojisData: entity.emojis.flatMap { Status.encode(emojis: $0) },
|
||||||
statusesCount: entity.statusesCount,
|
statusesCount: entity.statusesCount,
|
||||||
followingCount: entity.followingCount,
|
followingCount: entity.followingCount,
|
||||||
followersCount: entity.followersCount,
|
followersCount: entity.followersCount,
|
||||||
|
@ -98,3 +99,5 @@ extension MastodonUser {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MastodonUser: EmojiContinaer { }
|
||||||
|
|
|
@ -16,10 +16,11 @@ extension Status.Property {
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
uri: entity.uri,
|
uri: entity.uri,
|
||||||
createdAt: entity.createdAt,
|
createdAt: entity.createdAt,
|
||||||
content: entity.content,
|
content: entity.content!,
|
||||||
visibility: entity.visibility?.rawValue,
|
visibility: entity.visibility?.rawValue,
|
||||||
sensitive: entity.sensitive ?? false,
|
sensitive: entity.sensitive ?? false,
|
||||||
spoilerText: entity.spoilerText,
|
spoilerText: entity.spoilerText,
|
||||||
|
emojisData: entity.emojis.flatMap { Status.encode(emojis: $0) },
|
||||||
reblogsCount: NSNumber(value: entity.reblogsCount),
|
reblogsCount: NSNumber(value: entity.reblogsCount),
|
||||||
favouritesCount: NSNumber(value: entity.favouritesCount),
|
favouritesCount: NSNumber(value: entity.favouritesCount),
|
||||||
repliesCount: entity.repliesCount.flatMap { NSNumber(value: $0) },
|
repliesCount: entity.repliesCount.flatMap { NSNumber(value: $0) },
|
||||||
|
@ -86,3 +87,5 @@ extension Status {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Status: EmojiContinaer { }
|
||||||
|
|
|
@ -17,8 +17,8 @@ internal enum L10n {
|
||||||
/// Block entire domain
|
/// Block entire domain
|
||||||
internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain")
|
internal static let blockEntireDomain = L10n.tr("Localizable", "Common.Alerts.BlockDomain.BlockEntireDomain")
|
||||||
/// Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.
|
/// Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.
|
||||||
internal static func message(_ p1: Any) -> String {
|
internal static func title(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Message", String(describing: p1))
|
return L10n.tr("Localizable", "Common.Alerts.BlockDomain.Title", String(describing: p1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Common {
|
internal enum Common {
|
||||||
|
@ -27,6 +27,12 @@ internal enum L10n {
|
||||||
/// Please try again later.
|
/// Please try again later.
|
||||||
internal static let pleaseTryAgainLater = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgainLater")
|
internal static let pleaseTryAgainLater = L10n.tr("Localizable", "Common.Alerts.Common.PleaseTryAgainLater")
|
||||||
}
|
}
|
||||||
|
internal enum DeletePost {
|
||||||
|
/// Delete
|
||||||
|
internal static let delete = L10n.tr("Localizable", "Common.Alerts.DeletePost.Delete")
|
||||||
|
/// Are you sure you want to delete this post?
|
||||||
|
internal static let title = L10n.tr("Localizable", "Common.Alerts.DeletePost.Title")
|
||||||
|
}
|
||||||
internal enum DiscardPostContent {
|
internal enum DiscardPostContent {
|
||||||
/// Confirm discard composed post content.
|
/// Confirm discard composed post content.
|
||||||
internal static let message = L10n.tr("Localizable", "Common.Alerts.DiscardPostContent.Message")
|
internal static let message = L10n.tr("Localizable", "Common.Alerts.DiscardPostContent.Message")
|
||||||
|
@ -84,6 +90,8 @@ internal enum L10n {
|
||||||
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
|
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
|
||||||
/// Continue
|
/// Continue
|
||||||
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
|
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
|
||||||
|
/// Delete
|
||||||
|
internal static let delete = L10n.tr("Localizable", "Common.Controls.Actions.Delete")
|
||||||
/// Discard
|
/// Discard
|
||||||
internal static let discard = L10n.tr("Localizable", "Common.Controls.Actions.Discard")
|
internal static let discard = L10n.tr("Localizable", "Common.Controls.Actions.Discard")
|
||||||
/// Done
|
/// Done
|
||||||
|
|
|
@ -11,9 +11,21 @@ import ActiveLabel
|
||||||
|
|
||||||
enum MastodonStatusContent {
|
enum MastodonStatusContent {
|
||||||
|
|
||||||
static func parse(status: String) throws -> MastodonStatusContent.ParseResult {
|
typealias EmojiShortcode = String
|
||||||
let status = status.replacingOccurrences(of: "<br/>", with: "\n")
|
typealias EmojiDict = [EmojiShortcode: URL]
|
||||||
let rootNode = try Node.parse(document: status)
|
|
||||||
|
static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult {
|
||||||
|
let document: String = {
|
||||||
|
var content = content
|
||||||
|
content = content.replacingOccurrences(of: "<br/>", with: "\n")
|
||||||
|
for (shortcode, url) in emojiDict {
|
||||||
|
let emojiNode = "<span class=\"emoji\" href=\"\(url.absoluteString)\">\(shortcode)</span>"
|
||||||
|
let pattern = ":\(shortcode):"
|
||||||
|
content = content.replacingOccurrences(of: pattern, with: emojiNode)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}()
|
||||||
|
let rootNode = try Node.parse(document: document)
|
||||||
let text = String(rootNode.text)
|
let text = String(rootNode.text)
|
||||||
|
|
||||||
var activeEntities: [ActiveEntity] = []
|
var activeEntities: [ActiveEntity] = []
|
||||||
|
@ -25,7 +37,7 @@ enum MastodonStatusContent {
|
||||||
case .url:
|
case .url:
|
||||||
guard let href = entity.href else { continue }
|
guard let href = entity.href else { continue }
|
||||||
let text = String(entity.text)
|
let text = String(entity.text)
|
||||||
activeEntities.append(ActiveEntity(range: range, type: .url(text, trimmed: entity.hrefEllipsis ?? text, url: href)))
|
activeEntities.append(ActiveEntity(range: range, type: .url(text, trimmed: entity.hrefEllipsis ?? text, url: href, userInfo: nil)))
|
||||||
case .hashtag:
|
case .hashtag:
|
||||||
var userInfo: [AnyHashable: Any] = [:]
|
var userInfo: [AnyHashable: Any] = [:]
|
||||||
entity.href.flatMap { href in
|
entity.href.flatMap { href in
|
||||||
|
@ -40,27 +52,44 @@ enum MastodonStatusContent {
|
||||||
}
|
}
|
||||||
let mention = String(entity.text).deletingPrefix("@")
|
let mention = String(entity.text).deletingPrefix("@")
|
||||||
activeEntities.append(ActiveEntity(range: range, type: .mention(mention, userInfo: userInfo)))
|
activeEntities.append(ActiveEntity(range: range, type: .mention(mention, userInfo: userInfo)))
|
||||||
default:
|
case .emoji:
|
||||||
|
var userInfo: [AnyHashable: Any] = [:]
|
||||||
|
guard let href = entity.href else { continue }
|
||||||
|
userInfo["href"] = href
|
||||||
|
let emoji = String(entity.text)
|
||||||
|
activeEntities.append(ActiveEntity(range: range, type: .emoji(emoji, url: href, userInfo: userInfo)))
|
||||||
|
case .none:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var trimmed = text
|
var trimmed = text
|
||||||
for activeEntity in activeEntities {
|
for activeEntity in activeEntities {
|
||||||
guard case .url = activeEntity.type else { continue }
|
|
||||||
MastodonStatusContent.trimEntity(status: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities)
|
MastodonStatusContent.trimEntity(status: &trimmed, activeEntity: activeEntity, activeEntities: activeEntities)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseResult(
|
return ParseResult(
|
||||||
document: status,
|
document: document,
|
||||||
original: text,
|
original: text,
|
||||||
trimmed: trimmed,
|
trimmed: trimmed,
|
||||||
activeEntities: validate(text: trimmed, activeEntities: activeEntities) ? activeEntities : []
|
activeEntities: activeEntities
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func trimEntity(status: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) {
|
static func trimEntity(status: inout String, activeEntity: ActiveEntity, activeEntities: [ActiveEntity]) {
|
||||||
guard case let .url(text, trimmed, _, _) = activeEntity.type else { return }
|
let text: String
|
||||||
|
let trimmed: String
|
||||||
|
switch activeEntity.type {
|
||||||
|
case .url(let _text, let _trimmed, _, _):
|
||||||
|
text = _text
|
||||||
|
trimmed = _trimmed
|
||||||
|
case .emoji(let _text, _, _):
|
||||||
|
text = _text
|
||||||
|
trimmed = " "
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
guard let index = activeEntities.firstIndex(where: { $0.range == activeEntity.range }) else { return }
|
guard let index = activeEntities.firstIndex(where: { $0.range == activeEntity.range }) else { return }
|
||||||
guard let range = Range(activeEntity.range, in: status) else { return }
|
guard let range = Range(activeEntity.range, in: status) else { return }
|
||||||
status.replaceSubrange(range, with: trimmed)
|
status.replaceSubrange(range, with: trimmed)
|
||||||
|
@ -73,19 +102,6 @@ enum MastodonStatusContent {
|
||||||
moveActiveEntity.range.location += offset
|
moveActiveEntity.range.location += offset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func validate(text: String, activeEntities: [ActiveEntity]) -> Bool {
|
|
||||||
for activeEntity in activeEntities {
|
|
||||||
let count = text.utf16.count
|
|
||||||
let endIndex = activeEntity.range.location + activeEntity.range.length
|
|
||||||
guard endIndex <= count else {
|
|
||||||
assertionFailure("Please file issue")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +122,7 @@ extension MastodonStatusContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extension MastodonStatusContent {
|
extension MastodonStatusContent {
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
|
@ -154,6 +171,10 @@ extension MastodonStatusContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _classNames.contains("emoji") {
|
||||||
|
return .emoji
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
self.level = level
|
self.level = level
|
||||||
|
@ -257,6 +278,7 @@ extension MastodonStatusContent.Node {
|
||||||
case url
|
case url
|
||||||
case mention
|
case mention
|
||||||
case hashtag
|
case hashtag
|
||||||
|
case emoji
|
||||||
}
|
}
|
||||||
|
|
||||||
static func entities(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] {
|
static func entities(in node: MastodonStatusContent.Node) -> [MastodonStatusContent.Node] {
|
||||||
|
|
|
@ -252,7 +252,7 @@ extension UserProviderFacade {
|
||||||
} else {
|
} else {
|
||||||
let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
|
let blockDomainAction = UIAction(title: L10n.Common.Controls.Actions.blockDomain(mastodonUser.domainFromAcct), image: UIImage(systemName: "nosign"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak provider] _ in
|
||||||
guard let provider = provider else { return }
|
guard let provider = provider else { return }
|
||||||
let alertController = UIAlertController(title: "", message: L10n.Common.Alerts.BlockDomain.message(mastodonUser.domainFromAcct), preferredStyle: .alert)
|
let alertController = UIAlertController(title: L10n.Common.Alerts.BlockDomain.title(mastodonUser.domainFromAcct), message: nil, preferredStyle: .alert)
|
||||||
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
|
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
|
||||||
}
|
}
|
||||||
alertController.addAction(cancelAction)
|
alertController.addAction(cancelAction)
|
||||||
|
@ -300,6 +300,35 @@ extension UserProviderFacade {
|
||||||
children.append(shareAction)
|
children.append(shareAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let status = shareStatus, isMyself {
|
||||||
|
let deleteAction = UIAction(title: L10n.Common.Controls.Actions.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off) {
|
||||||
|
[weak provider] _ in
|
||||||
|
guard let provider = provider else { return }
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: L10n.Common.Alerts.DeletePost.title, message: nil, preferredStyle: .alert)
|
||||||
|
let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .default) { _ in
|
||||||
|
}
|
||||||
|
alertController.addAction(cancelAction)
|
||||||
|
let deleteAction = UIAlertAction(title: L10n.Common.Alerts.DeletePost.delete, style: .destructive) { _ in
|
||||||
|
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||||
|
provider.context.apiService.deleteStatus(domain: activeMastodonAuthenticationBox.domain,
|
||||||
|
statusID: status.id,
|
||||||
|
authorizationBox: activeMastodonAuthenticationBox
|
||||||
|
)
|
||||||
|
.sink { _ in
|
||||||
|
// do nothing
|
||||||
|
} receiveValue: { _ in
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
.store(in: &provider.context.disposeBag)
|
||||||
|
}
|
||||||
|
alertController.addAction(deleteAction)
|
||||||
|
provider.present(alertController, animated: true, completion: nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
children.append(deleteAction)
|
||||||
|
}
|
||||||
|
|
||||||
return UIMenu(title: "", options: [], children: children)
|
return UIMenu(title: "", options: [], children: children)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
"Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain";
|
"Common.Alerts.BlockDomain.BlockEntireDomain" = "Block entire domain";
|
||||||
"Common.Alerts.BlockDomain.Message" = "Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.";
|
"Common.Alerts.BlockDomain.Title" = "Are you really, really sure you want to block the entire %@? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.";
|
||||||
"Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
|
"Common.Alerts.Common.PleaseTryAgain" = "Please try again.";
|
||||||
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
|
"Common.Alerts.Common.PleaseTryAgainLater" = "Please try again later.";
|
||||||
|
"Common.Alerts.DeletePost.Delete" = "Delete";
|
||||||
|
"Common.Alerts.DeletePost.Title" = "Are you sure you want to delete this post?";
|
||||||
"Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content.";
|
"Common.Alerts.DiscardPostContent.Message" = "Confirm discard composed post content.";
|
||||||
"Common.Alerts.DiscardPostContent.Title" = "Discard Publish";
|
"Common.Alerts.DiscardPostContent.Title" = "Discard Publish";
|
||||||
"Common.Alerts.PublishPostFailure.Message" = "Failed to publish the post.
|
"Common.Alerts.PublishPostFailure.Message" = "Failed to publish the post.
|
||||||
|
@ -22,6 +24,7 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Actions.Cancel" = "Cancel";
|
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||||
"Common.Controls.Actions.Confirm" = "Confirm";
|
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||||
"Common.Controls.Actions.Continue" = "Continue";
|
"Common.Controls.Actions.Continue" = "Continue";
|
||||||
|
"Common.Controls.Actions.Delete" = "Delete";
|
||||||
"Common.Controls.Actions.Discard" = "Discard";
|
"Common.Controls.Actions.Discard" = "Discard";
|
||||||
"Common.Controls.Actions.Done" = "Done";
|
"Common.Controls.Actions.Done" = "Done";
|
||||||
"Common.Controls.Actions.Edit" = "Edit";
|
"Common.Controls.Actions.Edit" = "Edit";
|
||||||
|
|
|
@ -175,7 +175,7 @@ extension ProfileHeaderViewController {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isEditing, note, editingNote in
|
.sink { [weak self] isEditing, note, editingNote in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.profileHeaderView.bioActiveLabel.configure(note: note ?? "")
|
self.profileHeaderView.bioActiveLabel.configure(note: note ?? "", emojiDict: [:]) // FIXME: custom emoji
|
||||||
self.profileHeaderView.bioTextEditorView.text = editingNote ?? ""
|
self.profileHeaderView.bioTextEditorView.text = editingNote ?? ""
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
|
@ -20,7 +20,7 @@ final class ProfileFieldView: UIView {
|
||||||
|
|
||||||
let valueActiveLabel: ActiveLabel = {
|
let valueActiveLabel: ActiveLabel = {
|
||||||
let label = ActiveLabel(style: .profileField)
|
let label = ActiveLabel(style: .profileField)
|
||||||
label.configure(content: "value")
|
label.configure(content: "value", emojiDict: [:])
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ class SettingsViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
let label = ActiveLabel(style: .default)
|
let label = ActiveLabel(style: .default)
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
label.configure(content: "Mastodon is open source software. You can contribute or report issues on GitHub at <a href=\"https://github.com/tootsuite/mastodon\">tootsuite/mastodon</a> (v3.3.0).")
|
label.configure(content: "Mastodon is open source software. You can contribute or report issues on GitHub at <a href=\"https://github.com/tootsuite/mastodon\">tootsuite/mastodon</a> (v3.3.0).", emojiDict: [:])
|
||||||
label.delegate = self
|
label.delegate = self
|
||||||
|
|
||||||
view.addArrangedSubview(label)
|
view.addArrangedSubview(label)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import AVKit
|
||||||
import ActiveLabel
|
import ActiveLabel
|
||||||
import AlamofireImage
|
import AlamofireImage
|
||||||
|
|
||||||
protocol StatusViewDelegate: class {
|
protocol StatusViewDelegate: AnyObject {
|
||||||
func statusView(_ statusView: StatusView, headerInfoLabelDidPressed label: UILabel)
|
func statusView(_ statusView: StatusView, headerInfoLabelDidPressed label: UILabel)
|
||||||
func statusView(_ statusView: StatusView, avatarButtonDidPressed button: UIButton)
|
func statusView(_ statusView: StatusView, avatarButtonDidPressed button: UIButton)
|
||||||
func statusView(_ statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton)
|
func statusView(_ statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton)
|
||||||
|
@ -69,10 +69,8 @@ final class StatusView: UIView {
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let headerInfoLabel: UILabel = {
|
let headerInfoLabel: ActiveLabel = {
|
||||||
let label = UILabel()
|
let label = ActiveLabel(style: .statusHeader)
|
||||||
label.font = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: .systemFont(ofSize: 13, weight: .medium), maximumPointSize: 17)
|
|
||||||
label.textColor = Asset.Colors.Label.secondary.color
|
|
||||||
label.text = "Bob reblogged"
|
label.text = "Bob reblogged"
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
@ -87,10 +85,8 @@ final class StatusView: UIView {
|
||||||
}()
|
}()
|
||||||
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
|
let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton()
|
||||||
|
|
||||||
let nameLabel: UILabel = {
|
let nameLabel: ActiveLabel = {
|
||||||
let label = UILabel()
|
let label = ActiveLabel(style: .statusName)
|
||||||
label.font = .systemFont(ofSize: 17, weight: .semibold)
|
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
|
||||||
label.text = "Alice"
|
label.text = "Alice"
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -88,4 +88,50 @@ extension APIService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteStatus(
|
||||||
|
domain: String,
|
||||||
|
statusID: Mastodon.Entity.Status.ID,
|
||||||
|
authorizationBox: AuthenticationService.MastodonAuthenticationBox
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
|
let authorization = authorizationBox.userAuthorization
|
||||||
|
let query = Mastodon.API.Statuses.DeleteStatusQuery(id: statusID)
|
||||||
|
return Mastodon.API.Statuses.deleteStatus(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
query: query,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> in
|
||||||
|
return self.backgroundManagedObjectContext.performChanges{
|
||||||
|
// fetch old Status
|
||||||
|
let oldStatus: Status? = {
|
||||||
|
let request = Status.sortedFetchRequest
|
||||||
|
request.predicate = Status.predicate(domain: domain, id: response.value.id)
|
||||||
|
request.fetchLimit = 1
|
||||||
|
request.returnsObjectsAsFaults = false
|
||||||
|
do {
|
||||||
|
return try self.backgroundManagedObjectContext.fetch(request).first
|
||||||
|
} catch {
|
||||||
|
assertionFailure(error.localizedDescription)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if let status = oldStatus {
|
||||||
|
self.backgroundManagedObjectContext.delete(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setFailureType(to: Error.self)
|
||||||
|
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Status> in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
return response
|
||||||
|
case .failure(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,9 +89,6 @@ extension APIService.CoreData {
|
||||||
let metions = entity.mentions?.enumerated().compactMap { index, mention -> Mention in
|
let metions = entity.mentions?.enumerated().compactMap { index, mention -> Mention in
|
||||||
Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url), index: index)
|
Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url), index: index)
|
||||||
}
|
}
|
||||||
let emojis = entity.emojis?.compactMap { emoji -> Emoji in
|
|
||||||
Emoji.insert(into: managedObjectContext, property: Emoji.Property(shortcode: emoji.shortcode, url: emoji.url, staticURL: emoji.staticURL, visibleInPicker: emoji.visibleInPicker, category: emoji.category))
|
|
||||||
}
|
|
||||||
let tags = entity.tags?.compactMap { tag -> Tag in
|
let tags = entity.tags?.compactMap { tag -> Tag in
|
||||||
let histories = tag.history?.compactMap { history -> History in
|
let histories = tag.history?.compactMap { history -> History in
|
||||||
History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
|
History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
|
||||||
|
@ -121,7 +118,6 @@ extension APIService.CoreData {
|
||||||
replyTo: replyTo,
|
replyTo: replyTo,
|
||||||
poll: poll,
|
poll: poll,
|
||||||
mentions: metions,
|
mentions: metions,
|
||||||
emojis: emojis,
|
|
||||||
tags: tags,
|
tags: tags,
|
||||||
mediaAttachments: mediaAttachments,
|
mediaAttachments: mediaAttachments,
|
||||||
favouritedBy: (entity.favourited ?? false) ? requestMastodonUser : nil,
|
favouritedBy: (entity.favourited ?? false) ? requestMastodonUser : nil,
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Combine
|
||||||
|
|
||||||
extension Mastodon.API.Statuses {
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
static func viewStatusEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
static func statusEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||||
let pathComponent = "statuses/" + statusID
|
let pathComponent = "statuses/" + statusID
|
||||||
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ extension Mastodon.API.Statuses {
|
||||||
authorization: Mastodon.API.OAuth.Authorization?
|
authorization: Mastodon.API.OAuth.Authorization?
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
let request = Mastodon.API.get(
|
let request = Mastodon.API.get(
|
||||||
url: viewStatusEndpointURL(domain: domain, statusID: statusID),
|
url: statusEndpointURL(domain: domain, statusID: statusID),
|
||||||
query: nil,
|
query: nil,
|
||||||
authorization: authorization
|
authorization: authorization
|
||||||
)
|
)
|
||||||
|
@ -150,6 +150,54 @@ extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
|
/// Delete status
|
||||||
|
///
|
||||||
|
/// Delete one of your own statuses.
|
||||||
|
///
|
||||||
|
/// - Since: 0.0.0
|
||||||
|
/// - Version: 3.3.0
|
||||||
|
/// # Last Update
|
||||||
|
/// 2021/5/7
|
||||||
|
/// # Reference
|
||||||
|
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||||
|
/// - Parameters:
|
||||||
|
/// - session: `URLSession`
|
||||||
|
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||||
|
/// - query: `DeleteStatusQuery`
|
||||||
|
/// - authorization: User token
|
||||||
|
/// - Returns: `AnyPublisher` contains `Status` nested in the response
|
||||||
|
public static func deleteStatus(
|
||||||
|
session: URLSession,
|
||||||
|
domain: String,
|
||||||
|
query: DeleteStatusQuery,
|
||||||
|
authorization: Mastodon.API.OAuth.Authorization?
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
|
let request = Mastodon.API.delete(
|
||||||
|
url: statusEndpointURL(domain: domain, statusID: query.id),
|
||||||
|
query: query,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
return session.dataTaskPublisher(for: request)
|
||||||
|
.tryMap { data, response in
|
||||||
|
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)
|
||||||
|
return Mastodon.Response.Content(value: value, response: response)
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DeleteStatusQuery: Codable, DeleteQuery {
|
||||||
|
public let id: Mastodon.Entity.Status.ID
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: Mastodon.Entity.Status.ID
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Mastodon.API.Statuses {
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
static func statusContextEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
static func statusContextEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||||
|
|
|
@ -26,7 +26,7 @@ extension Mastodon.Entity {
|
||||||
public let uri: String
|
public let uri: String
|
||||||
public let createdAt: Date
|
public let createdAt: Date
|
||||||
public let account: Account
|
public let account: Account
|
||||||
public let content: String
|
public let content: String? // will be optional when delete status
|
||||||
|
|
||||||
public let visibility: Visibility?
|
public let visibility: Visibility?
|
||||||
public let sensitive: Bool?
|
public let sensitive: Bool?
|
||||||
|
|
Loading…
Reference in New Issue