mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-02-02 18:36:44 +01:00
feat: implement image media status cell UI
This commit is contained in:
parent
cee84d95a0
commit
98ebddc438
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D5029f" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D74" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Application" representedClassName=".Application" syncable="YES">
|
||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
@ -7,6 +7,23 @@
|
||||
<attribute name="website" optional="YES" attributeType="String"/>
|
||||
<relationship name="toots" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Toot" inverseName="application" inverseEntity="Toot"/>
|
||||
</entity>
|
||||
<entity name="Attachment" representedClassName=".Attachment" syncable="YES">
|
||||
<attribute name="blurhash" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="descriptionString" optional="YES" attributeType="String"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="index" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="metaData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="previewRemoteURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="previewURL" attributeType="String"/>
|
||||
<attribute name="remoteURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="textURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="typeRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<relationship name="toot" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Toot" inverseName="mediaAttachments" inverseEntity="Toot"/>
|
||||
</entity>
|
||||
<entity name="Emoji" representedClassName=".Emoji" syncable="YES">
|
||||
<attribute name="category" optional="YES" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
@ -110,6 +127,7 @@
|
||||
<relationship name="emojis" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Emoji" inverseName="toot" inverseEntity="Emoji"/>
|
||||
<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="toot" inverseEntity="HomeTimelineIndex"/>
|
||||
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="toot" inverseEntity="Attachment"/>
|
||||
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Mention" inverseName="toot" inverseEntity="Mention"/>
|
||||
<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="pinnedToot" inverseEntity="MastodonUser"/>
|
||||
@ -127,6 +145,7 @@
|
||||
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="284"/>
|
||||
<element name="Mention" positionX="9" positionY="108" width="128" height="134"/>
|
||||
<element name="Tag" positionX="18" positionY="117" width="128" height="119"/>
|
||||
<element name="Toot" positionX="0" positionY="0" width="128" height="509"/>
|
||||
<element name="Toot" positionX="0" positionY="0" width="128" height="524"/>
|
||||
<element name="Attachment" positionX="72" positionY="162" width="128" height="14"/>
|
||||
</elements>
|
||||
</model>
|
126
CoreDataStack/Entity/Attachment.swift
Normal file
126
CoreDataStack/Entity/Attachment.swift
Normal file
@ -0,0 +1,126 @@
|
||||
//
|
||||
// Attachment.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-2-23.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public final class Attachment: NSManagedObject {
|
||||
public typealias ID = String
|
||||
|
||||
@NSManaged public private(set) var id: ID
|
||||
@NSManaged public private(set) var domain: String
|
||||
@NSManaged public private(set) var typeRaw: String
|
||||
@NSManaged public private(set) var url: String
|
||||
@NSManaged public private(set) var previewURL: String
|
||||
|
||||
@NSManaged public private(set) var remoteURL: String?
|
||||
@NSManaged public private(set) var metaData: Data?
|
||||
@NSManaged public private(set) var textURL: String?
|
||||
@NSManaged public private(set) var descriptionString: String?
|
||||
@NSManaged public private(set) var blurhash: String?
|
||||
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
@NSManaged public private(set) var index: NSNumber
|
||||
|
||||
// many-to-one relastionship
|
||||
@NSManaged public private(set) var toot: Toot?
|
||||
|
||||
}
|
||||
|
||||
public extension Attachment {
|
||||
|
||||
override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
createdAt = Date()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> Attachment {
|
||||
let attachment: Attachment = context.insertObject()
|
||||
|
||||
attachment.domain = property.domain
|
||||
attachment.index = property.index
|
||||
|
||||
attachment.id = property.id
|
||||
attachment.typeRaw = property.typeRaw
|
||||
attachment.url = property.url
|
||||
attachment.previewURL = property.previewURL
|
||||
|
||||
attachment.remoteURL = property.remoteURL
|
||||
attachment.metaData = property.metaData
|
||||
attachment.textURL = property.textURL
|
||||
attachment.descriptionString = property.descriptionString
|
||||
attachment.blurhash = property.blurhash
|
||||
|
||||
attachment.updatedAt = property.networkDate
|
||||
|
||||
return attachment
|
||||
}
|
||||
|
||||
func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension Attachment {
|
||||
struct Property {
|
||||
public let domain: String
|
||||
public let index: NSNumber
|
||||
|
||||
public let id: ID
|
||||
public let typeRaw: String
|
||||
public let url: String
|
||||
|
||||
public let previewURL: String
|
||||
public let remoteURL: String?
|
||||
public let metaData: Data?
|
||||
public let textURL: String?
|
||||
public let descriptionString: String?
|
||||
public let blurhash: String?
|
||||
|
||||
public let networkDate: Date
|
||||
|
||||
public init(
|
||||
domain: String,
|
||||
index: Int,
|
||||
id: Attachment.ID,
|
||||
typeRaw: String,
|
||||
url: String,
|
||||
previewURL: String,
|
||||
remoteURL: String?,
|
||||
metaData: Data?,
|
||||
textURL: String?,
|
||||
descriptionString: String?,
|
||||
blurhash: String?,
|
||||
networkDate: Date
|
||||
) {
|
||||
self.domain = domain
|
||||
self.index = NSNumber(value: index)
|
||||
self.id = id
|
||||
self.typeRaw = typeRaw
|
||||
self.url = url
|
||||
self.previewURL = previewURL
|
||||
self.remoteURL = remoteURL
|
||||
self.metaData = metaData
|
||||
self.textURL = textURL
|
||||
self.descriptionString = descriptionString
|
||||
self.blurhash = blurhash
|
||||
self.networkDate = networkDate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Attachment: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Attachment.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
@ -39,6 +39,8 @@ public final class Toot: NSManagedObject {
|
||||
// many-to-one relastionship
|
||||
@NSManaged public private(set) var author: MastodonUser
|
||||
@NSManaged public private(set) var reblog: Toot?
|
||||
|
||||
// many-to-many relastionship
|
||||
@NSManaged public private(set) var favouritedBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var rebloggedBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var mutedBy: Set<MastodonUser>?
|
||||
@ -53,6 +55,7 @@ public final class Toot: NSManagedObject {
|
||||
@NSManaged public private(set) var emojis: Set<Emoji>?
|
||||
@NSManaged public private(set) var tags: Set<Tag>?
|
||||
@NSManaged public private(set) var homeTimelineIndexes: Set<HomeTimelineIndex>?
|
||||
@NSManaged public private(set) var mediaAttachments: Set<Attachment>?
|
||||
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
@NSManaged public private(set) var deletedAt: Date?
|
||||
@ -69,6 +72,7 @@ public extension Toot {
|
||||
mentions: [Mention]?,
|
||||
emojis: [Emoji]?,
|
||||
tags: [Tag]?,
|
||||
mediaAttachments: [Attachment]?,
|
||||
favouritedBy: MastodonUser?,
|
||||
rebloggedBy: MastodonUser?,
|
||||
mutedBy: MastodonUser?,
|
||||
@ -115,6 +119,9 @@ public extension Toot {
|
||||
if let tags = tags {
|
||||
toot.mutableSetValue(forKey: #keyPath(Toot.tags)).addObjects(from: tags)
|
||||
}
|
||||
if let mediaAttachments = mediaAttachments {
|
||||
toot.mutableSetValue(forKey: #keyPath(Toot.mediaAttachments)).addObjects(from: mediaAttachments)
|
||||
}
|
||||
if let favouritedBy = favouritedBy {
|
||||
toot.mutableSetValue(forKey: #keyPath(Toot.favouritedBy)).add(favouritedBy)
|
||||
}
|
||||
|
@ -137,6 +137,10 @@
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BE825E4F5340051B173 /* SearchViewController.swift */; };
|
||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */; };
|
||||
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */; };
|
||||
DB9D6C0E25E4F9780051B173 /* MosaicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C0D25E4F9780051B173 /* MosaicImageView.swift */; };
|
||||
DB9D6C2425E502C60051B173 /* MosaicImageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C2225E502C60051B173 /* MosaicImageViewModel.swift */; };
|
||||
DB9D6C2E25E504AC0051B173 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C2D25E504AC0051B173 /* Attachment.swift */; };
|
||||
DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C3725E508BE0051B173 /* Attachment.swift */; };
|
||||
DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; };
|
||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; };
|
||||
DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */; };
|
||||
@ -334,6 +338,10 @@
|
||||
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
|
||||
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = "<group>"; };
|
||||
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
|
||||
DB9D6C0D25E4F9780051B173 /* MosaicImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MosaicImageView.swift; sourceTree = "<group>"; };
|
||||
DB9D6C2225E502C60051B173 /* MosaicImageViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MosaicImageViewModel.swift; sourceTree = "<group>"; };
|
||||
DB9D6C2D25E504AC0051B173 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
|
||||
DB9D6C3725E508BE0051B173 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
|
||||
DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = "<group>"; };
|
||||
DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewController.swift; sourceTree = "<group>"; };
|
||||
DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -547,6 +555,7 @@
|
||||
2D7631A425C1532200929FB9 /* Share */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB9D6C2025E502C60051B173 /* ViewModel */,
|
||||
2D7631A525C1532D00929FB9 /* View */,
|
||||
);
|
||||
path = Share;
|
||||
@ -557,6 +566,7 @@
|
||||
children = (
|
||||
2D42FF8325C82245004A627A /* Button */,
|
||||
2D42FF7C25C82207004A627A /* ToolBar */,
|
||||
DB9D6C1325E4F97A0051B173 /* Container */,
|
||||
2D152A8A25C295B8009AA50C /* Content */,
|
||||
2D7631A625C1533800929FB9 /* TableviewCell */,
|
||||
);
|
||||
@ -628,6 +638,7 @@
|
||||
children = (
|
||||
DB45FAE225CA7181005A8AC7 /* MastodonUser.swift */,
|
||||
DB084B5625CBC56C00F898ED /* Toot.swift */,
|
||||
DB9D6C3725E508BE0051B173 /* Attachment.swift */,
|
||||
);
|
||||
path = CoreDataStack;
|
||||
sourceTree = "<group>";
|
||||
@ -816,6 +827,7 @@
|
||||
2D927F1325C7EDD9004F19B8 /* Emoji.swift */,
|
||||
DB45FAEC25CA7A9A005A8AC7 /* MastodonAuthentication.swift */,
|
||||
2DA7D05625CA693F00804E11 /* Application.swift */,
|
||||
DB9D6C2D25E504AC0051B173 /* Attachment.swift */,
|
||||
);
|
||||
path = Entity;
|
||||
sourceTree = "<group>";
|
||||
@ -935,6 +947,22 @@
|
||||
path = Profile;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB9D6C1325E4F97A0051B173 /* Container */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB9D6C0D25E4F9780051B173 /* MosaicImageView.swift */,
|
||||
);
|
||||
path = Container;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB9D6C2025E502C60051B173 /* ViewModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB9D6C2225E502C60051B173 /* MosaicImageViewModel.swift */,
|
||||
);
|
||||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DBE0821A25CD382900FD6BBD /* Register */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1319,6 +1347,7 @@
|
||||
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
|
||||
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */,
|
||||
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */,
|
||||
DB9D6C2425E502C60051B173 /* MosaicImageViewModel.swift in Sources */,
|
||||
2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */,
|
||||
DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */,
|
||||
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
|
||||
@ -1355,6 +1384,7 @@
|
||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
||||
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */,
|
||||
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
|
||||
DB9D6C0E25E4F9780051B173 /* MosaicImageView.swift in Sources */,
|
||||
DB98338725C945ED00AD9700 /* Strings.swift in Sources */,
|
||||
DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */,
|
||||
DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */,
|
||||
@ -1369,6 +1399,7 @@
|
||||
DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */,
|
||||
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */,
|
||||
2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */,
|
||||
DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */,
|
||||
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */,
|
||||
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */,
|
||||
2D76318325C14E8F00929FB9 /* PublicTimelineViewModel+Diffable.swift in Sources */,
|
||||
@ -1412,6 +1443,7 @@
|
||||
2DF75BC725D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift in Sources */,
|
||||
DB89BA1225C1105C008580ED /* CoreDataStack.swift in Sources */,
|
||||
DB89BA1C25C1107F008580ED /* NSManagedObjectContext.swift in Sources */,
|
||||
DB9D6C2E25E504AC0051B173 /* Attachment.swift in Sources */,
|
||||
2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */,
|
||||
DB89BA3725C1145C008580ED /* CoreData.xcdatamodeld in Sources */,
|
||||
DB8AF52525C131D1002E6C99 /* MastodonUser.swift in Sources */,
|
||||
|
@ -34,7 +34,7 @@ extension TimelineSection {
|
||||
// configure cell
|
||||
managedObjectContext.performAndWait {
|
||||
let timelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex
|
||||
TimelineSection.configure(cell: cell, timestampUpdatePublisher: timestampUpdatePublisher, toot: timelineIndex.toot, requestUserID: timelineIndex.userID)
|
||||
TimelineSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: timelineIndex.toot, requestUserID: timelineIndex.userID)
|
||||
}
|
||||
cell.delegate = timelinePostTableViewCellDelegate
|
||||
return cell
|
||||
@ -45,7 +45,7 @@ extension TimelineSection {
|
||||
// configure cell
|
||||
managedObjectContext.performAndWait {
|
||||
let toot = managedObjectContext.object(with: objectID) as! Toot
|
||||
TimelineSection.configure(cell: cell, timestampUpdatePublisher: timestampUpdatePublisher, toot: toot, requestUserID: requestUserID)
|
||||
TimelineSection.configure(cell: cell, readableLayoutFrame: tableView.readableContentGuide.layoutFrame, timestampUpdatePublisher: timestampUpdatePublisher, toot: toot, requestUserID: requestUserID)
|
||||
}
|
||||
cell.delegate = timelinePostTableViewCellDelegate
|
||||
return cell
|
||||
@ -69,22 +69,74 @@ extension TimelineSection {
|
||||
|
||||
static func configure(
|
||||
cell: StatusTableViewCell,
|
||||
readableLayoutFrame: CGRect?,
|
||||
timestampUpdatePublisher: AnyPublisher<Date, Never>,
|
||||
toot: Toot,
|
||||
requestUserID: String
|
||||
) {
|
||||
// set header
|
||||
cell.statusView.headerContainerStackView.isHidden = toot.reblog == nil
|
||||
cell.statusView.headerInfoLabel.text = L10n.Common.Controls.Status.userboosted(toot.author.displayName)
|
||||
cell.statusView.headerInfoLabel.text = {
|
||||
let author = toot.author
|
||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||
return L10n.Common.Controls.Status.userboosted(name)
|
||||
}()
|
||||
|
||||
// set name username avatar
|
||||
cell.statusView.nameLabel.text = toot.author.displayName
|
||||
cell.statusView.usernameLabel.text = "@" + toot.author.acct
|
||||
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: toot.author.avatarImageURL()))
|
||||
cell.statusView.nameLabel.text = {
|
||||
let author = (toot.reblog ?? toot).author
|
||||
return author.displayName.isEmpty ? author.username : author.displayName
|
||||
}()
|
||||
cell.statusView.usernameLabel.text = "@" + (toot.reblog ?? toot).author.acct
|
||||
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: (toot.reblog ?? toot).author.avatarImageURL()))
|
||||
|
||||
// set text
|
||||
cell.statusView.activeTextLabel.config(content: (toot.reblog ?? toot).content)
|
||||
|
||||
// prepare media attachments
|
||||
let mediaAttachments = Array((toot.reblog ?? toot).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
|
||||
|
||||
// set image
|
||||
let mosiacImageViewModel = MosaicImageViewModel(mediaAttachments: mediaAttachments)
|
||||
let imageViewMaxSize: CGSize = {
|
||||
let maxWidth: CGFloat = {
|
||||
// use timelinePostView width as container width
|
||||
// that width follows readable width and keep constant width after rotate
|
||||
let containerFrame = readableLayoutFrame ?? cell.statusView.frame
|
||||
var containerWidth = containerFrame.width
|
||||
containerWidth -= 10
|
||||
containerWidth -= StatusView.avatarImageSize.width
|
||||
return containerWidth
|
||||
}()
|
||||
let scale: CGFloat = {
|
||||
switch mosiacImageViewModel.metas.count {
|
||||
case 1: return 1.3
|
||||
default: return 0.7
|
||||
}
|
||||
}()
|
||||
return CGSize(width: maxWidth, height: maxWidth * scale)
|
||||
}()
|
||||
if mosiacImageViewModel.metas.count == 1 {
|
||||
let meta = mosiacImageViewModel.metas[0]
|
||||
let imageView = cell.statusView.mosaicImageView.setupImageView(aspectRatio: meta.size, maxSize: imageViewMaxSize)
|
||||
imageView.af.setImage(
|
||||
withURL: meta.url,
|
||||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
imageTransition: .crossDissolve(0.2)
|
||||
)
|
||||
} else {
|
||||
let imageViews = cell.statusView.mosaicImageView.setupImageViews(count: mosiacImageViewModel.metas.count, maxHeight: imageViewMaxSize.height)
|
||||
for (i, imageView) in imageViews.enumerated() {
|
||||
let meta = mosiacImageViewModel.metas[i]
|
||||
imageView.af.setImage(
|
||||
withURL: meta.url,
|
||||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
imageTransition: .crossDissolve(0.2)
|
||||
)
|
||||
}
|
||||
}
|
||||
cell.statusView.mosaicImageView.isHidden = mosiacImageViewModel.metas.isEmpty
|
||||
|
||||
// toolbar
|
||||
let replyCountTitle: String = {
|
||||
let count = (toot.reblog ?? toot).repliesCount?.intValue ?? 0
|
||||
|
@ -41,10 +41,12 @@ extension ActiveLabel {
|
||||
|
||||
extension ActiveLabel {
|
||||
func config(content: String) {
|
||||
if let parseResult = try? TootContent.parse(toot: content) {
|
||||
activeEntities.removeAll()
|
||||
if let parseResult = try? TootContent.parse(toot: content) {
|
||||
text = parseResult.trimmed
|
||||
activeEntities = parseResult.activeEntities
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
Mastodon/Extension/CoreDataStack/Attachment.swift
Normal file
23
Mastodon/Extension/CoreDataStack/Attachment.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Attachment.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-2-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension Attachment {
|
||||
|
||||
var type: Mastodon.Entity.Attachment.AttachmentType {
|
||||
return Mastodon.Entity.Attachment.AttachmentType(rawValue: typeRaw) ?? ._other(typeRaw)
|
||||
}
|
||||
|
||||
var meta: Mastodon.Entity.Attachment.Meta? {
|
||||
let decoder = JSONDecoder()
|
||||
return metaData.flatMap { try? decoder.decode(Mastodon.Entity.Attachment.Meta.self, from: $0) }
|
||||
}
|
||||
|
||||
}
|
12
Mastodon/Resources/Preview Assets.xcassets/bradley-dunn.imageset/Contents.json
vendored
Normal file
12
Mastodon/Resources/Preview Assets.xcassets/bradley-dunn.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bradley-dunn-miqbDWtOG-o-unsplash.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
12
Mastodon/Resources/Preview Assets.xcassets/lucas-ludwig.imageset/Contents.json
vendored
Normal file
12
Mastodon/Resources/Preview Assets.xcassets/lucas-ludwig.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lucas-ludwig-8ARg12PU8nE-unsplash.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 116 KiB |
12
Mastodon/Resources/Preview Assets.xcassets/markus-spiske.imageset/Contents.json
vendored
Normal file
12
Mastodon/Resources/Preview Assets.xcassets/markus-spiske.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "markus-spiske-45R3oFOJt2k-unsplash.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 217 KiB |
12
Mastodon/Resources/Preview Assets.xcassets/mrdongok.imageset/Contents.json
vendored
Normal file
12
Mastodon/Resources/Preview Assets.xcassets/mrdongok.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "mrdongok-Z53ognhPjek-unsplash.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Mastodon/Resources/Preview Assets.xcassets/mrdongok.imageset/mrdongok-Z53ognhPjek-unsplash.jpg
vendored
Normal file
BIN
Mastodon/Resources/Preview Assets.xcassets/mrdongok.imageset/mrdongok-Z53ognhPjek-unsplash.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
284
Mastodon/Scene/Share/View/Container/MosaicImageView.swift
Normal file
284
Mastodon/Scene/Share/View/Container/MosaicImageView.swift
Normal file
@ -0,0 +1,284 @@
|
||||
//
|
||||
// MosaicImageView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-2-23.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import func AVFoundation.AVMakeRect
|
||||
import UIKit
|
||||
|
||||
protocol MosaicImageViewPresentable: class {
|
||||
var mosaicImageView: MosaicImageView { get }
|
||||
}
|
||||
|
||||
protocol MosaicImageViewDelegate: class {
|
||||
func mosaicImageView(_ mosaicImageView: MosaicImageView, didTapImageView imageView: UIImageView, atIndex index: Int)
|
||||
}
|
||||
|
||||
final class MosaicImageView: UIView {
|
||||
|
||||
static let cornerRadius: CGFloat = 4
|
||||
|
||||
weak var delegate: MosaicImageViewDelegate?
|
||||
|
||||
let container = UIStackView()
|
||||
var imageViews = [UIImageView]() {
|
||||
didSet {
|
||||
imageViews.forEach { imageView in
|
||||
imageView.isUserInteractionEnabled = true
|
||||
let tapGesture = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||
tapGesture.addTarget(self, action: #selector(MosaicImageView.photoTapGestureRecognizerHandler(_:)))
|
||||
imageView.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var containerHeightLayoutConstraint: NSLayoutConstraint!
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MosaicImageView {
|
||||
|
||||
private func _init() {
|
||||
container.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(container)
|
||||
containerHeightLayoutConstraint = container.heightAnchor.constraint(equalToConstant: 162).priority(.required - 1)
|
||||
NSLayoutConstraint.activate([
|
||||
container.topAnchor.constraint(equalTo: topAnchor),
|
||||
container.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
||||
bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
||||
containerHeightLayoutConstraint
|
||||
])
|
||||
|
||||
container.axis = .horizontal
|
||||
container.distribution = .fillEqually
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MosaicImageView {
|
||||
|
||||
func reset() {
|
||||
container.arrangedSubviews.forEach { subview in
|
||||
container.removeArrangedSubview(subview)
|
||||
subview.removeFromSuperview()
|
||||
}
|
||||
container.subviews.forEach { subview in
|
||||
subview.removeFromSuperview()
|
||||
}
|
||||
imageViews = []
|
||||
|
||||
container.spacing = 1
|
||||
}
|
||||
|
||||
func setupImageView(aspectRatio: CGSize, maxSize: CGSize) -> UIImageView {
|
||||
reset()
|
||||
|
||||
let contentView = UIView()
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addArrangedSubview(contentView)
|
||||
|
||||
let rect = AVMakeRect(
|
||||
aspectRatio: aspectRatio,
|
||||
insideRect: CGRect(origin: .zero, size: maxSize)
|
||||
)
|
||||
|
||||
let imageView = UIImageView()
|
||||
imageViews.append(imageView)
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = MosaicImageView.cornerRadius
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
imageView.widthAnchor.constraint(equalToConstant: floor(rect.width)).priority(.required - 1),
|
||||
])
|
||||
containerHeightLayoutConstraint.constant = floor(rect.height)
|
||||
containerHeightLayoutConstraint.isActive = true
|
||||
|
||||
return imageView
|
||||
}
|
||||
|
||||
func setupImageViews(count: Int, maxHeight: CGFloat) -> [UIImageView] {
|
||||
reset()
|
||||
guard count > 1 else {
|
||||
return []
|
||||
}
|
||||
|
||||
containerHeightLayoutConstraint.constant = maxHeight
|
||||
containerHeightLayoutConstraint.isActive = true
|
||||
|
||||
let contentLeftStackView = UIStackView()
|
||||
let contentRightStackView = UIStackView()
|
||||
[contentLeftStackView, contentRightStackView].forEach { stackView in
|
||||
stackView.axis = .vertical
|
||||
stackView.distribution = .fillEqually
|
||||
stackView.spacing = 1
|
||||
}
|
||||
container.addArrangedSubview(contentLeftStackView)
|
||||
container.addArrangedSubview(contentRightStackView)
|
||||
|
||||
var imageViews: [UIImageView] = []
|
||||
for _ in 0..<count {
|
||||
imageViews.append(UIImageView())
|
||||
}
|
||||
self.imageViews.append(contentsOf: imageViews)
|
||||
imageViews.forEach { imageView in
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = MosaicImageView.cornerRadius
|
||||
imageView.layer.cornerCurve = .continuous
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
}
|
||||
if count == 2 {
|
||||
contentLeftStackView.addArrangedSubview(imageViews[0])
|
||||
contentRightStackView.addArrangedSubview(imageViews[1])
|
||||
switch UIApplication.shared.userInterfaceLayoutDirection {
|
||||
case .rightToLeft:
|
||||
imageViews[1].layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
imageViews[0].layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
default:
|
||||
imageViews[0].layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
imageViews[1].layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
}
|
||||
|
||||
} else if count == 3 {
|
||||
contentLeftStackView.addArrangedSubview(imageViews[0])
|
||||
contentRightStackView.addArrangedSubview(imageViews[1])
|
||||
contentRightStackView.addArrangedSubview(imageViews[2])
|
||||
switch UIApplication.shared.userInterfaceLayoutDirection {
|
||||
case .rightToLeft:
|
||||
imageViews[0].layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
imageViews[1].layer.maskedCorners = [.layerMinXMinYCorner]
|
||||
imageViews[2].layer.maskedCorners = [.layerMinXMaxYCorner]
|
||||
default:
|
||||
imageViews[0].layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
imageViews[1].layer.maskedCorners = [.layerMaxXMinYCorner]
|
||||
imageViews[2].layer.maskedCorners = [.layerMaxXMaxYCorner]
|
||||
}
|
||||
} else if count == 4 {
|
||||
contentLeftStackView.addArrangedSubview(imageViews[0])
|
||||
contentRightStackView.addArrangedSubview(imageViews[1])
|
||||
contentLeftStackView.addArrangedSubview(imageViews[2])
|
||||
contentRightStackView.addArrangedSubview(imageViews[3])
|
||||
switch UIApplication.shared.userInterfaceLayoutDirection {
|
||||
case .rightToLeft:
|
||||
imageViews[0].layer.maskedCorners = [.layerMaxXMinYCorner]
|
||||
imageViews[1].layer.maskedCorners = [.layerMinXMinYCorner]
|
||||
imageViews[2].layer.maskedCorners = [.layerMaxXMaxYCorner]
|
||||
imageViews[3].layer.maskedCorners = [.layerMinXMaxYCorner]
|
||||
default:
|
||||
imageViews[0].layer.maskedCorners = [.layerMinXMinYCorner]
|
||||
imageViews[1].layer.maskedCorners = [.layerMaxXMinYCorner]
|
||||
imageViews[2].layer.maskedCorners = [.layerMinXMaxYCorner]
|
||||
imageViews[3].layer.maskedCorners = [.layerMaxXMaxYCorner]
|
||||
}
|
||||
}
|
||||
|
||||
return imageViews
|
||||
}
|
||||
}
|
||||
|
||||
extension MosaicImageView {
|
||||
|
||||
@objc private func photoTapGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
||||
guard let imageView = sender.view as? UIImageView else { return }
|
||||
guard let index = imageViews.firstIndex(of: imageView) else { return }
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: tap photo at index: %ld", ((#file as NSString).lastPathComponent), #line, #function, index)
|
||||
delegate?.mosaicImageView(self, didTapImageView: imageView, atIndex: index)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG && canImport(SwiftUI)
|
||||
import SwiftUI
|
||||
|
||||
struct MosaicImageView_Previews: PreviewProvider {
|
||||
|
||||
static var images: [UIImage] {
|
||||
return ["bradley-dunn", "mrdongok", "lucas-ludwig", "markus-spiske"]
|
||||
.map { UIImage(named: $0)! }
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageView()
|
||||
let image = images[3]
|
||||
let imageView = view.setupImageView(
|
||||
aspectRatio: image.size,
|
||||
maxSize: CGSize(width: 375, height: 400)
|
||||
)
|
||||
imageView.image = image
|
||||
return view
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 400))
|
||||
.previewDisplayName("Portrait - one image")
|
||||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageView()
|
||||
let image = images[1]
|
||||
let imageView = view.setupImageView(
|
||||
aspectRatio: image.size,
|
||||
maxSize: CGSize(width: 375, height: 400)
|
||||
)
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = 8
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.image = image
|
||||
return view
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 400))
|
||||
.previewDisplayName("Landscape - one image")
|
||||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageView()
|
||||
let images = self.images.prefix(2)
|
||||
let imageViews = view.setupImageViews(count: images.count, maxHeight: 162)
|
||||
for (i, imageView) in imageViews.enumerated() {
|
||||
imageView.image = images[i]
|
||||
}
|
||||
return view
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 200))
|
||||
.previewDisplayName("two image")
|
||||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageView()
|
||||
let images = self.images.prefix(3)
|
||||
let imageViews = view.setupImageViews(count: images.count, maxHeight: 162)
|
||||
for (i, imageView) in imageViews.enumerated() {
|
||||
imageView.image = images[i]
|
||||
}
|
||||
return view
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 200))
|
||||
.previewDisplayName("three image")
|
||||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageView()
|
||||
let images = self.images.prefix(4)
|
||||
let imageViews = view.setupImageViews(count: images.count, maxHeight: 162)
|
||||
for (i, imageView) in imageViews.enumerated() {
|
||||
imageView.image = images[i]
|
||||
}
|
||||
return view
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 200))
|
||||
.previewDisplayName("four image")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
@ -73,6 +73,8 @@ final class StatusView: UIView {
|
||||
|
||||
let statusContainerStackView = UIStackView()
|
||||
|
||||
let mosaicImageView = MosaicImageView()
|
||||
|
||||
let actionToolbarContainer: ActionToolbarContainer = {
|
||||
let actionToolbarContainer = ActionToolbarContainer()
|
||||
actionToolbarContainer.configure(for: .inline)
|
||||
@ -183,12 +185,14 @@ extension StatusView {
|
||||
statusContainerStackView.spacing = 10
|
||||
statusContainerStackView.addArrangedSubview(activeTextLabel)
|
||||
activeTextLabel.setContentCompressionResistancePriority(.required - 2, for: .vertical)
|
||||
statusContainerStackView.addArrangedSubview(mosaicImageView)
|
||||
|
||||
// action toolbar container
|
||||
containerStackView.addArrangedSubview(actionToolbarContainer)
|
||||
actionToolbarContainer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
|
||||
|
||||
headerContainerStackView.isHidden = true
|
||||
mosaicImageView.isHidden = true
|
||||
}
|
||||
|
||||
}
|
||||
@ -208,7 +212,7 @@ import SwiftUI
|
||||
|
||||
struct StatusView_Previews: PreviewProvider {
|
||||
|
||||
static let avatarFlora = UIImage(named: "tiraya-adam-QfHEWqPelsc-unsplash")
|
||||
static let avatarFlora = UIImage(named: "tiraya-adam")
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
|
36
Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift
Normal file
36
Mastodon/Scene/Share/ViewModel/MosaicImageViewModel.swift
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// MosaicImageViewModel.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-2-23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
|
||||
struct MosaicImageViewModel {
|
||||
|
||||
let metas: [MosaicMeta]
|
||||
|
||||
init(mediaAttachments: [Attachment]) {
|
||||
var metas: [MosaicMeta] = []
|
||||
for element in mediaAttachments where element.type == .image {
|
||||
// Display original on the iPad/Mac
|
||||
let urlString = UIDevice.current.userInterfaceIdiom == .phone ? element.previewURL : element.url
|
||||
guard let meta = element.meta,
|
||||
let width = meta.original?.width,
|
||||
let height = meta.original?.height,
|
||||
let url = URL(string: urlString) else {
|
||||
continue
|
||||
}
|
||||
metas.append(MosaicMeta(url: url, size: CGSize(width: width, height: height)))
|
||||
}
|
||||
self.metas = metas
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct MosaicMeta {
|
||||
let url: URL
|
||||
let size: CGSize
|
||||
}
|
@ -58,11 +58,24 @@ extension APIService.CoreData {
|
||||
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 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))
|
||||
})
|
||||
}
|
||||
return Tag.insert(into: managedObjectContext, property: Tag.Property(name: tag.name, url: tag.url, histories: histories))
|
||||
}
|
||||
let mediaAttachments: [Attachment]? = {
|
||||
let encoder = JSONEncoder()
|
||||
var attachments: [Attachment] = []
|
||||
for (index, attachment) in (entity.mediaAttachments ?? []).enumerated() {
|
||||
let metaData = attachment.meta.flatMap { meta in
|
||||
try? encoder.encode(meta)
|
||||
}
|
||||
let property = Attachment.Property(domain: domain, index: index, id: attachment.id, typeRaw: attachment.type.rawValue, url: attachment.url, previewURL: attachment.previewURL, remoteURL: attachment.remoteURL, metaData: metaData, textURL: attachment.textURL, descriptionString: attachment.description, blurhash: attachment.blurhash, networkDate: networkDate)
|
||||
attachments.append(Attachment.insert(into: managedObjectContext, property: property))
|
||||
}
|
||||
guard !attachments.isEmpty else { return nil }
|
||||
return attachments
|
||||
}()
|
||||
let tootProperty = Toot.Property(entity: entity, domain: domain, networkDate: networkDate)
|
||||
let toot = Toot.insert(
|
||||
into: managedObjectContext,
|
||||
@ -73,6 +86,7 @@ extension APIService.CoreData {
|
||||
mentions: metions,
|
||||
emojis: emojis,
|
||||
tags: tags,
|
||||
mediaAttachments: mediaAttachments,
|
||||
favouritedBy: (entity.favourited ?? false) ? requestMastodonUser : nil,
|
||||
rebloggedBy: (entity.reblogged ?? false) ? requestMastodonUser : nil,
|
||||
mutedBy: (entity.muted ?? false) ? requestMastodonUser : nil,
|
||||
|
@ -47,6 +47,7 @@ extension Mastodon.Entity {
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Attachment {
|
||||
public typealias AttachmentType = Type
|
||||
public enum `Type`: RawRepresentable, Codable {
|
||||
case unknown
|
||||
case image
|
||||
|
@ -14,7 +14,7 @@ extension Mastodon.Entity {
|
||||
/// - Since: 0.1.0
|
||||
/// - Version: 3.3.0
|
||||
/// # Last Update
|
||||
/// 2021/1/28
|
||||
/// 2021/2/23
|
||||
/// # Reference
|
||||
/// [Document](https://docs.joinmastodon.org/entities/status/)
|
||||
public class Status: Codable {
|
||||
@ -31,7 +31,7 @@ extension Mastodon.Entity {
|
||||
public let visibility: Visibility?
|
||||
public let sensitive: Bool?
|
||||
public let spoilerText: String?
|
||||
public let mediaAttachments: [Attachment]
|
||||
public let mediaAttachments: [Attachment]?
|
||||
public let application: Application?
|
||||
|
||||
// Rendering
|
||||
|
Loading…
x
Reference in New Issue
Block a user