feat: adapt the app to async & await. Update timeline UI
This commit is contained in:
parent
b8181b83c9
commit
b76b3a2750
|
@ -17,6 +17,6 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>90</string>
|
||||
<string>91</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
sources:
|
||||
- .
|
||||
templates:
|
||||
- ./Template
|
||||
output:
|
||||
Generated
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>CoreData 2.xcdatamodel</string>
|
||||
<string>CoreData 3.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19206" systemVersion="20G165" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" 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"/>
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21D49" 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"/>
|
||||
<attribute name="vapidKey" optional="YES" attributeType="String"/>
|
||||
<attribute name="website" optional="YES" attributeType="String"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="application" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="DomainBlock" representedClassName=".DomainBlock" syncable="YES">
|
||||
<attribute name="blockedDomain" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="userID"/>
|
||||
<constraint value="domain"/>
|
||||
<constraint value="blockedDomain"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Emoji" representedClassName=".Emoji" syncable="YES">
|
||||
<attribute name="category" optional="YES" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="shortcode" attributeType="String"/>
|
||||
<attribute name="staticURL" attributeType="String"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<attribute name="visibleInPicker" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="Feed" representedClassName=".Feed" syncable="YES">
|
||||
<attribute name="acctRaw" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hasMore" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isLoadingMore" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="kindRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="notification" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Notification" inverseName="feeds" inverseEntity="Notification"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="feeds" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="HomeTimelineIndex" representedClassName=".HomeTimelineIndex" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="hasMore" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Instance" representedClassName=".Instance" syncable="YES">
|
||||
<attribute name="configurationRaw" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="authentications" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="instance" inverseEntity="MastodonAuthentication"/>
|
||||
</entity>
|
||||
<entity name="MastodonAuthentication" representedClassName=".MastodonAuthentication" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="appAccessToken" attributeType="String"/>
|
||||
<attribute name="clientID" attributeType="String"/>
|
||||
<attribute name="clientSecret" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userAccessToken" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="instance" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Instance" inverseName="authentications" inverseEntity="Instance"/>
|
||||
<relationship name="user" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mastodonAuthentication" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="MastodonUser" representedClassName=".MastodonUser" syncable="YES">
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="avatar" attributeType="String"/>
|
||||
<attribute name="avatarStatic" optional="YES" attributeType="String"/>
|
||||
<attribute name="bot" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="displayName" attributeType="String"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="fields" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="followersCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="header" attributeType="String"/>
|
||||
<attribute name="headerStatic" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="statusesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="suspended" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="blocking" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="blockingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="blockingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="blocking" inverseEntity="MastodonUser"/>
|
||||
<relationship name="bookmarked" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="bookmarkedBy" inverseEntity="Status"/>
|
||||
<relationship name="domainBlocking" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="domainBlockingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="domainBlockingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="domainBlocking" inverseEntity="MastodonUser"/>
|
||||
<relationship name="endorsed" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="endorsedBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="endorsedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="endorsed" inverseEntity="MastodonUser"/>
|
||||
<relationship name="favourite" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="favouritedBy" inverseEntity="Status"/>
|
||||
<relationship name="following" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="following" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequested" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followRequestedBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequestedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followRequested" inverseEntity="MastodonUser"/>
|
||||
<relationship name="mastodonAuthentication" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="user" inverseEntity="MastodonAuthentication"/>
|
||||
<relationship name="muted" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="mutedBy" inverseEntity="Status"/>
|
||||
<relationship name="muting" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mutingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="mutingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muting" inverseEntity="MastodonUser"/>
|
||||
<relationship name="notifications" toMany="YES" deletionRule="Nullify" destinationEntity="Notification" inverseName="account" inverseEntity="Notification"/>
|
||||
<relationship name="pinnedStatus" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="pinnedBy" inverseEntity="Status"/>
|
||||
<relationship name="privateNotes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="to" inverseEntity="PrivateNote"/>
|
||||
<relationship name="privateNotesTo" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="from" inverseEntity="PrivateNote"/>
|
||||
<relationship name="reblogged" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="rebloggedBy" inverseEntity="Status"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Nullify" destinationEntity="SearchHistory" inverseName="account" inverseEntity="SearchHistory"/>
|
||||
<relationship name="statuses" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="author" inverseEntity="Status"/>
|
||||
<relationship name="votePollOptions" toMany="YES" deletionRule="Nullify" destinationEntity="PollOption" inverseName="votedBy" inverseEntity="PollOption"/>
|
||||
<relationship name="votePolls" toMany="YES" deletionRule="Nullify" destinationEntity="Poll" inverseName="votedBy" inverseEntity="Poll"/>
|
||||
</entity>
|
||||
<entity name="Notification" representedClassName=".Notification" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="typeRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="notifications" inverseEntity="MastodonUser"/>
|
||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="notification" inverseEntity="Feed"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="notifications" inverseEntity="Status"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Poll" representedClassName=".Poll" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="expired" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="expiresAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="isVoting" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="multiple" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="votersCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="votesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<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="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePolls" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="PollOption" representedClassName=".PollOption" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="index" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isSelected" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="title" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="votesCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="poll" maxCount="1" deletionRule="Nullify" destinationEntity="Poll" inverseName="options" inverseEntity="Poll"/>
|
||||
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePollOptions" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="PrivateNote" representedClassName=".PrivateNote" syncable="YES">
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="from" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotesTo" inverseEntity="MastodonUser"/>
|
||||
<relationship name="to" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotes" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="SearchHistory" representedClassName=".SearchHistory" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String" defaultValueString=""/>
|
||||
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="searchHistories" inverseEntity="MastodonUser"/>
|
||||
<relationship name="hashtag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag" inverseName="searchHistories" inverseEntity="Tag"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="searchHistories" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Setting" representedClassName=".Setting" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="preferredStaticAvatar" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="preferredStaticEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="preferredTrueBlackDarkMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="preferredUsingDefaultBrowser" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<relationship name="subscriptions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Subscription" inverseName="setting" inverseEntity="Subscription"/>
|
||||
</entity>
|
||||
<entity name="Status" representedClassName=".Status" syncable="YES">
|
||||
<attribute name="attachments" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="content" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="emojis" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="inReplyToAccountID" optional="YES" attributeType="String"/>
|
||||
<attribute name="inReplyToID" optional="YES" attributeType="String"/>
|
||||
<attribute name="language" optional="YES" attributeType="String"/>
|
||||
<attribute name="mentions" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="reblogsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="repliesCount" optional="YES" attributeType="Integer 64" usesScalarValueType="NO"/>
|
||||
<attribute name="revealedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
||||
<attribute name="text" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uri" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<attribute name="visibilityRaw" optional="YES" attributeType="String" elementID="visibility"/>
|
||||
<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="bookmarkedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
||||
<relationship name="favouritedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="status" inverseEntity="Feed"/>
|
||||
<relationship name="mutedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||
<relationship name="notifications" toMany="YES" deletionRule="Cascade" destinationEntity="Notification" inverseName="status" inverseEntity="Notification"/>
|
||||
<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="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>
|
||||
<relationship name="reblogFrom" toMany="YES" deletionRule="Cascade" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
||||
<relationship name="rebloggedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
||||
<relationship name="replyFrom" 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="searchHistories" toMany="YES" deletionRule="Cascade" destinationEntity="SearchHistory" inverseName="status" inverseEntity="SearchHistory"/>
|
||||
</entity>
|
||||
<entity name="Subscription" representedClassName=".Subscription" syncable="YES">
|
||||
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="endpoint" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" optional="YES" attributeType="String"/>
|
||||
<attribute name="policyRaw" attributeType="String"/>
|
||||
<attribute name="serverKey" optional="YES" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="userToken" optional="YES" attributeType="String"/>
|
||||
<relationship name="alert" maxCount="1" deletionRule="Cascade" destinationEntity="SubscriptionAlerts" inverseName="subscription" inverseEntity="SubscriptionAlerts"/>
|
||||
<relationship name="setting" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Setting" inverseName="subscriptions" inverseEntity="Setting"/>
|
||||
</entity>
|
||||
<entity name="SubscriptionAlerts" representedClassName=".SubscriptionAlerts" syncable="YES">
|
||||
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="favouriteRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="followRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="followRequestRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mentionRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="pollRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="reblogRaw" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="subscription" maxCount="1" deletionRule="Nullify" destinationEntity="Subscription" inverseName="alert" inverseEntity="Subscription"/>
|
||||
</entity>
|
||||
<entity name="Tag" representedClassName=".Tag" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="histories" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Nullify" destinationEntity="SearchHistory" inverseName="hashtag" inverseEntity="SearchHistory"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Application" positionX="0" positionY="0" width="128" height="104"/>
|
||||
<element name="DomainBlock" positionX="45" positionY="162" width="128" height="89"/>
|
||||
<element name="Emoji" positionX="0" positionY="0" width="128" height="134"/>
|
||||
<element name="Feed" positionX="54" positionY="171" width="128" height="149"/>
|
||||
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
|
||||
<element name="Instance" positionX="45" positionY="162" width="128" height="104"/>
|
||||
<element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="224"/>
|
||||
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="734"/>
|
||||
<element name="Notification" positionX="9" positionY="162" width="128" height="164"/>
|
||||
<element name="Poll" positionX="0" positionY="0" width="128" height="224"/>
|
||||
<element name="PollOption" positionX="0" positionY="0" width="128" height="149"/>
|
||||
<element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
|
||||
<element name="SearchHistory" positionX="0" positionY="0" width="128" height="149"/>
|
||||
<element name="Setting" positionX="72" positionY="162" width="128" height="164"/>
|
||||
<element name="Status" positionX="0" positionY="0" width="128" height="599"/>
|
||||
<element name="Subscription" positionX="81" positionY="171" width="128" height="179"/>
|
||||
<element name="SubscriptionAlerts" positionX="72" positionY="162" width="128" height="14"/>
|
||||
<element name="Tag" positionX="0" positionY="0" width="128" height="149"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -0,0 +1,198 @@
|
|||
//
|
||||
// Feed.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK on 2022-1-11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
final public class Feed: NSManagedObject {
|
||||
|
||||
@NSManaged public private(set) var acctRaw: String
|
||||
// sourcery: autoGenerateProperty
|
||||
public var acct: Acct {
|
||||
get {
|
||||
Acct(rawValue: acctRaw) ?? .none
|
||||
}
|
||||
set {
|
||||
acctRaw = newValue.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@NSManaged public private(set) var kindRaw: String
|
||||
// sourcery: autoGenerateProperty
|
||||
public var kind: Kind {
|
||||
get {
|
||||
Kind(rawValue: kindRaw) ?? .none
|
||||
}
|
||||
set {
|
||||
kindRaw = newValue.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var hasMore: Bool
|
||||
// sourcery: autoUpdatableObject
|
||||
@NSManaged public private(set) var isLoadingMore: Bool
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var status: Status?
|
||||
@NSManaged public private(set) var notification: Notification?
|
||||
|
||||
}
|
||||
|
||||
extension Feed {
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> Feed {
|
||||
let object: Feed = context.insertObject()
|
||||
object.configure(property: property)
|
||||
return object
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Feed: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Feed.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension Feed {
|
||||
|
||||
static func predicate(kind: Kind) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Feed.kindRaw), kind.rawValue)
|
||||
}
|
||||
|
||||
static func predicate(acct: Acct) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Feed.acctRaw), acct.rawValue)
|
||||
}
|
||||
|
||||
public static func predicate(kind: Kind, acct: Acct) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Feed.predicate(kind: kind),
|
||||
Feed.predicate(acct: acct)
|
||||
])
|
||||
}
|
||||
|
||||
public static func nonePredicate() -> NSPredicate {
|
||||
return predicate(kind: .none, acct: .none)
|
||||
}
|
||||
|
||||
public static func hasMorePredicate() -> NSPredicate {
|
||||
return NSPredicate(format: "%K == YES", #keyPath(Feed.hasMore))
|
||||
}
|
||||
|
||||
public static func hasNotificationPredicate() -> NSPredicate {
|
||||
return NSPredicate(format: "%K != nil", #keyPath(Feed.notification))
|
||||
}
|
||||
|
||||
public static func notificationTypePredicate(types: [MastodonNotificationType]) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
hasNotificationPredicate(),
|
||||
NSPredicate(
|
||||
format: "%K.%K IN %@",
|
||||
#keyPath(Feed.notification),
|
||||
#keyPath(Notification.typeRaw),
|
||||
types.map { $0.rawValue }
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension Feed: AutoGenerateProperty {
|
||||
// sourcery:inline:Feed.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let acct: Acct
|
||||
public let kind: Kind
|
||||
public let hasMore: Bool
|
||||
public let createdAt: Date
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(
|
||||
acct: Acct,
|
||||
kind: Kind,
|
||||
hasMore: Bool,
|
||||
createdAt: Date,
|
||||
updatedAt: Date
|
||||
) {
|
||||
self.acct = acct
|
||||
self.kind = kind
|
||||
self.hasMore = hasMore
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.acct = property.acct
|
||||
self.kind = property.kind
|
||||
self.hasMore = property.hasMore
|
||||
self.createdAt = property.createdAt
|
||||
self.updatedAt = property.updatedAt
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(hasMore: property.hasMore)
|
||||
update(createdAt: property.createdAt)
|
||||
update(updatedAt: property.updatedAt)
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension Feed: AutoUpdatableObject {
|
||||
// sourcery:inline:Feed.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(hasMore: Bool) {
|
||||
if self.hasMore != hasMore {
|
||||
self.hasMore = hasMore
|
||||
}
|
||||
}
|
||||
public func update(isLoadingMore: Bool) {
|
||||
if self.isLoadingMore != isLoadingMore {
|
||||
self.isLoadingMore = isLoadingMore
|
||||
}
|
||||
}
|
||||
public func update(createdAt: Date) {
|
||||
if self.createdAt != createdAt {
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
public protocol FeedIndexable {
|
||||
var feeds: Set<Feed> { get }
|
||||
func feed(kind: Feed.Kind, acct: Feed.Acct) -> Feed?
|
||||
}
|
||||
|
||||
extension FeedIndexable {
|
||||
public func feed(kind: Feed.Kind, acct: Feed.Acct) -> Feed? {
|
||||
return feeds.first(where: { feed in
|
||||
feed.kind == kind && feed.acct == acct
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
//
|
||||
// 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 relationship
|
||||
@NSManaged public private(set) var status: Status?
|
||||
|
||||
}
|
||||
|
||||
public extension Attachment {
|
||||
|
||||
override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(Attachment.createdAt))
|
||||
}
|
||||
|
||||
@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)]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,604 @@
|
|||
//
|
||||
// MastodonUser.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/1/27.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
final public class MastodonUser: NSManagedObject {
|
||||
|
||||
public typealias ID = String
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var domain: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var id: ID
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var acct: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var username: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var displayName: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var avatar: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var avatarStatic: String?
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var header: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var headerStatic: String?
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var note: String?
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var url: String?
|
||||
|
||||
@NSManaged public private(set) var emojisData: Data?
|
||||
@NSManaged public private(set) var fieldsData: Data?
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var statusesCount: Int64
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var followingCount: Int64
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var followersCount: Int64
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var locked: Bool
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var bot: Bool
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var suspended: Bool
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var pinnedStatus: Status?
|
||||
@NSManaged public private(set) var mastodonAuthentication: MastodonAuthentication?
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var statuses: Set<Status>
|
||||
@NSManaged public private(set) var notifications: Set<Notification>
|
||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var favourite: Set<Status>
|
||||
@NSManaged public private(set) var reblogged: Set<Status>
|
||||
@NSManaged public private(set) var muted: Set<Status>
|
||||
@NSManaged public private(set) var bookmarked: Set<Status>
|
||||
@NSManaged public private(set) var votePollOptions: Set<PollOption>
|
||||
@NSManaged public private(set) var votePolls: Set<Poll>
|
||||
// relationships
|
||||
@NSManaged public private(set) var following: Set<MastodonUser>
|
||||
@NSManaged public private(set) var followingBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var followRequested: Set<MastodonUser>
|
||||
@NSManaged public private(set) var followRequestedBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var muting: Set<MastodonUser>
|
||||
@NSManaged public private(set) var mutingBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var blocking: Set<MastodonUser>
|
||||
@NSManaged public private(set) var blockingBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var endorsed: Set<MastodonUser>
|
||||
@NSManaged public private(set) var endorsedBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var domainBlocking: Set<MastodonUser>
|
||||
@NSManaged public private(set) var domainBlockingBy: Set<MastodonUser>
|
||||
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@objc public var emojis: [MastodonEmoji] {
|
||||
get {
|
||||
let keyPath = #keyPath(MastodonUser.emojis)
|
||||
willAccessValue(forKey: keyPath)
|
||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
||||
didAccessValue(forKey: keyPath)
|
||||
do {
|
||||
guard let data = _data else { return [] }
|
||||
let emojis = try JSONDecoder().decode([MastodonEmoji].self, from: data)
|
||||
return emojis
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}
|
||||
set {
|
||||
let keyPath = #keyPath(MastodonUser.emojis)
|
||||
let data = try? JSONEncoder().encode(newValue)
|
||||
willChangeValue(forKey: keyPath)
|
||||
setPrimitiveValue(data, forKey: keyPath)
|
||||
didChangeValue(forKey: keyPath)
|
||||
}
|
||||
}
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@objc public var fields: [MastodonField] {
|
||||
get {
|
||||
let keyPath = #keyPath(MastodonUser.fields)
|
||||
willAccessValue(forKey: keyPath)
|
||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
||||
didAccessValue(forKey: keyPath)
|
||||
do {
|
||||
guard let data = _data else { return [] }
|
||||
let fields = try JSONDecoder().decode([MastodonField].self, from: data)
|
||||
return fields
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}
|
||||
set {
|
||||
let keyPath = #keyPath(MastodonUser.fields)
|
||||
let data = try? JSONEncoder().encode(newValue)
|
||||
willChangeValue(forKey: keyPath)
|
||||
setPrimitiveValue(data, forKey: keyPath)
|
||||
didChangeValue(forKey: keyPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> MastodonUser {
|
||||
let object: MastodonUser = context.insertObject()
|
||||
object.configure(property: property)
|
||||
return object
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonUser: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \MastodonUser.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(id: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.id), id)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, id: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonUser.predicate(domain: domain),
|
||||
MastodonUser.predicate(id: id)
|
||||
])
|
||||
}
|
||||
|
||||
static func predicate(ids: [String]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(MastodonUser.id), ids)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, ids: [String]) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonUser.predicate(domain: domain),
|
||||
MastodonUser.predicate(ids: ids)
|
||||
])
|
||||
}
|
||||
|
||||
static func predicate(username: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.username), username)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, username: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonUser.predicate(domain: domain),
|
||||
MastodonUser.predicate(username: username)
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension MastodonUser {
|
||||
|
||||
public func findSearchHistory(
|
||||
domain: String,
|
||||
userID: MastodonUser.ID
|
||||
) -> SearchHistory? {
|
||||
return searchHistories.first { searchHistory in
|
||||
return searchHistory.domain == domain
|
||||
&& searchHistory.userID == userID
|
||||
}
|
||||
}
|
||||
|
||||
public func findSearchHistory(for user: MastodonUser) -> SearchHistory? {
|
||||
return searchHistories.first { searchHistory in
|
||||
return searchHistory.domain == user.domain
|
||||
&& searchHistory.userID == user.id
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension MastodonUser: AutoGenerateProperty {
|
||||
// sourcery:inline:MastodonUser.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let identifier: ID
|
||||
public let domain: String
|
||||
public let id: ID
|
||||
public let acct: String
|
||||
public let username: String
|
||||
public let displayName: String
|
||||
public let avatar: String
|
||||
public let avatarStatic: String?
|
||||
public let header: String
|
||||
public let headerStatic: String?
|
||||
public let note: String?
|
||||
public let url: String?
|
||||
public let statusesCount: Int64
|
||||
public let followingCount: Int64
|
||||
public let followersCount: Int64
|
||||
public let locked: Bool
|
||||
public let bot: Bool
|
||||
public let suspended: Bool
|
||||
public let createdAt: Date
|
||||
public let updatedAt: Date
|
||||
public let emojis: [MastodonEmoji]
|
||||
public let fields: [MastodonField]
|
||||
|
||||
public init(
|
||||
identifier: ID,
|
||||
domain: String,
|
||||
id: ID,
|
||||
acct: String,
|
||||
username: String,
|
||||
displayName: String,
|
||||
avatar: String,
|
||||
avatarStatic: String?,
|
||||
header: String,
|
||||
headerStatic: String?,
|
||||
note: String?,
|
||||
url: String?,
|
||||
statusesCount: Int64,
|
||||
followingCount: Int64,
|
||||
followersCount: Int64,
|
||||
locked: Bool,
|
||||
bot: Bool,
|
||||
suspended: Bool,
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
emojis: [MastodonEmoji],
|
||||
fields: [MastodonField]
|
||||
) {
|
||||
self.identifier = identifier
|
||||
self.domain = domain
|
||||
self.id = id
|
||||
self.acct = acct
|
||||
self.username = username
|
||||
self.displayName = displayName
|
||||
self.avatar = avatar
|
||||
self.avatarStatic = avatarStatic
|
||||
self.header = header
|
||||
self.headerStatic = headerStatic
|
||||
self.note = note
|
||||
self.url = url
|
||||
self.statusesCount = statusesCount
|
||||
self.followingCount = followingCount
|
||||
self.followersCount = followersCount
|
||||
self.locked = locked
|
||||
self.bot = bot
|
||||
self.suspended = suspended
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
self.emojis = emojis
|
||||
self.fields = fields
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.identifier = property.identifier
|
||||
self.domain = property.domain
|
||||
self.id = property.id
|
||||
self.acct = property.acct
|
||||
self.username = property.username
|
||||
self.displayName = property.displayName
|
||||
self.avatar = property.avatar
|
||||
self.avatarStatic = property.avatarStatic
|
||||
self.header = property.header
|
||||
self.headerStatic = property.headerStatic
|
||||
self.note = property.note
|
||||
self.url = property.url
|
||||
self.statusesCount = property.statusesCount
|
||||
self.followingCount = property.followingCount
|
||||
self.followersCount = property.followersCount
|
||||
self.locked = property.locked
|
||||
self.bot = property.bot
|
||||
self.suspended = property.suspended
|
||||
self.createdAt = property.createdAt
|
||||
self.updatedAt = property.updatedAt
|
||||
self.emojis = property.emojis
|
||||
self.fields = property.fields
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(acct: property.acct)
|
||||
update(username: property.username)
|
||||
update(displayName: property.displayName)
|
||||
update(avatar: property.avatar)
|
||||
update(avatarStatic: property.avatarStatic)
|
||||
update(header: property.header)
|
||||
update(headerStatic: property.headerStatic)
|
||||
update(note: property.note)
|
||||
update(url: property.url)
|
||||
update(statusesCount: property.statusesCount)
|
||||
update(followingCount: property.followingCount)
|
||||
update(followersCount: property.followersCount)
|
||||
update(locked: property.locked)
|
||||
update(bot: property.bot)
|
||||
update(suspended: property.suspended)
|
||||
update(createdAt: property.createdAt)
|
||||
update(updatedAt: property.updatedAt)
|
||||
update(emojis: property.emojis)
|
||||
update(fields: property.fields)
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
//extension MastodonUser {
|
||||
// public struct Property {
|
||||
// public let identifier: String
|
||||
// public let domain: String
|
||||
//
|
||||
// public let id: String
|
||||
// public let acct: String
|
||||
// public let username: String
|
||||
// public let displayName: String
|
||||
// public let avatar: String
|
||||
// public let avatarStatic: String?
|
||||
// public let header: String
|
||||
// public let headerStatic: String?
|
||||
// public let note: String?
|
||||
// public let url: String?
|
||||
// public let emojisData: Data?
|
||||
// public let fieldsData: Data?
|
||||
// public let statusesCount: Int
|
||||
// public let followingCount: Int
|
||||
// public let followersCount: Int
|
||||
// public let locked: Bool
|
||||
// public let bot: Bool?
|
||||
// public let suspended: Bool?
|
||||
//
|
||||
// public let createdAt: Date
|
||||
// public let networkDate: Date
|
||||
//
|
||||
// public init(
|
||||
// id: String,
|
||||
// domain: String,
|
||||
// acct: String,
|
||||
// username: String,
|
||||
// displayName: String,
|
||||
// avatar: String,
|
||||
// avatarStatic: String?,
|
||||
// header: String,
|
||||
// headerStatic: String?,
|
||||
// note: String?,
|
||||
// url: String?,
|
||||
// emojisData: Data?,
|
||||
// fieldsData: Data?,
|
||||
// statusesCount: Int,
|
||||
// followingCount: Int,
|
||||
// followersCount: Int,
|
||||
// locked: Bool,
|
||||
// bot: Bool?,
|
||||
// suspended: Bool?,
|
||||
// createdAt: Date,
|
||||
// networkDate: Date
|
||||
// ) {
|
||||
// self.identifier = id + "@" + domain
|
||||
// self.domain = domain
|
||||
// self.id = id
|
||||
// self.acct = acct
|
||||
// self.username = username
|
||||
// self.displayName = displayName
|
||||
// self.avatar = avatar
|
||||
// self.avatarStatic = avatarStatic
|
||||
// self.header = header
|
||||
// self.headerStatic = headerStatic
|
||||
// self.note = note
|
||||
// self.url = url
|
||||
// self.emojisData = emojisData
|
||||
// self.fieldsData = fieldsData
|
||||
// self.statusesCount = statusesCount
|
||||
// self.followingCount = followingCount
|
||||
// self.followersCount = followersCount
|
||||
// self.locked = locked
|
||||
// self.bot = bot
|
||||
// self.suspended = suspended
|
||||
// self.createdAt = createdAt
|
||||
// self.networkDate = networkDate
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension MastodonUser: AutoUpdatableObject {
|
||||
// sourcery:inline:MastodonUser.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(acct: String) {
|
||||
if self.acct != acct {
|
||||
self.acct = acct
|
||||
}
|
||||
}
|
||||
public func update(username: String) {
|
||||
if self.username != username {
|
||||
self.username = username
|
||||
}
|
||||
}
|
||||
public func update(displayName: String) {
|
||||
if self.displayName != displayName {
|
||||
self.displayName = displayName
|
||||
}
|
||||
}
|
||||
public func update(avatar: String) {
|
||||
if self.avatar != avatar {
|
||||
self.avatar = avatar
|
||||
}
|
||||
}
|
||||
public func update(avatarStatic: String?) {
|
||||
if self.avatarStatic != avatarStatic {
|
||||
self.avatarStatic = avatarStatic
|
||||
}
|
||||
}
|
||||
public func update(header: String) {
|
||||
if self.header != header {
|
||||
self.header = header
|
||||
}
|
||||
}
|
||||
public func update(headerStatic: String?) {
|
||||
if self.headerStatic != headerStatic {
|
||||
self.headerStatic = headerStatic
|
||||
}
|
||||
}
|
||||
public func update(note: String?) {
|
||||
if self.note != note {
|
||||
self.note = note
|
||||
}
|
||||
}
|
||||
public func update(url: String?) {
|
||||
if self.url != url {
|
||||
self.url = url
|
||||
}
|
||||
}
|
||||
public func update(statusesCount: Int64) {
|
||||
if self.statusesCount != statusesCount {
|
||||
self.statusesCount = statusesCount
|
||||
}
|
||||
}
|
||||
public func update(followingCount: Int64) {
|
||||
if self.followingCount != followingCount {
|
||||
self.followingCount = followingCount
|
||||
}
|
||||
}
|
||||
public func update(followersCount: Int64) {
|
||||
if self.followersCount != followersCount {
|
||||
self.followersCount = followersCount
|
||||
}
|
||||
}
|
||||
public func update(locked: Bool) {
|
||||
if self.locked != locked {
|
||||
self.locked = locked
|
||||
}
|
||||
}
|
||||
public func update(bot: Bool) {
|
||||
if self.bot != bot {
|
||||
self.bot = bot
|
||||
}
|
||||
}
|
||||
public func update(suspended: Bool) {
|
||||
if self.suspended != suspended {
|
||||
self.suspended = suspended
|
||||
}
|
||||
}
|
||||
public func update(createdAt: Date) {
|
||||
if self.createdAt != createdAt {
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
public func update(emojis: [MastodonEmoji]) {
|
||||
if self.emojis != emojis {
|
||||
self.emojis = emojis
|
||||
}
|
||||
}
|
||||
public func update(fields: [MastodonField]) {
|
||||
if self.fields != fields {
|
||||
self.fields = fields
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
|
||||
public func update(isFollowing: Bool, by mastodonUser: MastodonUser) {
|
||||
if isFollowing {
|
||||
if !self.followingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.followingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isFollowRequested: Bool, by mastodonUser: MastodonUser) {
|
||||
if isFollowRequested {
|
||||
if !self.followRequestedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followRequestedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.followRequestedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followRequestedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isMuting: Bool, by mastodonUser: MastodonUser) {
|
||||
if isMuting {
|
||||
if !self.mutingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.mutingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.mutingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.mutingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isBlocking: Bool, by mastodonUser: MastodonUser) {
|
||||
if isBlocking {
|
||||
if !self.blockingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.blockingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.blockingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.blockingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isEndorsed: Bool, by mastodonUser: MastodonUser) {
|
||||
if isEndorsed {
|
||||
if !self.endorsedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.endorsedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.endorsedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.endorsedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isDomainBlocking: Bool, by mastodonUser: MastodonUser) {
|
||||
if isDomainBlocking {
|
||||
if !self.domainBlockingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.domainBlockingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.domainBlockingBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.domainBlockingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
//
|
||||
// Notification.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class Notification: NSManagedObject {
|
||||
public typealias ID = String
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var id: ID
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var typeRaw: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var domain: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var userID: String
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var createAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// one-to-one relationship
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var account: MastodonUser
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var status: Status?
|
||||
|
||||
// many-to-one relationship
|
||||
@NSManaged public private(set) var feeds: Set<Feed>
|
||||
|
||||
}
|
||||
|
||||
extension Notification: FeedIndexable { }
|
||||
|
||||
extension Notification {
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
relationship: Relationship
|
||||
) -> Notification {
|
||||
let object: Notification = context.insertObject()
|
||||
|
||||
object.configure(property: property)
|
||||
object.configure(relationship: relationship)
|
||||
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Notification.createAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification {
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Notification.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(userID: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Notification.userID), userID)
|
||||
}
|
||||
|
||||
static func predicate(id: ID) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Notification.id), id)
|
||||
}
|
||||
|
||||
static func predicate(typeRaw: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Notification.typeRaw), typeRaw)
|
||||
}
|
||||
|
||||
public static func predicate(
|
||||
domain: String,
|
||||
userID: String,
|
||||
id: ID
|
||||
) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Notification.predicate(domain: domain),
|
||||
Notification.predicate(userID: userID),
|
||||
Notification.predicate(id: id)
|
||||
])
|
||||
}
|
||||
|
||||
public static func predicate(
|
||||
domain: String,
|
||||
userID: String,
|
||||
typeRaw: String? = nil
|
||||
) -> NSPredicate {
|
||||
if let typeRaw = typeRaw {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Notification.predicate(domain: domain),
|
||||
Notification.predicate(typeRaw: typeRaw),
|
||||
Notification.predicate(userID: userID),
|
||||
])
|
||||
} else {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Notification.predicate(domain: domain),
|
||||
Notification.predicate(userID: userID)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
public static func predicate(validTypesRaws types: [String]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(Notification.typeRaw), types)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension Notification: AutoGenerateProperty {
|
||||
// sourcery:inline:Notification.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let id: ID
|
||||
public let typeRaw: String
|
||||
public let domain: String
|
||||
public let userID: String
|
||||
public let createAt: Date
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(
|
||||
id: ID,
|
||||
typeRaw: String,
|
||||
domain: String,
|
||||
userID: String,
|
||||
createAt: Date,
|
||||
updatedAt: Date
|
||||
) {
|
||||
self.id = id
|
||||
self.typeRaw = typeRaw
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
self.createAt = createAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.id = property.id
|
||||
self.typeRaw = property.typeRaw
|
||||
self.domain = property.domain
|
||||
self.userID = property.userID
|
||||
self.createAt = property.createAt
|
||||
self.updatedAt = property.updatedAt
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(updatedAt: property.updatedAt)
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateRelationship
|
||||
extension Notification: AutoGenerateRelationship {
|
||||
// sourcery:inline:Notification.AutoGenerateRelationship
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Relationship {
|
||||
public let account: MastodonUser
|
||||
public let status: Status?
|
||||
|
||||
public init(
|
||||
account: MastodonUser,
|
||||
status: Status?
|
||||
) {
|
||||
self.account = account
|
||||
self.status = status
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(relationship: Relationship) {
|
||||
self.account = relationship.account
|
||||
self.status = relationship.status
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension Notification: AutoUpdatableObject {
|
||||
// sourcery:inline:Notification.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
extension Notification {
|
||||
public func attach(feed: Feed) {
|
||||
mutableSetValue(forKey: #keyPath(Notification.feeds)).add(feed)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,326 @@
|
|||
//
|
||||
// Poll.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class Poll: NSManagedObject {
|
||||
public typealias ID = String
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var domain: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var id: ID
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var expiresAt: Date?
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var expired: Bool
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var multiple: Bool
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var votesCount: Int64
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var votersCount: Int64
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// sourcery: autoUpdatableObject
|
||||
@NSManaged public private(set) var isVoting: Bool
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var status: Status
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var options: Set<PollOption>
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var votedBy: Set<MastodonUser>?
|
||||
}
|
||||
|
||||
extension Poll {
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> Poll {
|
||||
let object: Poll = context.insertObject()
|
||||
|
||||
object.configure(property: property)
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Poll: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Poll.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension Poll {
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Poll.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(id: ID) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Poll.id), id)
|
||||
}
|
||||
|
||||
static func predicate(ids: [ID]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(Poll.id), ids)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, id: ID) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(id: id)
|
||||
])
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, ids: [ID]) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(ids: ids)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
//extension Poll {
|
||||
//
|
||||
// public override func awakeFromInsert() {
|
||||
// super.awakeFromInsert()
|
||||
// setPrimitiveValue(Date(), forKey: #keyPath(Poll.createdAt))
|
||||
// }
|
||||
//
|
||||
// @discardableResult
|
||||
// public static func insert(
|
||||
// into context: NSManagedObjectContext,
|
||||
// property: Property,
|
||||
// votedBy: MastodonUser?,
|
||||
// options: [PollOption]
|
||||
// ) -> Poll {
|
||||
// let poll: Poll = context.insertObject()
|
||||
//
|
||||
// poll.id = property.id
|
||||
// poll.expiresAt = property.expiresAt
|
||||
// poll.expired = property.expired
|
||||
// poll.multiple = property.multiple
|
||||
// poll.votesCount = property.votesCount
|
||||
// poll.votersCount = property.votersCount
|
||||
//
|
||||
//
|
||||
// poll.updatedAt = property.networkDate
|
||||
//
|
||||
// if let votedBy = votedBy {
|
||||
// poll.mutableSetValue(forKey: #keyPath(Poll.votedBy)).add(votedBy)
|
||||
// }
|
||||
// poll.mutableSetValue(forKey: #keyPath(Poll.options)).addObjects(from: options)
|
||||
//
|
||||
// return poll
|
||||
// }
|
||||
//
|
||||
// public func update(expiresAt: Date?) {
|
||||
// if self.expiresAt != expiresAt {
|
||||
// self.expiresAt = expiresAt
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(expired: Bool) {
|
||||
// if self.expired != expired {
|
||||
// self.expired = expired
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(votesCount: Int) {
|
||||
// if self.votesCount.intValue != votesCount {
|
||||
// self.votesCount = NSNumber(value: votesCount)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(votersCount: Int?) {
|
||||
// if self.votersCount?.intValue != votersCount {
|
||||
// self.votersCount = votersCount.flatMap { NSNumber(value: $0) }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(voted: Bool, by: MastodonUser) {
|
||||
// if voted {
|
||||
// if !(votedBy ?? Set()).contains(by) {
|
||||
// mutableSetValue(forKey: #keyPath(Poll.votedBy)).add(by)
|
||||
// }
|
||||
// } else {
|
||||
// if (votedBy ?? Set()).contains(by) {
|
||||
// mutableSetValue(forKey: #keyPath(Poll.votedBy)).remove(by)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func didUpdate(at networkDate: Date) {
|
||||
// self.updatedAt = networkDate
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
//extension Poll {
|
||||
// public struct Property {
|
||||
// public let id: ID
|
||||
// public let expiresAt: Date?
|
||||
// public let expired: Bool
|
||||
// public let multiple: Bool
|
||||
// public let votesCount: NSNumber
|
||||
// public let votersCount: NSNumber?
|
||||
//
|
||||
// public let networkDate: Date
|
||||
//
|
||||
// public init(
|
||||
// id: Poll.ID,
|
||||
// expiresAt: Date?,
|
||||
// expired: Bool,
|
||||
// multiple: Bool,
|
||||
// votesCount: Int,
|
||||
// votersCount: Int?,
|
||||
// networkDate: Date
|
||||
// ) {
|
||||
// self.id = id
|
||||
// self.expiresAt = expiresAt
|
||||
// self.expired = expired
|
||||
// self.multiple = multiple
|
||||
// self.votesCount = NSNumber(value: votesCount)
|
||||
// self.votersCount = votersCount.flatMap { NSNumber(value: $0) }
|
||||
// self.networkDate = networkDate
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension Poll: AutoGenerateProperty {
|
||||
// sourcery:inline:Poll.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let domain: String
|
||||
public let id: ID
|
||||
public let expiresAt: Date?
|
||||
public let expired: Bool
|
||||
public let multiple: Bool
|
||||
public let votesCount: Int64
|
||||
public let votersCount: Int64
|
||||
public let createdAt: Date
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(
|
||||
domain: String,
|
||||
id: ID,
|
||||
expiresAt: Date?,
|
||||
expired: Bool,
|
||||
multiple: Bool,
|
||||
votesCount: Int64,
|
||||
votersCount: Int64,
|
||||
createdAt: Date,
|
||||
updatedAt: Date
|
||||
) {
|
||||
self.domain = domain
|
||||
self.id = id
|
||||
self.expiresAt = expiresAt
|
||||
self.expired = expired
|
||||
self.multiple = multiple
|
||||
self.votesCount = votesCount
|
||||
self.votersCount = votersCount
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.domain = property.domain
|
||||
self.id = property.id
|
||||
self.expiresAt = property.expiresAt
|
||||
self.expired = property.expired
|
||||
self.multiple = property.multiple
|
||||
self.votesCount = property.votesCount
|
||||
self.votersCount = property.votersCount
|
||||
self.createdAt = property.createdAt
|
||||
self.updatedAt = property.updatedAt
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(expiresAt: property.expiresAt)
|
||||
update(expired: property.expired)
|
||||
update(votesCount: property.votesCount)
|
||||
update(votersCount: property.votersCount)
|
||||
update(updatedAt: property.updatedAt)
|
||||
}
|
||||
// sourcery:end
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension Poll: AutoUpdatableObject {
|
||||
// sourcery:inline:Poll.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(expiresAt: Date?) {
|
||||
if self.expiresAt != expiresAt {
|
||||
self.expiresAt = expiresAt
|
||||
}
|
||||
}
|
||||
public func update(expired: Bool) {
|
||||
if self.expired != expired {
|
||||
self.expired = expired
|
||||
}
|
||||
}
|
||||
public func update(votesCount: Int64) {
|
||||
if self.votesCount != votesCount {
|
||||
self.votesCount = votesCount
|
||||
}
|
||||
}
|
||||
public func update(votersCount: Int64) {
|
||||
if self.votersCount != votersCount {
|
||||
self.votersCount = votersCount
|
||||
}
|
||||
}
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
public func update(isVoting: Bool) {
|
||||
if self.isVoting != isVoting {
|
||||
self.isVoting = isVoting
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
|
||||
public func update(voted: Bool, by: MastodonUser) {
|
||||
if voted {
|
||||
if !(votedBy ?? Set()).contains(by) {
|
||||
mutableSetValue(forKey: #keyPath(Poll.votedBy)).add(by)
|
||||
}
|
||||
} else {
|
||||
if (votedBy ?? Set()).contains(by) {
|
||||
mutableSetValue(forKey: #keyPath(Poll.votedBy)).remove(by)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func attach(options: [PollOption]) {
|
||||
for option in options {
|
||||
guard !self.options.contains(option) else { continue }
|
||||
self.mutableSetValue(forKey: #keyPath(Poll.options)).add(option)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
//
|
||||
// PollOption.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class PollOption: NSManagedObject {
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var index: Int64
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var title: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var votesCount: Int64
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// sourcery: autoUpdatableObject
|
||||
@NSManaged public private(set) var isSelected: Bool
|
||||
|
||||
// many-to-one relationship
|
||||
@NSManaged public private(set) var poll: Poll
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var votedBy: Set<MastodonUser>?
|
||||
}
|
||||
|
||||
|
||||
extension PollOption {
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> PollOption {
|
||||
let object: PollOption = context.insertObject()
|
||||
|
||||
object.configure(property: property)
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PollOption: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \PollOption.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
//extension PollOption {
|
||||
//
|
||||
// public override func awakeFromInsert() {
|
||||
// super.awakeFromInsert()
|
||||
// setPrimitiveValue(Date(), forKey: #keyPath(PollOption.createdAt))
|
||||
// }
|
||||
//
|
||||
// @discardableResult
|
||||
// public static func insert(
|
||||
// into context: NSManagedObjectContext,
|
||||
// property: Property,
|
||||
// votedBy: MastodonUser?
|
||||
// ) -> PollOption {
|
||||
// let option: PollOption = context.insertObject()
|
||||
//
|
||||
// option.index = property.index
|
||||
// option.title = property.title
|
||||
// option.votesCount = property.votesCount
|
||||
// option.updatedAt = property.networkDate
|
||||
//
|
||||
// if let votedBy = votedBy {
|
||||
// option.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).add(votedBy)
|
||||
// }
|
||||
//
|
||||
// return option
|
||||
// }
|
||||
//
|
||||
// public func update(votesCount: Int?) {
|
||||
// if self.votesCount?.intValue != votesCount {
|
||||
// self.votesCount = votesCount.flatMap { NSNumber(value: $0) }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func didUpdate(at networkDate: Date) {
|
||||
// self.updatedAt = networkDate
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
//extension PollOption {
|
||||
// public struct Property {
|
||||
// public let index: NSNumber
|
||||
// public let title: String
|
||||
// public let votesCount: NSNumber?
|
||||
//
|
||||
// public let networkDate: Date
|
||||
//
|
||||
// public init(index: Int, title: String, votesCount: Int?, networkDate: Date) {
|
||||
// self.index = NSNumber(value: index)
|
||||
// self.title = title
|
||||
// self.votesCount = votesCount.flatMap { NSNumber(value: $0) }
|
||||
// self.networkDate = networkDate
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension PollOption: AutoGenerateProperty {
|
||||
// sourcery:inline:PollOption.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let index: Int64
|
||||
public let title: String
|
||||
public let votesCount: Int64
|
||||
public let createdAt: Date
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(
|
||||
index: Int64,
|
||||
title: String,
|
||||
votesCount: Int64,
|
||||
createdAt: Date,
|
||||
updatedAt: Date
|
||||
) {
|
||||
self.index = index
|
||||
self.title = title
|
||||
self.votesCount = votesCount
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.index = property.index
|
||||
self.title = property.title
|
||||
self.votesCount = property.votesCount
|
||||
self.createdAt = property.createdAt
|
||||
self.updatedAt = property.updatedAt
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(title: property.title)
|
||||
update(votesCount: property.votesCount)
|
||||
update(updatedAt: property.updatedAt)
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension PollOption: AutoUpdatableObject {
|
||||
// sourcery:inline:PollOption.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(title: String) {
|
||||
if self.title != title {
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
public func update(votesCount: Int64) {
|
||||
if self.votesCount != votesCount {
|
||||
self.votesCount = votesCount
|
||||
}
|
||||
}
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
public func update(isSelected: Bool) {
|
||||
if self.isSelected != isSelected {
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
|
||||
public func update(voted: Bool, by: MastodonUser) {
|
||||
if voted {
|
||||
if !(self.votedBy ?? Set()).contains(by) {
|
||||
self.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).add(by)
|
||||
}
|
||||
} else {
|
||||
if (self.votedBy ?? Set()).contains(by) {
|
||||
self.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).remove(by)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// SearchHistory.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/7.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class SearchHistory: NSManagedObject {
|
||||
public typealias ID = UUID
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var domain: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var userID: MastodonUser.ID
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var createAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// many-to-one relationship
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var account: MastodonUser?
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var hashtag: Tag?
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var status: Status?
|
||||
|
||||
}
|
||||
|
||||
extension SearchHistory {
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
relationship: Relationship
|
||||
) -> SearchHistory {
|
||||
let object: SearchHistory = context.insertObject()
|
||||
|
||||
object.configure(property: property)
|
||||
object.configure(relationship: relationship)
|
||||
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchHistory: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \SearchHistory.updatedAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchHistory {
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(SearchHistory.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(userID: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(SearchHistory.userID), userID)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, userID: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(userID: userID)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension SearchHistory: AutoGenerateProperty {
|
||||
// sourcery:inline:SearchHistory.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let identifier: ID
|
||||
public let domain: String
|
||||
public let userID: MastodonUser.ID
|
||||
public let createAt: Date
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(
|
||||
identifier: ID,
|
||||
domain: String,
|
||||
userID: MastodonUser.ID,
|
||||
createAt: Date,
|
||||
updatedAt: Date
|
||||
) {
|
||||
self.identifier = identifier
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
self.createAt = createAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.identifier = property.identifier
|
||||
self.domain = property.domain
|
||||
self.userID = property.userID
|
||||
self.createAt = property.createAt
|
||||
self.updatedAt = property.updatedAt
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(updatedAt: property.updatedAt)
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateRelationship
|
||||
extension SearchHistory: AutoGenerateRelationship {
|
||||
// sourcery:inline:SearchHistory.AutoGenerateRelationship
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Relationship {
|
||||
public let account: MastodonUser?
|
||||
public let hashtag: Tag?
|
||||
public let status: Status?
|
||||
|
||||
public init(
|
||||
account: MastodonUser?,
|
||||
hashtag: Tag?,
|
||||
status: Status?
|
||||
) {
|
||||
self.account = account
|
||||
self.hashtag = hashtag
|
||||
self.status = status
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(relationship: Relationship) {
|
||||
self.account = relationship.account
|
||||
self.hashtag = relationship.hashtag
|
||||
self.status = relationship.status
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension SearchHistory: AutoUpdatableObject {
|
||||
// sourcery:inline:SearchHistory.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
|
@ -0,0 +1,802 @@
|
|||
//
|
||||
// Status.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/1/27.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public final class Status: NSManagedObject {
|
||||
public typealias ID = String
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var domain: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var id: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var uri: String
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var content: String
|
||||
|
||||
@NSManaged public private(set) var visibilityRaw: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
public var visibility: MastodonVisibility {
|
||||
get {
|
||||
let rawValue = visibilityRaw
|
||||
return MastodonVisibility(rawValue: rawValue) ?? ._other(rawValue)
|
||||
}
|
||||
set {
|
||||
visibilityRaw = newValue.rawValue
|
||||
}
|
||||
}
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var sensitive: Bool
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var spoilerText: String?
|
||||
|
||||
@NSManaged public private(set) var application: Application?
|
||||
|
||||
// Informational
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var reblogsCount: Int64
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var favouritesCount: Int64
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var repliesCount: Int64
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var url: String?
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var inReplyToID: Status.ID?
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var inReplyToAccountID: MastodonUser.ID?
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var language: String? // (ISO 639 Part 1 two-letter language code)
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var text: String?
|
||||
|
||||
// many-to-one relationship
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var author: MastodonUser
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var reblog: Status?
|
||||
// sourcery: autoUpdatableObject
|
||||
@NSManaged public private(set) var replyTo: Status?
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var favouritedBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var rebloggedBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var mutedBy: Set<MastodonUser>
|
||||
@NSManaged public private(set) var bookmarkedBy: Set<MastodonUser>
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var pinnedBy: MastodonUser?
|
||||
// sourcery: autoGenerateRelationship
|
||||
@NSManaged public private(set) var poll: Poll?
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var feeds: Set<Feed>
|
||||
|
||||
@NSManaged public private(set) var reblogFrom: Set<Status>
|
||||
// @NSManaged public private(set) var mentions: Set<Mention>?
|
||||
// @NSManaged public private(set) var homeTimelineIndexes: Set<HomeTimelineIndex>?
|
||||
// @NSManaged public private(set) var mediaAttachments: Set<Attachment>?
|
||||
@NSManaged public private(set) var replyFrom: Set<Status>
|
||||
@NSManaged public private(set) var notifications: Set<Notification>
|
||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var deletedAt: Date?
|
||||
// sourcery: autoUpdatableObject
|
||||
@NSManaged public private(set) var revealedAt: Date?
|
||||
}
|
||||
|
||||
extension Status {
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@objc public var attachments: [MastodonAttachment] {
|
||||
get {
|
||||
let keyPath = #keyPath(Status.attachments)
|
||||
willAccessValue(forKey: keyPath)
|
||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
||||
didAccessValue(forKey: keyPath)
|
||||
do {
|
||||
guard let data = _data else { return [] }
|
||||
let attachments = try JSONDecoder().decode([MastodonAttachment].self, from: data)
|
||||
return attachments
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}
|
||||
set {
|
||||
let keyPath = #keyPath(Status.attachments)
|
||||
let data = try? JSONEncoder().encode(newValue)
|
||||
willChangeValue(forKey: keyPath)
|
||||
setPrimitiveValue(data, forKey: keyPath)
|
||||
didChangeValue(forKey: keyPath)
|
||||
}
|
||||
}
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@objc public var emojis: [MastodonEmoji] {
|
||||
get {
|
||||
let keyPath = #keyPath(Status.emojis)
|
||||
willAccessValue(forKey: keyPath)
|
||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
||||
didAccessValue(forKey: keyPath)
|
||||
do {
|
||||
guard let data = _data else { return [] }
|
||||
let emojis = try JSONDecoder().decode([MastodonEmoji].self, from: data)
|
||||
return emojis
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}
|
||||
set {
|
||||
let keyPath = #keyPath(Status.emojis)
|
||||
let data = try? JSONEncoder().encode(newValue)
|
||||
willChangeValue(forKey: keyPath)
|
||||
setPrimitiveValue(data, forKey: keyPath)
|
||||
didChangeValue(forKey: keyPath)
|
||||
}
|
||||
}
|
||||
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@objc public var mentions: [MastodonMention] {
|
||||
get {
|
||||
let keyPath = #keyPath(Status.mentions)
|
||||
willAccessValue(forKey: keyPath)
|
||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
||||
didAccessValue(forKey: keyPath)
|
||||
do {
|
||||
guard let data = _data else { return [] }
|
||||
let emojis = try JSONDecoder().decode([MastodonMention].self, from: data)
|
||||
return emojis
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}
|
||||
set {
|
||||
let keyPath = #keyPath(Status.mentions)
|
||||
let data = try? JSONEncoder().encode(newValue)
|
||||
willChangeValue(forKey: keyPath)
|
||||
setPrimitiveValue(data, forKey: keyPath)
|
||||
didChangeValue(forKey: keyPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Status: FeedIndexable { }
|
||||
|
||||
extension Status {
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
relationship: Relationship
|
||||
) -> Status {
|
||||
let object: Status = context.insertObject()
|
||||
|
||||
object.configure(property: property)
|
||||
object.configure(relationship: relationship)
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
// @discardableResult
|
||||
// public static func insert(
|
||||
// into context: NSManagedObjectContext,
|
||||
// property: Property,
|
||||
// author: MastodonUser,
|
||||
// reblog: Status?,
|
||||
// application: Application?,
|
||||
// replyTo: Status?,
|
||||
// poll: Poll?,
|
||||
// mentions: [Mention]?,
|
||||
// mediaAttachments: [Attachment]?,
|
||||
// favouritedBy: MastodonUser?,
|
||||
// rebloggedBy: MastodonUser?,
|
||||
// mutedBy: MastodonUser?,
|
||||
// bookmarkedBy: MastodonUser?,
|
||||
// pinnedBy: MastodonUser?
|
||||
// ) -> Status {
|
||||
// let status: Status = context.insertObject()
|
||||
//
|
||||
// status.identifier = property.identifier
|
||||
// status.domain = property.domain
|
||||
//
|
||||
// status.id = property.id
|
||||
// status.uri = property.uri
|
||||
// status.createdAt = property.createdAt
|
||||
// status.content = property.content
|
||||
//
|
||||
// status.visibility = property.visibility
|
||||
// status.sensitive = property.sensitive
|
||||
// status.spoilerText = property.spoilerText
|
||||
// status.application = application
|
||||
//
|
||||
// status.emojisData = property.emojisData
|
||||
//
|
||||
// status.reblogsCount = property.reblogsCount
|
||||
// status.favouritesCount = property.favouritesCount
|
||||
// status.repliesCount = property.repliesCount
|
||||
//
|
||||
// status.url = property.url
|
||||
// status.inReplyToID = property.inReplyToID
|
||||
// status.inReplyToAccountID = property.inReplyToAccountID
|
||||
//
|
||||
// status.language = property.language
|
||||
// status.text = property.text
|
||||
//
|
||||
// status.author = author
|
||||
// status.reblog = reblog
|
||||
//
|
||||
// status.pinnedBy = pinnedBy
|
||||
// status.poll = poll
|
||||
//
|
||||
// if let mentions = mentions {
|
||||
// status.mutableSetValue(forKey: #keyPath(Status.mentions)).addObjects(from: mentions)
|
||||
// }
|
||||
// if let mediaAttachments = mediaAttachments {
|
||||
// status.mutableSetValue(forKey: #keyPath(Status.mediaAttachments)).addObjects(from: mediaAttachments)
|
||||
// }
|
||||
// if let favouritedBy = favouritedBy {
|
||||
// status.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).add(favouritedBy)
|
||||
// }
|
||||
// if let rebloggedBy = rebloggedBy {
|
||||
// status.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).add(rebloggedBy)
|
||||
// }
|
||||
// if let mutedBy = mutedBy {
|
||||
// status.mutableSetValue(forKey: #keyPath(Status.mutedBy)).add(mutedBy)
|
||||
// }
|
||||
// if let bookmarkedBy = bookmarkedBy {
|
||||
// status.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).add(bookmarkedBy)
|
||||
// }
|
||||
//
|
||||
// status.updatedAt = property.networkDate
|
||||
//
|
||||
// return status
|
||||
// }
|
||||
//
|
||||
// public func update(emojisData: Data?) {
|
||||
// if self.emojisData != emojisData {
|
||||
// self.emojisData = emojisData
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(reblogsCount: NSNumber) {
|
||||
// if self.reblogsCount.intValue != reblogsCount.intValue {
|
||||
// self.reblogsCount = reblogsCount
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(favouritesCount: NSNumber) {
|
||||
// if self.favouritesCount.intValue != favouritesCount.intValue {
|
||||
// self.favouritesCount = favouritesCount
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(repliesCount: NSNumber?) {
|
||||
// guard let count = repliesCount else {
|
||||
// return
|
||||
// }
|
||||
// if self.repliesCount?.intValue != count.intValue {
|
||||
// self.repliesCount = repliesCount
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(replyTo: Status?) {
|
||||
// if self.replyTo != replyTo {
|
||||
// self.replyTo = replyTo
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(liked: Bool, by mastodonUser: MastodonUser) {
|
||||
// if liked {
|
||||
// if !(self.favouritedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).add(mastodonUser)
|
||||
// }
|
||||
// } else {
|
||||
// if (self.favouritedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).remove(mastodonUser)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(reblogged: Bool, by mastodonUser: MastodonUser) {
|
||||
// if reblogged {
|
||||
// if !(self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).add(mastodonUser)
|
||||
// }
|
||||
// } else {
|
||||
// if (self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).remove(mastodonUser)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(muted: Bool, by mastodonUser: MastodonUser) {
|
||||
// if muted {
|
||||
// if !(self.mutedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.mutedBy)).add(mastodonUser)
|
||||
// }
|
||||
// } else {
|
||||
// if (self.mutedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.mutedBy)).remove(mastodonUser)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func update(bookmarked: Bool, by mastodonUser: MastodonUser) {
|
||||
// if bookmarked {
|
||||
// if !(self.bookmarkedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).add(mastodonUser)
|
||||
// }
|
||||
// } else {
|
||||
// if (self.bookmarkedBy ?? Set()).contains(mastodonUser) {
|
||||
// self.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).remove(mastodonUser)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func didUpdate(at networkDate: Date) {
|
||||
// self.updatedAt = networkDate
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
extension Status: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Status.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension Status {
|
||||
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Status.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(id: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Status.id), id)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, id: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(id: id)
|
||||
])
|
||||
}
|
||||
|
||||
static func predicate(ids: [String]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(Status.id), ids)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, ids: [String]) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(ids: ids)
|
||||
])
|
||||
}
|
||||
|
||||
public static func notDeleted() -> NSPredicate {
|
||||
return NSPredicate(format: "%K == nil", #keyPath(Status.deletedAt))
|
||||
}
|
||||
|
||||
public static func deleted() -> NSPredicate {
|
||||
return NSPredicate(format: "%K != nil", #keyPath(Status.deletedAt))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension Status: AutoGenerateProperty {
|
||||
// sourcery:inline:Status.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let identifier: ID
|
||||
public let domain: String
|
||||
public let id: String
|
||||
public let uri: String
|
||||
public let createdAt: Date
|
||||
public let content: String
|
||||
public let visibility: MastodonVisibility
|
||||
public let sensitive: Bool
|
||||
public let spoilerText: String?
|
||||
public let reblogsCount: Int64
|
||||
public let favouritesCount: Int64
|
||||
public let repliesCount: Int64
|
||||
public let url: String?
|
||||
public let inReplyToID: Status.ID?
|
||||
public let inReplyToAccountID: MastodonUser.ID?
|
||||
public let language: String?
|
||||
public let text: String?
|
||||
public let updatedAt: Date
|
||||
public let deletedAt: Date?
|
||||
public let attachments: [MastodonAttachment]
|
||||
public let emojis: [MastodonEmoji]
|
||||
public let mentions: [MastodonMention]
|
||||
|
||||
public init(
|
||||
identifier: ID,
|
||||
domain: String,
|
||||
id: String,
|
||||
uri: String,
|
||||
createdAt: Date,
|
||||
content: String,
|
||||
visibility: MastodonVisibility,
|
||||
sensitive: Bool,
|
||||
spoilerText: String?,
|
||||
reblogsCount: Int64,
|
||||
favouritesCount: Int64,
|
||||
repliesCount: Int64,
|
||||
url: String?,
|
||||
inReplyToID: Status.ID?,
|
||||
inReplyToAccountID: MastodonUser.ID?,
|
||||
language: String?,
|
||||
text: String?,
|
||||
updatedAt: Date,
|
||||
deletedAt: Date?,
|
||||
attachments: [MastodonAttachment],
|
||||
emojis: [MastodonEmoji],
|
||||
mentions: [MastodonMention]
|
||||
) {
|
||||
self.identifier = identifier
|
||||
self.domain = domain
|
||||
self.id = id
|
||||
self.uri = uri
|
||||
self.createdAt = createdAt
|
||||
self.content = content
|
||||
self.visibility = visibility
|
||||
self.sensitive = sensitive
|
||||
self.spoilerText = spoilerText
|
||||
self.reblogsCount = reblogsCount
|
||||
self.favouritesCount = favouritesCount
|
||||
self.repliesCount = repliesCount
|
||||
self.url = url
|
||||
self.inReplyToID = inReplyToID
|
||||
self.inReplyToAccountID = inReplyToAccountID
|
||||
self.language = language
|
||||
self.text = text
|
||||
self.updatedAt = updatedAt
|
||||
self.deletedAt = deletedAt
|
||||
self.attachments = attachments
|
||||
self.emojis = emojis
|
||||
self.mentions = mentions
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.identifier = property.identifier
|
||||
self.domain = property.domain
|
||||
self.id = property.id
|
||||
self.uri = property.uri
|
||||
self.createdAt = property.createdAt
|
||||
self.content = property.content
|
||||
self.visibility = property.visibility
|
||||
self.sensitive = property.sensitive
|
||||
self.spoilerText = property.spoilerText
|
||||
self.reblogsCount = property.reblogsCount
|
||||
self.favouritesCount = property.favouritesCount
|
||||
self.repliesCount = property.repliesCount
|
||||
self.url = property.url
|
||||
self.inReplyToID = property.inReplyToID
|
||||
self.inReplyToAccountID = property.inReplyToAccountID
|
||||
self.language = property.language
|
||||
self.text = property.text
|
||||
self.updatedAt = property.updatedAt
|
||||
self.deletedAt = property.deletedAt
|
||||
self.attachments = property.attachments
|
||||
self.emojis = property.emojis
|
||||
self.mentions = property.mentions
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(createdAt: property.createdAt)
|
||||
update(content: property.content)
|
||||
update(visibility: property.visibility)
|
||||
update(sensitive: property.sensitive)
|
||||
update(spoilerText: property.spoilerText)
|
||||
update(reblogsCount: property.reblogsCount)
|
||||
update(favouritesCount: property.favouritesCount)
|
||||
update(repliesCount: property.repliesCount)
|
||||
update(url: property.url)
|
||||
update(inReplyToID: property.inReplyToID)
|
||||
update(inReplyToAccountID: property.inReplyToAccountID)
|
||||
update(language: property.language)
|
||||
update(text: property.text)
|
||||
update(updatedAt: property.updatedAt)
|
||||
update(deletedAt: property.deletedAt)
|
||||
update(attachments: property.attachments)
|
||||
update(emojis: property.emojis)
|
||||
update(mentions: property.mentions)
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateRelationship
|
||||
extension Status: AutoGenerateRelationship {
|
||||
// sourcery:inline:Status.AutoGenerateRelationship
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Relationship {
|
||||
public let author: MastodonUser
|
||||
public let reblog: Status?
|
||||
public let poll: Poll?
|
||||
|
||||
public init(
|
||||
author: MastodonUser,
|
||||
reblog: Status?,
|
||||
poll: Poll?
|
||||
) {
|
||||
self.author = author
|
||||
self.reblog = reblog
|
||||
self.poll = poll
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(relationship: Relationship) {
|
||||
self.author = relationship.author
|
||||
self.reblog = relationship.reblog
|
||||
self.poll = relationship.poll
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension Status: AutoUpdatableObject {
|
||||
// sourcery:inline:Status.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(createdAt: Date) {
|
||||
if self.createdAt != createdAt {
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
public func update(content: String) {
|
||||
if self.content != content {
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
public func update(visibility: MastodonVisibility) {
|
||||
if self.visibility != visibility {
|
||||
self.visibility = visibility
|
||||
}
|
||||
}
|
||||
public func update(sensitive: Bool) {
|
||||
if self.sensitive != sensitive {
|
||||
self.sensitive = sensitive
|
||||
}
|
||||
}
|
||||
public func update(spoilerText: String?) {
|
||||
if self.spoilerText != spoilerText {
|
||||
self.spoilerText = spoilerText
|
||||
}
|
||||
}
|
||||
public func update(reblogsCount: Int64) {
|
||||
if self.reblogsCount != reblogsCount {
|
||||
self.reblogsCount = reblogsCount
|
||||
}
|
||||
}
|
||||
public func update(favouritesCount: Int64) {
|
||||
if self.favouritesCount != favouritesCount {
|
||||
self.favouritesCount = favouritesCount
|
||||
}
|
||||
}
|
||||
public func update(repliesCount: Int64) {
|
||||
if self.repliesCount != repliesCount {
|
||||
self.repliesCount = repliesCount
|
||||
}
|
||||
}
|
||||
public func update(url: String?) {
|
||||
if self.url != url {
|
||||
self.url = url
|
||||
}
|
||||
}
|
||||
public func update(inReplyToID: Status.ID?) {
|
||||
if self.inReplyToID != inReplyToID {
|
||||
self.inReplyToID = inReplyToID
|
||||
}
|
||||
}
|
||||
public func update(inReplyToAccountID: MastodonUser.ID?) {
|
||||
if self.inReplyToAccountID != inReplyToAccountID {
|
||||
self.inReplyToAccountID = inReplyToAccountID
|
||||
}
|
||||
}
|
||||
public func update(language: String?) {
|
||||
if self.language != language {
|
||||
self.language = language
|
||||
}
|
||||
}
|
||||
public func update(text: String?) {
|
||||
if self.text != text {
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
public func update(replyTo: Status?) {
|
||||
if self.replyTo != replyTo {
|
||||
self.replyTo = replyTo
|
||||
}
|
||||
}
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
public func update(deletedAt: Date?) {
|
||||
if self.deletedAt != deletedAt {
|
||||
self.deletedAt = deletedAt
|
||||
}
|
||||
}
|
||||
public func update(revealedAt: Date?) {
|
||||
if self.revealedAt != revealedAt {
|
||||
self.revealedAt = revealedAt
|
||||
}
|
||||
}
|
||||
public func update(attachments: [MastodonAttachment]) {
|
||||
if self.attachments != attachments {
|
||||
self.attachments = attachments
|
||||
}
|
||||
}
|
||||
public func update(emojis: [MastodonEmoji]) {
|
||||
if self.emojis != emojis {
|
||||
self.emojis = emojis
|
||||
}
|
||||
}
|
||||
public func update(mentions: [MastodonMention]) {
|
||||
if self.mentions != mentions {
|
||||
self.mentions = mentions
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
|
||||
public func update(liked: Bool, by mastodonUser: MastodonUser) {
|
||||
if liked {
|
||||
if !self.favouritedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.favouritedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(reblogged: Bool, by mastodonUser: MastodonUser) {
|
||||
if reblogged {
|
||||
if !self.rebloggedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.rebloggedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(muted: Bool, by mastodonUser: MastodonUser) {
|
||||
if muted {
|
||||
if !self.mutedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.mutedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.mutedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.mutedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(bookmarked: Bool, by mastodonUser: MastodonUser) {
|
||||
if bookmarked {
|
||||
if !self.bookmarkedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if self.bookmarkedBy.contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(isReveal: Bool) {
|
||||
revealedAt = isReveal ? Date() : nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Status {
|
||||
public func attach(feed: Feed) {
|
||||
mutableSetValue(forKey: #keyPath(Status.feeds)).add(feed)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//extension Status {
|
||||
// public struct Property {
|
||||
//
|
||||
// public let identifier: ID
|
||||
// public let domain: String
|
||||
//
|
||||
// public let id: String
|
||||
// public let uri: String
|
||||
// public let createdAt: Date
|
||||
// public let content: String
|
||||
//
|
||||
// public let visibility: String?
|
||||
// public let sensitive: Bool
|
||||
// public let spoilerText: String?
|
||||
//
|
||||
// public let emojisData: Data?
|
||||
//
|
||||
// public let reblogsCount: NSNumber
|
||||
// public let favouritesCount: NSNumber
|
||||
// public let repliesCount: NSNumber?
|
||||
//
|
||||
// public let url: String?
|
||||
// public let inReplyToID: Status.ID?
|
||||
// public let inReplyToAccountID: MastodonUser.ID?
|
||||
// public let language: String? // (ISO 639 Part @1 two-letter language code)
|
||||
// public let text: String?
|
||||
//
|
||||
// public let networkDate: Date
|
||||
//
|
||||
// public init(
|
||||
// domain: String,
|
||||
// id: String,
|
||||
// uri: String,
|
||||
// createdAt: Date,
|
||||
// content: String,
|
||||
// visibility: String?,
|
||||
// sensitive: Bool,
|
||||
// spoilerText: String?,
|
||||
// emojisData: Data?,
|
||||
// reblogsCount: NSNumber,
|
||||
// favouritesCount: NSNumber,
|
||||
// repliesCount: NSNumber?,
|
||||
// url: String?,
|
||||
// inReplyToID: Status.ID?,
|
||||
// inReplyToAccountID: MastodonUser.ID?,
|
||||
// language: String?,
|
||||
// text: String?,
|
||||
// networkDate: Date
|
||||
// ) {
|
||||
// self.identifier = id + "@" + domain
|
||||
// self.domain = domain
|
||||
// self.id = id
|
||||
// self.uri = uri
|
||||
// self.createdAt = createdAt
|
||||
// self.content = content
|
||||
// self.visibility = visibility
|
||||
// self.sensitive = sensitive
|
||||
// self.spoilerText = spoilerText
|
||||
// self.emojisData = emojisData
|
||||
// self.reblogsCount = reblogsCount
|
||||
// self.favouritesCount = favouritesCount
|
||||
// self.repliesCount = repliesCount
|
||||
// self.url = url
|
||||
// self.inReplyToID = inReplyToID
|
||||
// self.inReplyToAccountID = inReplyToAccountID
|
||||
// self.language = language
|
||||
// self.text = text
|
||||
// self.networkDate = networkDate
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//}
|
||||
//
|
|
@ -0,0 +1,218 @@
|
|||
//
|
||||
// Tag.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/2/1.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public final class Tag: NSManagedObject {
|
||||
public typealias ID = UUID
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var domain: String
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var createAt: Date
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// sourcery: autoGenerateProperty
|
||||
@NSManaged public private(set) var name: String
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@NSManaged public private(set) var url: String
|
||||
|
||||
// one-to-one relationship
|
||||
|
||||
// many-to-many relationship
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||
}
|
||||
|
||||
extension Tag {
|
||||
// sourcery: autoUpdatableObject, autoGenerateProperty
|
||||
@objc public var histories: [MastodonTagHistory] {
|
||||
get {
|
||||
let keyPath = #keyPath(Tag.histories)
|
||||
willAccessValue(forKey: keyPath)
|
||||
let _data = primitiveValue(forKey: keyPath) as? Data
|
||||
didAccessValue(forKey: keyPath)
|
||||
do {
|
||||
guard let data = _data else { return [] }
|
||||
let attachments = try JSONDecoder().decode([MastodonTagHistory].self, from: data)
|
||||
return attachments
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}
|
||||
set {
|
||||
let keyPath = #keyPath(Tag.histories)
|
||||
let data = try? JSONEncoder().encode(newValue)
|
||||
willChangeValue(forKey: keyPath)
|
||||
setPrimitiveValue(data, forKey: keyPath)
|
||||
didChangeValue(forKey: keyPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Tag {
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> Tag {
|
||||
let object: Tag = context.insertObject()
|
||||
|
||||
object.configure(property: property)
|
||||
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Tag: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
[NSSortDescriptor(keyPath: \Tag.createAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
public extension Tag {
|
||||
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
NSPredicate(format: "%K == %@", #keyPath(Tag.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(name: String) -> NSPredicate {
|
||||
NSPredicate(format: "%K == %@", #keyPath(Tag.name), name)
|
||||
}
|
||||
|
||||
static func predicate(domain: String, name: String) -> NSPredicate {
|
||||
NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(name: name),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AutoGenerateProperty
|
||||
extension Tag: AutoGenerateProperty {
|
||||
// sourcery:inline:Tag.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
public let identifier: ID
|
||||
public let domain: String
|
||||
public let createAt: Date
|
||||
public let updatedAt: Date
|
||||
public let name: String
|
||||
public let url: String
|
||||
public let histories: [MastodonTagHistory]
|
||||
|
||||
public init(
|
||||
identifier: ID,
|
||||
domain: String,
|
||||
createAt: Date,
|
||||
updatedAt: Date,
|
||||
name: String,
|
||||
url: String,
|
||||
histories: [MastodonTagHistory]
|
||||
) {
|
||||
self.identifier = identifier
|
||||
self.domain = domain
|
||||
self.createAt = createAt
|
||||
self.updatedAt = updatedAt
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.histories = histories
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
self.identifier = property.identifier
|
||||
self.domain = property.domain
|
||||
self.createAt = property.createAt
|
||||
self.updatedAt = property.updatedAt
|
||||
self.name = property.name
|
||||
self.url = property.url
|
||||
self.histories = property.histories
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
update(updatedAt: property.updatedAt)
|
||||
update(url: property.url)
|
||||
update(histories: property.histories)
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
// MARK: - AutoUpdatableObject
|
||||
extension Tag: AutoUpdatableObject {
|
||||
// sourcery:inline:Tag.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public func update(updatedAt: Date) {
|
||||
if self.updatedAt != updatedAt {
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
public func update(url: String) {
|
||||
if self.url != url {
|
||||
self.url = url
|
||||
}
|
||||
}
|
||||
public func update(histories: [MastodonTagHistory]) {
|
||||
if self.histories != histories {
|
||||
self.histories = histories
|
||||
}
|
||||
}
|
||||
// sourcery:end
|
||||
}
|
||||
|
||||
|
||||
extension Tag {
|
||||
|
||||
public func findSearchHistory(domain: String, userID: MastodonUser.ID) -> SearchHistory? {
|
||||
return searchHistories.first { searchHistory in
|
||||
return searchHistory.domain == domain
|
||||
&& searchHistory.userID == userID
|
||||
}
|
||||
}
|
||||
|
||||
public func findSearchHistory(for user: MastodonUser) -> SearchHistory? {
|
||||
return searchHistories.first { searchHistory in
|
||||
return searchHistory.domain == user.domain
|
||||
&& searchHistory.userID == user.id
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension Tag {
|
||||
// func updateHistory(index: Int, day: Date, uses: String, account: String) {
|
||||
// let histories = self.histories.sorted {
|
||||
// $0.createAt.compare($1.createAt) == .orderedAscending
|
||||
// }
|
||||
// guard index < histories.count else { return }
|
||||
// let history = histories[index]
|
||||
// history.update(day: day)
|
||||
// history.update(uses: uses)
|
||||
// history.update(accounts: account)
|
||||
// }
|
||||
//
|
||||
// func appendHistory(history: History) {
|
||||
// self.mutableSetValue(forKeyPath: #keyPath(Tag.histories)).add(history)
|
||||
// }
|
||||
//
|
||||
// func update(url: String) {
|
||||
// if self.url != url {
|
||||
// self.url = url
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -1,407 +0,0 @@
|
|||
//
|
||||
// MastodonUser.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/1/27.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
final public class MastodonUser: NSManagedObject {
|
||||
|
||||
public typealias ID = String
|
||||
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
@NSManaged public private(set) var domain: String
|
||||
|
||||
@NSManaged public private(set) var id: ID
|
||||
@NSManaged public private(set) var acct: String
|
||||
@NSManaged public private(set) var username: String
|
||||
@NSManaged public private(set) var displayName: String
|
||||
@NSManaged public private(set) var avatar: String
|
||||
@NSManaged public private(set) var avatarStatic: String?
|
||||
@NSManaged public private(set) var header: String
|
||||
@NSManaged public private(set) var headerStatic: String?
|
||||
@NSManaged public private(set) var note: String?
|
||||
@NSManaged public private(set) var url: String?
|
||||
|
||||
@NSManaged public private(set) var emojisData: Data?
|
||||
@NSManaged public private(set) var fieldsData: Data?
|
||||
|
||||
@NSManaged public private(set) var statusesCount: NSNumber
|
||||
@NSManaged public private(set) var followingCount: NSNumber
|
||||
@NSManaged public private(set) var followersCount: NSNumber
|
||||
|
||||
@NSManaged public private(set) var locked: Bool
|
||||
@NSManaged public private(set) var bot: Bool
|
||||
@NSManaged public private(set) var suspended: Bool
|
||||
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var pinnedStatus: Status?
|
||||
@NSManaged public private(set) var mastodonAuthentication: MastodonAuthentication?
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var statuses: Set<Status>?
|
||||
@NSManaged public private(set) var notifications: Set<MastodonNotification>?
|
||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var favourite: Set<Status>?
|
||||
@NSManaged public private(set) var reblogged: Set<Status>?
|
||||
@NSManaged public private(set) var muted: Set<Status>?
|
||||
@NSManaged public private(set) var bookmarked: Set<Status>?
|
||||
@NSManaged public private(set) var votePollOptions: Set<PollOption>?
|
||||
@NSManaged public private(set) var votePolls: Set<Poll>?
|
||||
// relationships
|
||||
@NSManaged public private(set) var following: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var followingBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var followRequested: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var followRequestedBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var muting: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var mutingBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var blocking: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var blockingBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var endorsed: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var endorsedBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var domainBlocking: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var domainBlockingBy: Set<MastodonUser>?
|
||||
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> MastodonUser {
|
||||
let user: MastodonUser = context.insertObject()
|
||||
|
||||
user.identifier = property.identifier
|
||||
user.domain = property.domain
|
||||
|
||||
user.id = property.id
|
||||
user.acct = property.acct
|
||||
user.username = property.username
|
||||
user.displayName = property.displayName
|
||||
user.avatar = property.avatar
|
||||
user.avatarStatic = property.avatarStatic
|
||||
user.header = property.header
|
||||
user.headerStatic = property.headerStatic
|
||||
user.note = property.note
|
||||
user.url = property.url
|
||||
user.emojisData = property.emojisData
|
||||
user.fieldsData = property.fieldsData
|
||||
|
||||
user.statusesCount = NSNumber(value: property.statusesCount)
|
||||
user.followingCount = NSNumber(value: property.followingCount)
|
||||
user.followersCount = NSNumber(value: property.followersCount)
|
||||
|
||||
user.locked = property.locked
|
||||
user.bot = property.bot ?? false
|
||||
user.suspended = property.suspended ?? false
|
||||
|
||||
// Mastodon do not provide relationship on the `Account`
|
||||
// Update relationship via attribute updating interface
|
||||
|
||||
user.createdAt = property.createdAt
|
||||
user.updatedAt = property.networkDate
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
|
||||
public func update(acct: String) {
|
||||
if self.acct != acct {
|
||||
self.acct = acct
|
||||
}
|
||||
}
|
||||
public func update(username: String) {
|
||||
if self.username != username {
|
||||
self.username = username
|
||||
}
|
||||
}
|
||||
public func update(displayName: String) {
|
||||
if self.displayName != displayName {
|
||||
self.displayName = displayName
|
||||
}
|
||||
}
|
||||
public func update(avatar: String) {
|
||||
if self.avatar != avatar {
|
||||
self.avatar = avatar
|
||||
}
|
||||
}
|
||||
public func update(avatarStatic: String?) {
|
||||
if self.avatarStatic != avatarStatic {
|
||||
self.avatarStatic = avatarStatic
|
||||
}
|
||||
}
|
||||
public func update(header: String) {
|
||||
if self.header != header {
|
||||
self.header = header
|
||||
}
|
||||
}
|
||||
public func update(headerStatic: String?) {
|
||||
if self.headerStatic != headerStatic {
|
||||
self.headerStatic = headerStatic
|
||||
}
|
||||
}
|
||||
public func update(note: String?) {
|
||||
if self.note != note {
|
||||
self.note = note
|
||||
}
|
||||
}
|
||||
public func update(url: String?) {
|
||||
if self.url != url {
|
||||
self.url = url
|
||||
}
|
||||
}
|
||||
public func update(emojisData: Data?) {
|
||||
if self.emojisData != emojisData {
|
||||
self.emojisData = emojisData
|
||||
}
|
||||
}
|
||||
public func update(fieldsData: Data?) {
|
||||
if self.fieldsData != fieldsData {
|
||||
self.fieldsData = fieldsData
|
||||
}
|
||||
}
|
||||
public func update(statusesCount: Int) {
|
||||
if self.statusesCount.intValue != statusesCount {
|
||||
self.statusesCount = NSNumber(value: statusesCount)
|
||||
}
|
||||
}
|
||||
public func update(followingCount: Int) {
|
||||
if self.followingCount.intValue != followingCount {
|
||||
self.followingCount = NSNumber(value: followingCount)
|
||||
}
|
||||
}
|
||||
public func update(followersCount: Int) {
|
||||
if self.followersCount.intValue != followersCount {
|
||||
self.followersCount = NSNumber(value: followersCount)
|
||||
}
|
||||
}
|
||||
public func update(locked: Bool) {
|
||||
if self.locked != locked {
|
||||
self.locked = locked
|
||||
}
|
||||
}
|
||||
public func update(bot: Bool) {
|
||||
if self.bot != bot {
|
||||
self.bot = bot
|
||||
}
|
||||
}
|
||||
public func update(suspended: Bool) {
|
||||
if self.suspended != suspended {
|
||||
self.suspended = suspended
|
||||
}
|
||||
}
|
||||
|
||||
public func update(isFollowing: Bool, by mastodonUser: MastodonUser) {
|
||||
if isFollowing {
|
||||
if !(self.followingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.followingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isFollowRequested: Bool, by mastodonUser: MastodonUser) {
|
||||
if isFollowRequested {
|
||||
if !(self.followRequestedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followRequestedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.followRequestedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.followRequestedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isMuting: Bool, by mastodonUser: MastodonUser) {
|
||||
if isMuting {
|
||||
if !(self.mutingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.mutingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.mutingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.mutingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isBlocking: Bool, by mastodonUser: MastodonUser) {
|
||||
if isBlocking {
|
||||
if !(self.blockingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.blockingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.blockingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.blockingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isEndorsed: Bool, by mastodonUser: MastodonUser) {
|
||||
if isEndorsed {
|
||||
if !(self.endorsedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.endorsedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.endorsedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.endorsedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
public func update(isDomainBlocking: Bool, by mastodonUser: MastodonUser) {
|
||||
if isDomainBlocking {
|
||||
if !(self.domainBlockingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.domainBlockingBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.domainBlockingBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(MastodonUser.domainBlockingBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
public func findSearchHistory(domain: String, userID: MastodonUser.ID) -> SearchHistory? {
|
||||
return searchHistories.first { searchHistory in
|
||||
return searchHistory.domain == domain
|
||||
&& searchHistory.userID == userID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
public struct Property {
|
||||
public let identifier: String
|
||||
public let domain: String
|
||||
|
||||
public let id: String
|
||||
public let acct: String
|
||||
public let username: String
|
||||
public let displayName: String
|
||||
public let avatar: String
|
||||
public let avatarStatic: String?
|
||||
public let header: String
|
||||
public let headerStatic: String?
|
||||
public let note: String?
|
||||
public let url: String?
|
||||
public let emojisData: Data?
|
||||
public let fieldsData: Data?
|
||||
public let statusesCount: Int
|
||||
public let followingCount: Int
|
||||
public let followersCount: Int
|
||||
public let locked: Bool
|
||||
public let bot: Bool?
|
||||
public let suspended: Bool?
|
||||
|
||||
public let createdAt: Date
|
||||
public let networkDate: Date
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
domain: String,
|
||||
acct: String,
|
||||
username: String,
|
||||
displayName: String,
|
||||
avatar: String,
|
||||
avatarStatic: String?,
|
||||
header: String,
|
||||
headerStatic: String?,
|
||||
note: String?,
|
||||
url: String?,
|
||||
emojisData: Data?,
|
||||
fieldsData: Data?,
|
||||
statusesCount: Int,
|
||||
followingCount: Int,
|
||||
followersCount: Int,
|
||||
locked: Bool,
|
||||
bot: Bool?,
|
||||
suspended: Bool?,
|
||||
createdAt: Date,
|
||||
networkDate: Date
|
||||
) {
|
||||
self.identifier = id + "@" + domain
|
||||
self.domain = domain
|
||||
self.id = id
|
||||
self.acct = acct
|
||||
self.username = username
|
||||
self.displayName = displayName
|
||||
self.avatar = avatar
|
||||
self.avatarStatic = avatarStatic
|
||||
self.header = header
|
||||
self.headerStatic = headerStatic
|
||||
self.note = note
|
||||
self.url = url
|
||||
self.emojisData = emojisData
|
||||
self.fieldsData = fieldsData
|
||||
self.statusesCount = statusesCount
|
||||
self.followingCount = followingCount
|
||||
self.followersCount = followersCount
|
||||
self.locked = locked
|
||||
self.bot = bot
|
||||
self.suspended = suspended
|
||||
self.createdAt = createdAt
|
||||
self.networkDate = networkDate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonUser: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \MastodonUser.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonUser {
|
||||
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(id: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.id), id)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, id: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonUser.predicate(domain: domain),
|
||||
MastodonUser.predicate(id: id)
|
||||
])
|
||||
}
|
||||
|
||||
static func predicate(ids: [String]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(MastodonUser.id), ids)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, ids: [String]) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonUser.predicate(domain: domain),
|
||||
MastodonUser.predicate(ids: ids)
|
||||
])
|
||||
}
|
||||
|
||||
static func predicate(username: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonUser.username), username)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, username: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonUser.predicate(domain: domain),
|
||||
MastodonUser.predicate(username: username)
|
||||
])
|
||||
}
|
||||
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
//
|
||||
// Mention.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/2/1.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public final class Mention: NSManagedObject {
|
||||
public typealias ID = UUID
|
||||
|
||||
@NSManaged public private(set) var index: NSNumber
|
||||
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
@NSManaged public private(set) var id: String
|
||||
@NSManaged public private(set) var createAt: Date
|
||||
|
||||
@NSManaged public private(set) var username: String
|
||||
@NSManaged public private(set) var acct: String
|
||||
@NSManaged public private(set) var url: String
|
||||
|
||||
// many-to-one relationship
|
||||
@NSManaged public private(set) var status: Status
|
||||
}
|
||||
|
||||
public extension Mention {
|
||||
override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
|
||||
setPrimitiveValue(UUID(), forKey: #keyPath(Mention.identifier))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
index: Int
|
||||
) -> Mention {
|
||||
let mention: Mention = context.insertObject()
|
||||
mention.index = NSNumber(value: index)
|
||||
mention.id = property.id
|
||||
mention.username = property.username
|
||||
mention.acct = property.acct
|
||||
mention.url = property.url
|
||||
return mention
|
||||
}
|
||||
}
|
||||
|
||||
public extension Mention {
|
||||
struct Property {
|
||||
public let id: String
|
||||
public let username: String
|
||||
public let acct: String
|
||||
public let url: String
|
||||
|
||||
public init(id: String, username: String, acct: String, url: String) {
|
||||
self.id = id
|
||||
self.username = username
|
||||
self.acct = acct
|
||||
self.url = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Mention: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Mention.createAt, ascending: false)]
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
//
|
||||
// MastodonNotification.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class MastodonNotification: NSManagedObject {
|
||||
public typealias ID = UUID
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
@NSManaged public private(set) var id: String
|
||||
@NSManaged public private(set) var createAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
@NSManaged public private(set) var typeRaw: String
|
||||
@NSManaged public private(set) var account: MastodonUser
|
||||
@NSManaged public private(set) var status: Status?
|
||||
|
||||
@NSManaged public private(set) var domain: String
|
||||
@NSManaged public private(set) var userID: String
|
||||
}
|
||||
|
||||
extension MastodonNotification {
|
||||
public override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
setPrimitiveValue(UUID(), forKey: #keyPath(MastodonNotification.identifier))
|
||||
}
|
||||
}
|
||||
|
||||
public extension MastodonNotification {
|
||||
@discardableResult
|
||||
static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
domain: String,
|
||||
userID: String,
|
||||
networkDate: Date,
|
||||
property: Property
|
||||
) -> MastodonNotification {
|
||||
let notification: MastodonNotification = context.insertObject()
|
||||
notification.id = property.id
|
||||
notification.createAt = property.createdAt
|
||||
notification.updatedAt = networkDate
|
||||
notification.typeRaw = property.typeRaw
|
||||
notification.account = property.account
|
||||
notification.status = property.status
|
||||
notification.domain = domain
|
||||
notification.userID = userID
|
||||
return notification
|
||||
}
|
||||
}
|
||||
|
||||
public extension MastodonNotification {
|
||||
struct Property {
|
||||
public init(id: String,
|
||||
typeRaw: String,
|
||||
account: MastodonUser,
|
||||
status: Status?,
|
||||
createdAt: Date
|
||||
) {
|
||||
self.id = id
|
||||
self.typeRaw = typeRaw
|
||||
self.account = account
|
||||
self.status = status
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let typeRaw: String
|
||||
public let account: MastodonUser
|
||||
public let status: Status?
|
||||
public let createdAt: Date
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonNotification {
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(userID: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.userID), userID)
|
||||
}
|
||||
|
||||
static func predicate(typeRaw: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(MastodonNotification.typeRaw), typeRaw)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, userID: String, typeRaw: String? = nil) -> NSPredicate {
|
||||
if let typeRaw = typeRaw {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonNotification.predicate(domain: domain),
|
||||
MastodonNotification.predicate(typeRaw: typeRaw),
|
||||
MastodonNotification.predicate(userID: userID),
|
||||
])
|
||||
} else {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
MastodonNotification.predicate(domain: domain),
|
||||
MastodonNotification.predicate(userID: userID)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
public static func predicate(validTypesRaws types: [String]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(MastodonNotification.typeRaw), types)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonNotification: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \MastodonNotification.createAt, ascending: false)]
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
//
|
||||
// Poll.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class Poll: NSManagedObject {
|
||||
public typealias ID = String
|
||||
|
||||
@NSManaged public private(set) var id: ID
|
||||
@NSManaged public private(set) var expiresAt: Date?
|
||||
@NSManaged public private(set) var expired: Bool
|
||||
@NSManaged public private(set) var multiple: Bool
|
||||
@NSManaged public private(set) var votesCount: NSNumber
|
||||
@NSManaged public private(set) var votersCount: NSNumber?
|
||||
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var status: Status
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var options: Set<PollOption>
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var votedBy: Set<MastodonUser>?
|
||||
}
|
||||
|
||||
extension Poll {
|
||||
|
||||
public override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(Poll.createdAt))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
votedBy: MastodonUser?,
|
||||
options: [PollOption]
|
||||
) -> Poll {
|
||||
let poll: Poll = context.insertObject()
|
||||
|
||||
poll.id = property.id
|
||||
poll.expiresAt = property.expiresAt
|
||||
poll.expired = property.expired
|
||||
poll.multiple = property.multiple
|
||||
poll.votesCount = property.votesCount
|
||||
poll.votersCount = property.votersCount
|
||||
|
||||
|
||||
poll.updatedAt = property.networkDate
|
||||
|
||||
if let votedBy = votedBy {
|
||||
poll.mutableSetValue(forKey: #keyPath(Poll.votedBy)).add(votedBy)
|
||||
}
|
||||
poll.mutableSetValue(forKey: #keyPath(Poll.options)).addObjects(from: options)
|
||||
|
||||
return poll
|
||||
}
|
||||
|
||||
public func update(expiresAt: Date?) {
|
||||
if self.expiresAt != expiresAt {
|
||||
self.expiresAt = expiresAt
|
||||
}
|
||||
}
|
||||
|
||||
public func update(expired: Bool) {
|
||||
if self.expired != expired {
|
||||
self.expired = expired
|
||||
}
|
||||
}
|
||||
|
||||
public func update(votesCount: Int) {
|
||||
if self.votesCount.intValue != votesCount {
|
||||
self.votesCount = NSNumber(value: votesCount)
|
||||
}
|
||||
}
|
||||
|
||||
public func update(votersCount: Int?) {
|
||||
if self.votersCount?.intValue != votersCount {
|
||||
self.votersCount = votersCount.flatMap { NSNumber(value: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
public func update(voted: Bool, by: MastodonUser) {
|
||||
if voted {
|
||||
if !(votedBy ?? Set()).contains(by) {
|
||||
mutableSetValue(forKey: #keyPath(Poll.votedBy)).add(by)
|
||||
}
|
||||
} else {
|
||||
if (votedBy ?? Set()).contains(by) {
|
||||
mutableSetValue(forKey: #keyPath(Poll.votedBy)).remove(by)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Poll {
|
||||
public struct Property {
|
||||
public let id: ID
|
||||
public let expiresAt: Date?
|
||||
public let expired: Bool
|
||||
public let multiple: Bool
|
||||
public let votesCount: NSNumber
|
||||
public let votersCount: NSNumber?
|
||||
|
||||
public let networkDate: Date
|
||||
|
||||
public init(
|
||||
id: Poll.ID,
|
||||
expiresAt: Date?,
|
||||
expired: Bool,
|
||||
multiple: Bool,
|
||||
votesCount: Int,
|
||||
votersCount: Int?,
|
||||
networkDate: Date
|
||||
) {
|
||||
self.id = id
|
||||
self.expiresAt = expiresAt
|
||||
self.expired = expired
|
||||
self.multiple = multiple
|
||||
self.votesCount = NSNumber(value: votesCount)
|
||||
self.votersCount = votersCount.flatMap { NSNumber(value: $0) }
|
||||
self.networkDate = networkDate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Poll: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Poll.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
//
|
||||
// PollOption.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class PollOption: NSManagedObject {
|
||||
@NSManaged public private(set) var index: NSNumber
|
||||
@NSManaged public private(set) var title: String
|
||||
@NSManaged public private(set) var votesCount: NSNumber?
|
||||
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// many-to-one relationship
|
||||
@NSManaged public private(set) var poll: Poll
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var votedBy: Set<MastodonUser>?
|
||||
}
|
||||
|
||||
extension PollOption {
|
||||
|
||||
public override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(PollOption.createdAt))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
votedBy: MastodonUser?
|
||||
) -> PollOption {
|
||||
let option: PollOption = context.insertObject()
|
||||
|
||||
option.index = property.index
|
||||
option.title = property.title
|
||||
option.votesCount = property.votesCount
|
||||
option.updatedAt = property.networkDate
|
||||
|
||||
if let votedBy = votedBy {
|
||||
option.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).add(votedBy)
|
||||
}
|
||||
|
||||
return option
|
||||
}
|
||||
|
||||
public func update(votesCount: Int?) {
|
||||
if self.votesCount?.intValue != votesCount {
|
||||
self.votesCount = votesCount.flatMap { NSNumber(value: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
public func update(voted: Bool, by: MastodonUser) {
|
||||
if voted {
|
||||
if !(self.votedBy ?? Set()).contains(by) {
|
||||
self.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).add(by)
|
||||
}
|
||||
} else {
|
||||
if (self.votedBy ?? Set()).contains(by) {
|
||||
self.mutableSetValue(forKey: #keyPath(PollOption.votedBy)).remove(by)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PollOption {
|
||||
public struct Property {
|
||||
public let index: NSNumber
|
||||
public let title: String
|
||||
public let votesCount: NSNumber?
|
||||
|
||||
public let networkDate: Date
|
||||
|
||||
public init(index: Int, title: String, votesCount: Int?, networkDate: Date) {
|
||||
self.index = NSNumber(value: index)
|
||||
self.title = title
|
||||
self.votesCount = votesCount.flatMap { NSNumber(value: $0) }
|
||||
self.networkDate = networkDate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PollOption: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \PollOption.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
//
|
||||
// SearchHistory.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/7.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public final class SearchHistory: NSManagedObject {
|
||||
public typealias ID = UUID
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
@NSManaged public private(set) var domain: String
|
||||
@NSManaged public private(set) var userID: MastodonUser.ID
|
||||
@NSManaged public private(set) var createAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// many-to-one relationship
|
||||
@NSManaged public private(set) var account: MastodonUser?
|
||||
@NSManaged public private(set) var hashtag: Tag?
|
||||
@NSManaged public private(set) var status: Status?
|
||||
|
||||
}
|
||||
|
||||
extension SearchHistory {
|
||||
public override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
setPrimitiveValue(UUID(), forKey: #keyPath(SearchHistory.identifier))
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.createAt))
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.updatedAt))
|
||||
}
|
||||
|
||||
// public override func willSave() {
|
||||
// super.willSave()
|
||||
// setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.updatedAt))
|
||||
// }
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
account: MastodonUser
|
||||
) -> SearchHistory {
|
||||
let searchHistory: SearchHistory = context.insertObject()
|
||||
searchHistory.domain = property.domain
|
||||
searchHistory.userID = property.userID
|
||||
searchHistory.account = account
|
||||
return searchHistory
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
hashtag: Tag
|
||||
) -> SearchHistory {
|
||||
let searchHistory: SearchHistory = context.insertObject()
|
||||
searchHistory.domain = property.domain
|
||||
searchHistory.userID = property.userID
|
||||
searchHistory.hashtag = hashtag
|
||||
return searchHistory
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
status: Status
|
||||
) -> SearchHistory {
|
||||
let searchHistory: SearchHistory = context.insertObject()
|
||||
searchHistory.domain = property.domain
|
||||
searchHistory.userID = property.userID
|
||||
searchHistory.status = status
|
||||
return searchHistory
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchHistory {
|
||||
public func update(updatedAt: Date) {
|
||||
setValue(updatedAt, forKey: #keyPath(SearchHistory.updatedAt))
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchHistory {
|
||||
public struct Property {
|
||||
public let domain: String
|
||||
public let userID: MastodonUser.ID
|
||||
|
||||
public init(domain: String, userID: MastodonUser.ID) {
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchHistory: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \SearchHistory.updatedAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchHistory {
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(SearchHistory.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(userID: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(SearchHistory.userID), userID)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, userID: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(userID: userID)
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,355 +0,0 @@
|
|||
//
|
||||
// Status.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021/1/27.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public final class Status: NSManagedObject {
|
||||
public typealias ID = String
|
||||
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
@NSManaged public private(set) var domain: String
|
||||
|
||||
@NSManaged public private(set) var id: String
|
||||
@NSManaged public private(set) var uri: String
|
||||
@NSManaged public private(set) var createdAt: Date
|
||||
@NSManaged public private(set) var content: String
|
||||
|
||||
@NSManaged public private(set) var visibility: String?
|
||||
@NSManaged public private(set) var sensitive: Bool
|
||||
@NSManaged public private(set) var spoilerText: String?
|
||||
@NSManaged public private(set) var application: Application?
|
||||
|
||||
@NSManaged public private(set) var emojisData: Data?
|
||||
|
||||
// Informational
|
||||
@NSManaged public private(set) var reblogsCount: NSNumber
|
||||
@NSManaged public private(set) var favouritesCount: NSNumber
|
||||
@NSManaged public private(set) var repliesCount: NSNumber?
|
||||
|
||||
@NSManaged public private(set) var url: String?
|
||||
@NSManaged public private(set) var inReplyToID: Status.ID?
|
||||
@NSManaged public private(set) var inReplyToAccountID: MastodonUser.ID?
|
||||
|
||||
@NSManaged public private(set) var language: String? // (ISO 639 Part 1 two-letter language code)
|
||||
@NSManaged public private(set) var text: String?
|
||||
|
||||
// many-to-one relationship
|
||||
@NSManaged public private(set) var author: MastodonUser
|
||||
@NSManaged public private(set) var reblog: Status?
|
||||
@NSManaged public private(set) var replyTo: Status?
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var favouritedBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var rebloggedBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var mutedBy: Set<MastodonUser>?
|
||||
@NSManaged public private(set) var bookmarkedBy: Set<MastodonUser>?
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var pinnedBy: MastodonUser?
|
||||
@NSManaged public private(set) var poll: Poll?
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var reblogFrom: Set<Status>?
|
||||
@NSManaged public private(set) var mentions: Set<Mention>?
|
||||
@NSManaged public private(set) var homeTimelineIndexes: Set<HomeTimelineIndex>?
|
||||
@NSManaged public private(set) var mediaAttachments: Set<Attachment>?
|
||||
@NSManaged public private(set) var replyFrom: Set<Status>?
|
||||
|
||||
@NSManaged public private(set) var inNotifications: Set<MastodonNotification>?
|
||||
|
||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
@NSManaged public private(set) var deletedAt: Date?
|
||||
@NSManaged public private(set) var revealedAt: Date?
|
||||
}
|
||||
|
||||
extension Status {
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property,
|
||||
author: MastodonUser,
|
||||
reblog: Status?,
|
||||
application: Application?,
|
||||
replyTo: Status?,
|
||||
poll: Poll?,
|
||||
mentions: [Mention]?,
|
||||
mediaAttachments: [Attachment]?,
|
||||
favouritedBy: MastodonUser?,
|
||||
rebloggedBy: MastodonUser?,
|
||||
mutedBy: MastodonUser?,
|
||||
bookmarkedBy: MastodonUser?,
|
||||
pinnedBy: MastodonUser?
|
||||
) -> Status {
|
||||
let status: Status = context.insertObject()
|
||||
|
||||
status.identifier = property.identifier
|
||||
status.domain = property.domain
|
||||
|
||||
status.id = property.id
|
||||
status.uri = property.uri
|
||||
status.createdAt = property.createdAt
|
||||
status.content = property.content
|
||||
|
||||
status.visibility = property.visibility
|
||||
status.sensitive = property.sensitive
|
||||
status.spoilerText = property.spoilerText
|
||||
status.application = application
|
||||
|
||||
status.emojisData = property.emojisData
|
||||
|
||||
status.reblogsCount = property.reblogsCount
|
||||
status.favouritesCount = property.favouritesCount
|
||||
status.repliesCount = property.repliesCount
|
||||
|
||||
status.url = property.url
|
||||
status.inReplyToID = property.inReplyToID
|
||||
status.inReplyToAccountID = property.inReplyToAccountID
|
||||
|
||||
status.language = property.language
|
||||
status.text = property.text
|
||||
|
||||
status.author = author
|
||||
status.reblog = reblog
|
||||
|
||||
status.pinnedBy = pinnedBy
|
||||
status.poll = poll
|
||||
|
||||
if let mentions = mentions {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.mentions)).addObjects(from: mentions)
|
||||
}
|
||||
if let mediaAttachments = mediaAttachments {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.mediaAttachments)).addObjects(from: mediaAttachments)
|
||||
}
|
||||
if let favouritedBy = favouritedBy {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).add(favouritedBy)
|
||||
}
|
||||
if let rebloggedBy = rebloggedBy {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).add(rebloggedBy)
|
||||
}
|
||||
if let mutedBy = mutedBy {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.mutedBy)).add(mutedBy)
|
||||
}
|
||||
if let bookmarkedBy = bookmarkedBy {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).add(bookmarkedBy)
|
||||
}
|
||||
|
||||
status.updatedAt = property.networkDate
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
public func update(emojisData: Data?) {
|
||||
if self.emojisData != emojisData {
|
||||
self.emojisData = emojisData
|
||||
}
|
||||
}
|
||||
|
||||
public func update(reblogsCount: NSNumber) {
|
||||
if self.reblogsCount.intValue != reblogsCount.intValue {
|
||||
self.reblogsCount = reblogsCount
|
||||
}
|
||||
}
|
||||
|
||||
public func update(favouritesCount: NSNumber) {
|
||||
if self.favouritesCount.intValue != favouritesCount.intValue {
|
||||
self.favouritesCount = favouritesCount
|
||||
}
|
||||
}
|
||||
|
||||
public func update(repliesCount: NSNumber?) {
|
||||
guard let count = repliesCount else {
|
||||
return
|
||||
}
|
||||
if self.repliesCount?.intValue != count.intValue {
|
||||
self.repliesCount = repliesCount
|
||||
}
|
||||
}
|
||||
|
||||
public func update(replyTo: Status?) {
|
||||
if self.replyTo != replyTo {
|
||||
self.replyTo = replyTo
|
||||
}
|
||||
}
|
||||
|
||||
public func update(liked: Bool, by mastodonUser: MastodonUser) {
|
||||
if liked {
|
||||
if !(self.favouritedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.favouritedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.favouritedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(reblogged: Bool, by mastodonUser: MastodonUser) {
|
||||
if reblogged {
|
||||
if !(self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.rebloggedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(muted: Bool, by mastodonUser: MastodonUser) {
|
||||
if muted {
|
||||
if !(self.mutedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.mutedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.mutedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.mutedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(bookmarked: Bool, by mastodonUser: MastodonUser) {
|
||||
if bookmarked {
|
||||
if !(self.bookmarkedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).add(mastodonUser)
|
||||
}
|
||||
} else {
|
||||
if (self.bookmarkedBy ?? Set()).contains(mastodonUser) {
|
||||
self.mutableSetValue(forKey: #keyPath(Status.bookmarkedBy)).remove(mastodonUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(isReveal: Bool) {
|
||||
revealedAt = isReveal ? Date() : nil
|
||||
}
|
||||
|
||||
public func didUpdate(at networkDate: Date) {
|
||||
self.updatedAt = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Status {
|
||||
public struct Property {
|
||||
|
||||
public let identifier: ID
|
||||
public let domain: String
|
||||
|
||||
public let id: String
|
||||
public let uri: String
|
||||
public let createdAt: Date
|
||||
public let content: String
|
||||
|
||||
public let visibility: String?
|
||||
public let sensitive: Bool
|
||||
public let spoilerText: String?
|
||||
|
||||
public let emojisData: Data?
|
||||
|
||||
public let reblogsCount: NSNumber
|
||||
public let favouritesCount: NSNumber
|
||||
public let repliesCount: NSNumber?
|
||||
|
||||
public let url: String?
|
||||
public let inReplyToID: Status.ID?
|
||||
public let inReplyToAccountID: MastodonUser.ID?
|
||||
public let language: String? // (ISO 639 Part @1 two-letter language code)
|
||||
public let text: String?
|
||||
|
||||
public let networkDate: Date
|
||||
|
||||
public init(
|
||||
domain: String,
|
||||
id: String,
|
||||
uri: String,
|
||||
createdAt: Date,
|
||||
content: String,
|
||||
visibility: String?,
|
||||
sensitive: Bool,
|
||||
spoilerText: String?,
|
||||
emojisData: Data?,
|
||||
reblogsCount: NSNumber,
|
||||
favouritesCount: NSNumber,
|
||||
repliesCount: NSNumber?,
|
||||
url: String?,
|
||||
inReplyToID: Status.ID?,
|
||||
inReplyToAccountID: MastodonUser.ID?,
|
||||
language: String?,
|
||||
text: String?,
|
||||
networkDate: Date
|
||||
) {
|
||||
self.identifier = id + "@" + domain
|
||||
self.domain = domain
|
||||
self.id = id
|
||||
self.uri = uri
|
||||
self.createdAt = createdAt
|
||||
self.content = content
|
||||
self.visibility = visibility
|
||||
self.sensitive = sensitive
|
||||
self.spoilerText = spoilerText
|
||||
self.emojisData = emojisData
|
||||
self.reblogsCount = reblogsCount
|
||||
self.favouritesCount = favouritesCount
|
||||
self.repliesCount = repliesCount
|
||||
self.url = url
|
||||
self.inReplyToID = inReplyToID
|
||||
self.inReplyToAccountID = inReplyToAccountID
|
||||
self.language = language
|
||||
self.text = text
|
||||
self.networkDate = networkDate
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension Status: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
return [NSSortDescriptor(keyPath: \Status.createdAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
extension Status {
|
||||
|
||||
static func predicate(domain: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Status.domain), domain)
|
||||
}
|
||||
|
||||
static func predicate(id: String) -> NSPredicate {
|
||||
return NSPredicate(format: "%K == %@", #keyPath(Status.id), id)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, id: String) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(id: id)
|
||||
])
|
||||
}
|
||||
|
||||
static func predicate(ids: [String]) -> NSPredicate {
|
||||
return NSPredicate(format: "%K IN %@", #keyPath(Status.id), ids)
|
||||
}
|
||||
|
||||
public static func predicate(domain: String, ids: [String]) -> NSPredicate {
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
predicate(domain: domain),
|
||||
predicate(ids: ids)
|
||||
])
|
||||
}
|
||||
|
||||
public static func notDeleted() -> NSPredicate {
|
||||
return NSPredicate(format: "%K == nil", #keyPath(Status.deletedAt))
|
||||
}
|
||||
|
||||
public static func deleted() -> NSPredicate {
|
||||
return NSPredicate(format: "%K != nil", #keyPath(Status.deletedAt))
|
||||
}
|
||||
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
//
|
||||
// Tag.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/2/1.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
public final class Tag: NSManagedObject {
|
||||
public typealias ID = UUID
|
||||
@NSManaged public private(set) var identifier: ID
|
||||
@NSManaged public private(set) var createAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
@NSManaged public private(set) var name: String
|
||||
@NSManaged public private(set) var url: String
|
||||
|
||||
// one-to-one relationship
|
||||
|
||||
// many-to-many relationship
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var histories: Set<History>?
|
||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||
}
|
||||
|
||||
public extension Tag {
|
||||
override func awakeFromInsert() {
|
||||
super.awakeFromInsert()
|
||||
setPrimitiveValue(UUID(), forKey: #keyPath(Tag.identifier))
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(Tag.createAt))
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(Tag.updatedAt))
|
||||
}
|
||||
|
||||
override func willSave() {
|
||||
super.willSave()
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(Tag.updatedAt))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func insert(
|
||||
into context: NSManagedObjectContext,
|
||||
property: Property
|
||||
) -> Tag {
|
||||
let tag: Tag = context.insertObject()
|
||||
tag.name = property.name
|
||||
tag.url = property.url
|
||||
if let histories = property.histories {
|
||||
tag.mutableSetValue(forKey: #keyPath(Tag.histories)).addObjects(from: histories)
|
||||
}
|
||||
return tag
|
||||
}
|
||||
}
|
||||
|
||||
extension Tag {
|
||||
public func findSearchHistory(domain: String, userID: MastodonUser.ID) -> SearchHistory? {
|
||||
return searchHistories.first { searchHistory in
|
||||
return searchHistory.domain == domain
|
||||
&& searchHistory.userID == userID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Tag {
|
||||
struct Property {
|
||||
public let name: String
|
||||
public let url: String
|
||||
public let histories: [History]?
|
||||
|
||||
public init(name: String, url: String, histories: [History]?) {
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.histories = histories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Tag {
|
||||
func updateHistory(index: Int, day: Date, uses: String, account: String) {
|
||||
guard let histories = self.histories?.sorted(by: {
|
||||
$0.createAt.compare($1.createAt) == .orderedAscending
|
||||
}) else { return }
|
||||
let history = histories[index]
|
||||
history.update(day: day)
|
||||
history.update(uses: uses)
|
||||
history.update(accounts: account)
|
||||
}
|
||||
|
||||
func appendHistory(history: History) {
|
||||
self.mutableSetValue(forKeyPath: #keyPath(Tag.histories)).add(history)
|
||||
}
|
||||
|
||||
func update(url: String) {
|
||||
if self.url != url {
|
||||
self.url = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Tag: Managed {
|
||||
public static var defaultSortDescriptors: [NSSortDescriptor] {
|
||||
[NSSortDescriptor(keyPath: \Tag.createAt, ascending: false)]
|
||||
}
|
||||
}
|
||||
|
||||
public extension Tag {
|
||||
static func predicate(name: String) -> NSPredicate {
|
||||
NSPredicate(format: "%K == %@", #keyPath(Tag.name), name)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Feed+Acct.swift
|
||||
// Feed+Acct
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-26.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Feed {
|
||||
public enum Acct: RawRepresentable {
|
||||
case none
|
||||
case mastodon(domain: String, userID: MastodonUser.ID)
|
||||
|
||||
public init?(rawValue: String) {
|
||||
let components = rawValue.split(separator: "@", maxSplits: 2)
|
||||
guard components.count == 3 else { return nil }
|
||||
let userID = String(components[1]).escape
|
||||
let domain = String(components[2]).escape
|
||||
|
||||
switch components[0] {
|
||||
case "M":
|
||||
self = .mastodon(domain: domain, userID: userID)
|
||||
default:
|
||||
self = .none
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public var rawValue: String {
|
||||
switch self {
|
||||
case .none:
|
||||
return "none@userID@domain"
|
||||
case .mastodon(let domain, let userID):
|
||||
return "M@\(userID.escape)@\(domain.escape)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
fileprivate var escape: String {
|
||||
replacingOccurrences(of: "@", with: "_at_")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// Feed+Kind.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK on 2022-1-11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Feed {
|
||||
public enum Kind: String, CaseIterable, Hashable {
|
||||
case none
|
||||
case home
|
||||
case notificationAll
|
||||
case notificationMentions
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// MastodonAttachment.swift
|
||||
// MastodonAttachment
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-30.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
public final class MastodonAttachment: NSObject, Codable {
|
||||
public typealias ID = String
|
||||
|
||||
public let id: ID
|
||||
public let kind: Kind
|
||||
public let size: CGSize
|
||||
public let focus: CGPoint?
|
||||
public let blurhash: String?
|
||||
public let assetURL: String?
|
||||
public let previewURL: String?
|
||||
public let textURL: String?
|
||||
public let durationMS: Int?
|
||||
public let altDescription: String?
|
||||
|
||||
public init(
|
||||
id: MastodonAttachment.ID,
|
||||
kind: MastodonAttachment.Kind,
|
||||
size: CGSize,
|
||||
focus: CGPoint?,
|
||||
blurhash: String?,
|
||||
assetURL: String?,
|
||||
previewURL: String?,
|
||||
textURL: String?,
|
||||
durationMS: Int?,
|
||||
altDescription: String?
|
||||
) {
|
||||
self.id = id
|
||||
self.kind = kind
|
||||
self.size = size
|
||||
self.focus = focus
|
||||
self.blurhash = blurhash
|
||||
self.assetURL = assetURL
|
||||
self.previewURL = previewURL
|
||||
self.textURL = textURL
|
||||
self.durationMS = durationMS
|
||||
self.altDescription = altDescription
|
||||
}
|
||||
}
|
||||
|
||||
extension MastodonAttachment {
|
||||
public enum Kind: String, Codable {
|
||||
case image
|
||||
case video
|
||||
case gifv
|
||||
case audio
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// MastodonEmoji.swift
|
||||
// MastodonEmoji
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-9-2.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class MastodonEmoji: NSObject, Codable {
|
||||
public let code: String
|
||||
public let url: String
|
||||
public let staticURL: String
|
||||
public let visibleInPicker: Bool
|
||||
public let category: String?
|
||||
|
||||
public init(code:
|
||||
String, url:
|
||||
String, staticURL:
|
||||
String, visibleInPicker:
|
||||
Bool, category: String?
|
||||
) {
|
||||
self.code = code
|
||||
self.url = url
|
||||
self.staticURL = staticURL
|
||||
self.visibleInPicker = visibleInPicker
|
||||
self.category = category
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// MastodonField.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-9-18.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class MastodonField: NSObject, Codable {
|
||||
public let name: String
|
||||
public let value: String
|
||||
public let verifiedAt: Date?
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
value: String,
|
||||
verifiedAt: Date?
|
||||
) {
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.verifiedAt = verifiedAt
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// MastodonMention.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK on 2022-1-17.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class MastodonMention: NSObject, Codable {
|
||||
|
||||
public typealias ID = String
|
||||
|
||||
public let id: ID
|
||||
public let username: String
|
||||
public let acct: String
|
||||
public let url: String
|
||||
|
||||
public init(
|
||||
id: MastodonMention.ID,
|
||||
username: String,
|
||||
acct: String,
|
||||
url: String
|
||||
) {
|
||||
self.id = id
|
||||
self.username = username
|
||||
self.acct = acct
|
||||
self.url = url
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// MastodonNotificationType.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK on 2022-1-21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum MastodonNotificationType: RawRepresentable {
|
||||
case follow
|
||||
case followRequest
|
||||
case mention
|
||||
case reblog
|
||||
case favourite // same to API
|
||||
case poll
|
||||
case status
|
||||
|
||||
case _other(String)
|
||||
|
||||
public init?(rawValue: String) {
|
||||
switch rawValue {
|
||||
case "follow": self = .follow
|
||||
case "followRequest": self = .followRequest
|
||||
case "mention": self = .mention
|
||||
case "reblog": self = .reblog
|
||||
case "favourite": self = .favourite
|
||||
case "poll": self = .poll
|
||||
case "status": self = .status
|
||||
default: self = ._other(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: String {
|
||||
switch self {
|
||||
case .follow: return "follow"
|
||||
case .followRequest: return "followRequest"
|
||||
case .mention: return "mention"
|
||||
case .reblog: return "reblog"
|
||||
case .favourite: return "favourite"
|
||||
case .poll: return "poll"
|
||||
case .status: return "status"
|
||||
case ._other(let value): return value
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// MastodonTagHistory.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by MainasuK on 2022-1-20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class MastodonTagHistory: NSObject, Codable {
|
||||
|
||||
/// UNIX timestamp on midnight of the given day
|
||||
public let day: Date
|
||||
public let uses: String
|
||||
public let accounts: String
|
||||
|
||||
public init(day: Date, uses: String, accounts: String) {
|
||||
self.day = day
|
||||
self.uses = uses
|
||||
self.accounts = accounts
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// MastodonVisibility.swift
|
||||
// MastodonVisibility
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-27.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum MastodonVisibility: RawRepresentable {
|
||||
case `public`
|
||||
case unlisted
|
||||
case `private`
|
||||
case direct
|
||||
|
||||
case _other(String)
|
||||
|
||||
public init?(rawValue: String) {
|
||||
switch rawValue {
|
||||
case "public": self = .public
|
||||
case "unlisted": self = .unlisted
|
||||
case "private": self = .private
|
||||
case "direct": self = .direct
|
||||
default: self = ._other(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: String {
|
||||
switch self {
|
||||
case .public: return "public"
|
||||
case .unlisted: return "unlisted"
|
||||
case .private: return "private"
|
||||
case .direct: return "direct"
|
||||
case ._other(let value): return value
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,3 +47,66 @@ extension NSManagedObjectContext {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSManagedObjectContext {
|
||||
public func perform<T>(block: @escaping () throws -> T) async throws -> T {
|
||||
if #available(iOSApplicationExtension 15.0, *) {
|
||||
return try await perform(schedule: .enqueued) {
|
||||
try block()
|
||||
}
|
||||
} else {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
self.perform {
|
||||
do {
|
||||
let value = try block()
|
||||
continuation.resume(returning: value)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
} // end return
|
||||
}
|
||||
}
|
||||
|
||||
public func performChanges<T>(block: @escaping () throws -> T) async throws -> T {
|
||||
if #available(iOS 15.0, *) {
|
||||
return try await perform(schedule: .enqueued) {
|
||||
let value = try block()
|
||||
try self.saveOrRollback()
|
||||
return value
|
||||
}
|
||||
} else {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
self.perform {
|
||||
do {
|
||||
let value = try block()
|
||||
try self.saveOrRollback()
|
||||
continuation.resume(returning: value)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
} // end return
|
||||
}
|
||||
} // end func
|
||||
}
|
||||
|
||||
extension NSManagedObjectContext {
|
||||
static let objectCacheKey = "ObjectCacheKey"
|
||||
private typealias ObjectCache = [String: NSManagedObject]
|
||||
|
||||
public func cache(
|
||||
_ object: NSManagedObject?,
|
||||
key: String
|
||||
) {
|
||||
var cache = userInfo[NSManagedObjectContext.objectCacheKey] as? ObjectCache ?? [:]
|
||||
cache[key] = object
|
||||
userInfo[NSManagedObjectContext.objectCacheKey] = cache
|
||||
}
|
||||
|
||||
public func cache(froKey key: String) -> NSManagedObject? {
|
||||
guard let cache = userInfo[NSManagedObjectContext.objectCacheKey] as? ObjectCache
|
||||
else { return nil }
|
||||
return cache[key]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>90</string>
|
||||
<string>91</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -10,10 +10,10 @@ import CoreData
|
|||
|
||||
public struct ManagedObjectContextObjectsDidChangeNotification {
|
||||
|
||||
public let notification: Notification
|
||||
public let notification: Foundation.Notification
|
||||
public let managedObjectContext: NSManagedObjectContext
|
||||
|
||||
public init?(notification: Notification) {
|
||||
public init?(notification: Foundation.Notification) {
|
||||
guard notification.name == .NSManagedObjectContextObjectsDidChange,
|
||||
let managedObjectContext = notification.object as? NSManagedObjectContext else {
|
||||
return nil
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
// ManagedObjectObserver.swift
|
||||
// CoreDataStack
|
||||
//
|
||||
// Created by sxiaojian on 2021/2/8.
|
||||
// Created by Cirno MainasuK on 2020-6-12.
|
||||
// Copyright © 2020 Dimension. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
@ -15,6 +16,26 @@ final public class ManagedObjectObserver {
|
|||
|
||||
extension ManagedObjectObserver {
|
||||
|
||||
public static func observe(context: NSManagedObjectContext) -> AnyPublisher<Changes, Error> {
|
||||
|
||||
return NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange, object: context)
|
||||
.tryMap { notification in
|
||||
guard let notification = ManagedObjectContextObjectsDidChangeNotification(notification: notification) else {
|
||||
throw Error.notManagedObjectChangeNotification
|
||||
}
|
||||
|
||||
let changeTypes = ManagedObjectObserver.changeTypes(in: notification)
|
||||
return Changes(
|
||||
changeTypes: changeTypes,
|
||||
changeNotification: notification
|
||||
)
|
||||
}
|
||||
.mapError { error -> Error in
|
||||
return (error as? Error) ?? .unknown(error)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public static func observe(object: NSManagedObject) -> AnyPublisher<Change, Error> {
|
||||
guard let context = object.managedObjectContext else {
|
||||
return Fail(error: .noManagedObjectContext).eraseToAnyPublisher()
|
||||
|
@ -41,10 +62,26 @@ extension ManagedObjectObserver {
|
|||
}
|
||||
|
||||
extension ManagedObjectObserver {
|
||||
private static func changeTypes(in notification: ManagedObjectContextObjectsDidChangeNotification) -> [ChangeType] {
|
||||
var changeTypes: [ChangeType] = []
|
||||
|
||||
let deleted = notification.deletedObjects.union(notification.invalidedObjects)
|
||||
for object in deleted {
|
||||
changeTypes.append(.delete(object))
|
||||
}
|
||||
|
||||
let updated = notification.updatedObjects.union(notification.refreshedObjects)
|
||||
for object in updated {
|
||||
changeTypes.append(.update(object))
|
||||
}
|
||||
|
||||
return changeTypes
|
||||
}
|
||||
|
||||
private static func changeType(of object: NSManagedObject, in notification: ManagedObjectContextObjectsDidChangeNotification) -> ChangeType? {
|
||||
let deleted = notification.deletedObjects.union(notification.invalidedObjects)
|
||||
if notification.invalidatedAllObjects || deleted.contains(where: { $0 === object }) {
|
||||
return .delete
|
||||
return .delete(object)
|
||||
}
|
||||
|
||||
let updated = notification.updatedObjects.union(notification.refreshedObjects)
|
||||
|
@ -57,6 +94,16 @@ extension ManagedObjectObserver {
|
|||
}
|
||||
|
||||
extension ManagedObjectObserver {
|
||||
public struct Changes {
|
||||
public let changeTypes: [ChangeType]
|
||||
public let changeNotification: ManagedObjectContextObjectsDidChangeNotification
|
||||
|
||||
init(changeTypes: [ManagedObjectObserver.ChangeType], changeNotification: ManagedObjectContextObjectsDidChangeNotification) {
|
||||
self.changeTypes = changeTypes
|
||||
self.changeNotification = changeNotification
|
||||
}
|
||||
}
|
||||
|
||||
public struct Change {
|
||||
public let changeType: ChangeType?
|
||||
public let changeNotification: ManagedObjectContextObjectsDidChangeNotification
|
||||
|
@ -65,10 +112,10 @@ extension ManagedObjectObserver {
|
|||
self.changeType = changeType
|
||||
self.changeNotification = changeNotification
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum ChangeType {
|
||||
case delete
|
||||
case delete(NSManagedObject)
|
||||
case update(NSManagedObject)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// AutoGenerateProperty.swift
|
||||
// AutoGenerateProperty
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-18.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Stencil protocol
|
||||
protocol AutoGenerateProperty { }
|
||||
|
||||
// - autoGenerateProperty
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// AutoGenerateRelationship.swift
|
||||
// AutoGenerateRelationship
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-19.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Stencil protocol
|
||||
protocol AutoGenerateRelationship { }
|
||||
|
||||
// - autoGenerateRelationship
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// AutoUpdatableObject.swift
|
||||
// AutoUpdatableObject
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-18.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Stencil protocol
|
||||
protocol AutoUpdatableObject { }
|
||||
|
||||
// - autoUpdatableObject
|
|
@ -0,0 +1,45 @@
|
|||
{% for type in types.implementing.AutoGenerateProperty %}
|
||||
// sourcery:inline:{{type.name}}.AutoGenerateProperty
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Property {
|
||||
{% for variable in type.variables|instance where
|
||||
variable|annotated:"autoGenerateProperty"
|
||||
%}
|
||||
public let {{variable.name}}: {{variable.typeName}}
|
||||
{% endfor %}
|
||||
|
||||
public init(
|
||||
{% for variable in type.variables|instance where
|
||||
variable|annotated:"autoGenerateProperty"
|
||||
%}
|
||||
{{variable.name}}: {{variable.typeName}}{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
) {
|
||||
{% for variable in type.variables|instance where
|
||||
variable|annotated:"autoGenerateProperty"
|
||||
%}
|
||||
self.{{variable.name}} = {{variable.name}}
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(property: Property) {
|
||||
{% for variable in type.variables|instance where
|
||||
variable|annotated:"autoGenerateProperty"
|
||||
%}
|
||||
self.{{variable.name}} = property.{{variable.name}}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
public func update(property: Property) {
|
||||
{% for variable in type.variables|instance where
|
||||
variable|annotated:"autoUpdatableObject" and
|
||||
variable|annotated:"autoGenerateProperty"
|
||||
%}
|
||||
update({{variable.name}}: property.{{variable.name}})
|
||||
{% endfor %}
|
||||
}
|
||||
// sourcery:end
|
||||
{% endfor %}
|
|
@ -0,0 +1,29 @@
|
|||
{% for type in types.implementing.AutoGenerateRelationship %}
|
||||
// sourcery:inline:{{type.name}}.AutoGenerateRelationship
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
public struct Relationship {
|
||||
{% for variable in type.storedVariables|annotated:"autoGenerateRelationship" %}
|
||||
public let {{variable.name}}: {{variable.typeName}}
|
||||
{% endfor %}
|
||||
|
||||
public init(
|
||||
{% for variable in type.storedVariables|annotated:"autoGenerateRelationship" %}
|
||||
{{variable.name}}: {{variable.typeName}}{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
) {
|
||||
{% for variable in type.storedVariables|annotated:"autoGenerateRelationship" %}
|
||||
self.{{variable.name}} = {{variable.name}}
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(relationship: Relationship) {
|
||||
{% for variable in type.storedVariables|annotated:"autoGenerateRelationship" %}
|
||||
self.{{variable.name}} = relationship.{{variable.name}}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
// sourcery:end
|
||||
{% endfor %}
|
|
@ -0,0 +1,16 @@
|
|||
{% for type in types.implementing.AutoUpdatableObject %}
|
||||
// sourcery:inline:{{type.name}}.AutoUpdatableObject
|
||||
|
||||
// Generated using Sourcery
|
||||
// DO NOT EDIT
|
||||
{% for variable in type.variables|instance where
|
||||
variable|annotated:"autoUpdatableObject"
|
||||
%}
|
||||
public func update({{variable.name}}: {{variable.typeName}}) {
|
||||
if self.{{variable.name}} != {{variable.name}} {
|
||||
self.{{variable.name}} = {{variable.name}}
|
||||
}
|
||||
}
|
||||
{% endfor %}
|
||||
// sourcery:end
|
||||
{% endfor %}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// ManagedObjectRecord.swift
|
||||
// ManagedObjectRecord
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-25.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
public class ManagedObjectRecord<T: Managed>: Hashable {
|
||||
|
||||
public let objectID: NSManagedObjectID
|
||||
|
||||
public init(objectID: NSManagedObjectID) {
|
||||
self.objectID = objectID
|
||||
}
|
||||
|
||||
public func object(in managedObjectContext: NSManagedObjectContext) -> T? {
|
||||
return managedObjectContext.object(with: objectID) as? T
|
||||
}
|
||||
|
||||
public static func == (lhs: ManagedObjectRecord<T>, rhs: ManagedObjectRecord<T>) -> Bool {
|
||||
return lhs.objectID == rhs.objectID
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(objectID)
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,6 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>90</string>
|
||||
<string>91</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import os.log
|
||||
import Foundation
|
||||
|
||||
let currentFileURL = URL(fileURLWithPath: "\(#file)", isDirectory: false)
|
||||
let packageRootURL = currentFileURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
|
||||
let inputDirectoryURL = packageRootURL.appendingPathComponent("input", isDirectory: true)
|
||||
let outputDirectoryURL = packageRootURL.appendingPathComponent("output", isDirectory: true)
|
||||
|
||||
// conver i18n JSON templates to strings files
|
||||
private func convert(from inputDirectoryURL: URL, to outputDirectory: URL) {
|
||||
do {
|
||||
|
@ -17,7 +12,6 @@ private func convert(from inputDirectoryURL: URL, to outputDirectory: URL) {
|
|||
for inputLanguageDirectoryURL in inputLanguageDirectoryURLs {
|
||||
let language = inputLanguageDirectoryURL.lastPathComponent
|
||||
guard let mappedLanguage = map(language: language) else { continue }
|
||||
let outputDirectoryURL = outputDirectory.appendingPathComponent(mappedLanguage + ".lproj", isDirectory: true)
|
||||
os_log("%{public}s[%{public}ld], %{public}s: process %s -> %s", ((#file as NSString).lastPathComponent), #line, #function, language, mappedLanguage)
|
||||
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(
|
||||
|
@ -29,9 +23,19 @@ private func convert(from inputDirectoryURL: URL, to outputDirectory: URL) {
|
|||
os_log("%{public}s[%{public}ld], %{public}s: process %s", ((#file as NSString).lastPathComponent), #line, #function, jsonURL.debugDescription)
|
||||
let filename = jsonURL.deletingPathExtension().lastPathComponent
|
||||
guard let (mappedFilename, keyStyle) = map(filename: filename) else { continue }
|
||||
let outputFileURL = outputDirectoryURL.appendingPathComponent(mappedFilename).appendingPathExtension("strings")
|
||||
guard let bundle = bundle(filename: filename) else { continue }
|
||||
|
||||
let outputDirectoryURL = outputDirectory
|
||||
.appendingPathComponent(bundle, isDirectory: true)
|
||||
.appendingPathComponent(mappedLanguage + ".lproj", isDirectory: true)
|
||||
|
||||
let outputFileURL = outputDirectoryURL
|
||||
.appendingPathComponent(mappedFilename)
|
||||
.appendingPathExtension("strings")
|
||||
|
||||
let strings = try process(url: jsonURL, keyStyle: keyStyle)
|
||||
try? FileManager.default.createDirectory(at: outputDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
try strings.write(to: outputFileURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +48,7 @@ private func convert(from inputDirectoryURL: URL, to outputDirectory: URL) {
|
|||
private func map(language: String) -> String? {
|
||||
switch language {
|
||||
case "ar_SA": return "ar" // Arabic (Saudi Arabia)
|
||||
case "eu_ES": return "eu-ES" // Basque
|
||||
case "ca_ES": return "ca" // Catalan
|
||||
case "zh_CN": return "zh-Hans" // Chinese Simplified
|
||||
case "nl_NL": return "nl" // Dutch
|
||||
|
@ -56,6 +61,7 @@ private func map(language: String) -> String? {
|
|||
case "gd_GB": return "gd-GB" // Scottish Gaelic
|
||||
case "es_ES": return "es" // Spanish
|
||||
case "es_AR": return "es-419" // Spanish, Argentina
|
||||
case "sv_FI": return "sv_FI" // Swedish, Finland
|
||||
case "th_TH": return "th" // Thai
|
||||
default: return nil
|
||||
}
|
||||
|
@ -69,6 +75,14 @@ private func map(filename: String) -> (filename: String, keyStyle: Parser.KeySty
|
|||
}
|
||||
}
|
||||
|
||||
private func bundle(filename: String) -> String? {
|
||||
switch filename {
|
||||
case "app": return "module"
|
||||
case "ios-infoPlist": return "main"
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func process(url: URL, keyStyle: Parser.KeyStyle) throws -> String {
|
||||
do {
|
||||
let data = try Data(contentsOf: url)
|
||||
|
@ -115,9 +129,16 @@ private func move(from inputDirectoryURL: URL, to outputDirectoryURL: URL, pathE
|
|||
}
|
||||
}
|
||||
|
||||
// i18n from "input" to "output"
|
||||
|
||||
let currentFileURL = URL(fileURLWithPath: "\(#file)", isDirectory: false)
|
||||
let packageRootURL = currentFileURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
|
||||
|
||||
let inputDirectoryURL = packageRootURL.appendingPathComponent("input", isDirectory: true)
|
||||
let outputDirectoryURL = packageRootURL.appendingPathComponent("output", isDirectory: true)
|
||||
convert(from: inputDirectoryURL, to: outputDirectoryURL)
|
||||
move(from: inputDirectoryURL, to: outputDirectoryURL, pathExtension: "stringsdict")
|
||||
|
||||
let moduleDirectoryURL = outputDirectoryURL.appendingPathComponent("module", isDirectory: true)
|
||||
move(from: inputDirectoryURL, to: moduleDirectoryURL, pathExtension: "stringsdict")
|
||||
|
||||
// i18n from "Intents/input" to "Intents/output"
|
||||
let intentsDirectoryURL = packageRootURL.appendingPathComponent("Intents", isDirectory: true)
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
"message": "Please enable the photo library access permission to save the photo."
|
||||
},
|
||||
"delete_post": {
|
||||
"title": "Are you sure you want to delete this post?",
|
||||
"delete": "Delete"
|
||||
"title": "Delete Post",
|
||||
"message": "Are you sure you want to delete this post?"
|
||||
},
|
||||
"clean_cache": {
|
||||
"title": "Clean Cache",
|
||||
|
@ -412,14 +412,24 @@
|
|||
"segmented_control": {
|
||||
"posts": "Posts",
|
||||
"replies": "Replies",
|
||||
"media": "Media"
|
||||
"posts_and_replies": "Posts and Replies",
|
||||
"media": "Media",
|
||||
"about": "About"
|
||||
},
|
||||
"relationship_action_alert": {
|
||||
"confirm_mute_user": {
|
||||
"title": "Mute Account",
|
||||
"message": "Confirm to mute %s"
|
||||
},
|
||||
"confirm_unmute_user": {
|
||||
"title": "Unmute Account",
|
||||
"message": "Confirm to unmute %s"
|
||||
},
|
||||
"confirm_unblock_usre": {
|
||||
"confirm_block_user": {
|
||||
"title": "Block Account",
|
||||
"message": "Confirm to block %s"
|
||||
},
|
||||
"confirm_unblock_user": {
|
||||
"title": "Unblock Account",
|
||||
"message": "Confirm to unblock %s"
|
||||
}
|
||||
|
@ -472,12 +482,14 @@
|
|||
"Everything": "Everything",
|
||||
"Mentions": "Mentions"
|
||||
},
|
||||
"user_followed_you": "%s followed you",
|
||||
"user_favorited your post": "%s favorited your post",
|
||||
"user_reblogged_your_post": "%s reblogged your post",
|
||||
"user_mentioned_you": "%s mentioned you",
|
||||
"user_requested_to_follow_you": "%s requested to follow you",
|
||||
"user_your_poll_has_ended": "%s Your poll has ended",
|
||||
"notification_description": {
|
||||
"followed_you": "followd you",
|
||||
"favorited_your_post": "favorited your post",
|
||||
"reblogged_your_post": "reblogged your post",
|
||||
"mentioned_you": "mentioned you",
|
||||
"request_to_follow_you": "request to follow you",
|
||||
"poll_has_ended": "poll has ended"
|
||||
},
|
||||
"keyobard": {
|
||||
"show_everything": "Show Everything",
|
||||
"show_mentions": "Show Mentions"
|
||||
|
@ -564,4 +576,4 @@
|
|||
"accessibility_hint": "Double tap to dismiss this wizard"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,108 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DB427DD125BAA00100D1B89D"
|
||||
BuildableName = "Mastodon.app"
|
||||
BlueprintName = "Mastodon"
|
||||
ReferencedContainer = "container:Mastodon.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "ASDK - Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DB427DE725BAA00100D1B89D"
|
||||
BuildableName = "MastodonTests.xctest"
|
||||
BlueprintName = "MastodonTests"
|
||||
ReferencedContainer = "container:Mastodon.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DB427DF225BAA00100D1B89D"
|
||||
BuildableName = "MastodonUITests.xctest"
|
||||
BlueprintName = "MastodonUITests"
|
||||
ReferencedContainer = "container:Mastodon.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DB89B9F525C10FD0008580ED"
|
||||
BuildableName = "CoreDataStackTests.xctest"
|
||||
BlueprintName = "CoreDataStackTests"
|
||||
ReferencedContainer = "container:Mastodon.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "ASDK - Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DB427DD125BAA00100D1B89D"
|
||||
BuildableName = "Mastodon.app"
|
||||
BlueprintName = "Mastodon"
|
||||
ReferencedContainer = "container:Mastodon.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "ASDK - Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DB427DD125BAA00100D1B89D"
|
||||
BuildableName = "Mastodon.app"
|
||||
BlueprintName = "Mastodon"
|
||||
ReferencedContainer = "container:Mastodon.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "ASDK - Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "ASDK - Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -7,18 +7,13 @@
|
|||
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>26</integer>
|
||||
<integer>28</integer>
|
||||
</dict>
|
||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>27</integer>
|
||||
</dict>
|
||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
|
@ -102,7 +97,7 @@
|
|||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>25</integer>
|
||||
<integer>26</integer>
|
||||
</dict>
|
||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -117,15 +112,36 @@
|
|||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>24</integer>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict/>
|
||||
<dict>
|
||||
<key>DB427DD125BAA00100D1B89D</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>DB427DE725BAA00100D1B89D</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>DB427DF225BAA00100D1B89D</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>DB89B9F525C10FD0008580ED</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -55,15 +55,6 @@
|
|||
"version": "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "FLAnimatedImage",
|
||||
"repositoryURL": "https://github.com/Flipboard/FLAnimatedImage",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "e7f9fd4681ae41bf6f3056db08af4f401d61da52",
|
||||
"version": "1.0.16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "FPSIndicator",
|
||||
"repositoryURL": "https://github.com/MainasuK/FPSIndicator.git",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
sources:
|
||||
- .
|
||||
- ../MastodonSDK/Sources
|
||||
templates:
|
||||
- ./Template
|
||||
output:
|
||||
Generated
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import UIKit
|
||||
import SafariServices
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
final class SafariActivity: UIActivity {
|
||||
|
||||
|
@ -55,8 +57,10 @@ final class SafariActivity: UIActivity {
|
|||
return
|
||||
}
|
||||
|
||||
sceneCoordinator?.present(scene: .safari(url: url as URL), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||
activityDidFinish(true)
|
||||
Task {
|
||||
await sceneCoordinator?.present(scene: .safari(url: url as URL), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||
activityDidFinish(true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// ShareActivityProvider.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-1-25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol ShareActivityProvider {
|
||||
var activities: [Any] { get }
|
||||
var applicationActivities: [UIActivity] { get }
|
||||
}
|
|
@ -10,6 +10,8 @@ import SafariServices
|
|||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import PanModal
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
final public class SceneCoordinator {
|
||||
|
||||
|
@ -194,10 +196,6 @@ extension SceneCoordinator {
|
|||
case alertController(alertController: UIAlertController)
|
||||
case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?)
|
||||
|
||||
#if DEBUG
|
||||
case publicTimeline
|
||||
#endif
|
||||
|
||||
var isOnboarding: Bool {
|
||||
switch self {
|
||||
case .welcome,
|
||||
|
@ -211,7 +209,7 @@ extension SceneCoordinator {
|
|||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end enum Scene { }
|
||||
}
|
||||
|
||||
extension SceneCoordinator {
|
||||
|
@ -266,6 +264,7 @@ extension SceneCoordinator {
|
|||
}
|
||||
|
||||
@discardableResult
|
||||
@MainActor
|
||||
func present(scene: Scene, from sender: UIViewController?, transition: Transition) -> UIViewController? {
|
||||
guard let viewController = get(scene: scene) else {
|
||||
return nil
|
||||
|
@ -481,12 +480,6 @@ private extension SceneCoordinator {
|
|||
let _viewController = ReportViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
#if DEBUG
|
||||
case .publicTimeline:
|
||||
let _viewController = PublicTimelineViewController()
|
||||
_viewController.viewModel = PublicTimelineViewModel(context: appContext)
|
||||
viewController = _viewController
|
||||
#endif
|
||||
}
|
||||
|
||||
setupDependency(for: viewController as? NeedsDependency)
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
//
|
||||
// PickServerCategoriesCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/23.
|
||||
//
|
||||
|
||||
//import os.log
|
||||
//import UIKit
|
||||
//import MastodonSDK
|
||||
//
|
||||
//protocol PickServerCategoriesCellDelegate: AnyObject {
|
||||
// func pickServerCategoriesCell(_ cell: PickServerCategoriesCell, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
//}
|
||||
//
|
||||
//final class PickServerCategoriesCell: UITableViewCell {
|
||||
//
|
||||
// weak var delegate: PickServerCategoriesCellDelegate?
|
||||
//
|
||||
// var diffableDataSource: UICollectionViewDiffableDataSource<CategoryPickerSection, CategoryPickerItem>?
|
||||
//
|
||||
// let metricView = UIView()
|
||||
//
|
||||
// let collectionView: UICollectionView = {
|
||||
// let flowLayout = UICollectionViewFlowLayout()
|
||||
// flowLayout.scrollDirection = .horizontal
|
||||
// let view = ControlContainableCollectionView(frame: .zero, collectionViewLayout: flowLayout)
|
||||
// view.register(PickServerCategoryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: PickServerCategoryCollectionViewCell.self))
|
||||
// view.backgroundColor = .clear
|
||||
// view.showsHorizontalScrollIndicator = false
|
||||
// view.showsVerticalScrollIndicator = false
|
||||
// view.layer.masksToBounds = false
|
||||
// view.translatesAutoresizingMaskIntoConstraints = false
|
||||
// return view
|
||||
// }()
|
||||
//
|
||||
// override func prepareForReuse() {
|
||||
// super.prepareForReuse()
|
||||
//
|
||||
// delegate = nil
|
||||
// }
|
||||
//
|
||||
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
// super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
// required init?(coder: NSCoder) {
|
||||
// super.init(coder: coder)
|
||||
// _init()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension PickServerCategoriesCell {
|
||||
//
|
||||
// private func _init() {
|
||||
// selectionStyle = .none
|
||||
// backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
||||
// configureMargin()
|
||||
//
|
||||
// metricView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// contentView.addSubview(metricView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// metricView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||
// metricView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
||||
// metricView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
// metricView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
// metricView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
|
||||
// ])
|
||||
//
|
||||
// contentView.addSubview(collectionView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
// collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
// collectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
|
||||
// contentView.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20),
|
||||
// collectionView.heightAnchor.constraint(equalToConstant: 80).priority(.defaultHigh),
|
||||
// ])
|
||||
//
|
||||
// collectionView.delegate = self
|
||||
// }
|
||||
//
|
||||
// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
// super.traitCollectionDidChange(previousTraitCollection)
|
||||
//
|
||||
// configureMargin()
|
||||
// }
|
||||
//
|
||||
// override func layoutSubviews() {
|
||||
// super.layoutSubviews()
|
||||
//
|
||||
// collectionView.collectionViewLayout.invalidateLayout()
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//extension PickServerCategoriesCell {
|
||||
// private func configureMargin() {
|
||||
// switch traitCollection.horizontalSizeClass {
|
||||
// case .regular:
|
||||
// let margin = MastodonPickServerViewController.viewEdgeMargin
|
||||
// contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
|
||||
// default:
|
||||
// contentView.layoutMargins = .zero
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// MARK: - UICollectionViewDelegateFlowLayout
|
||||
//extension PickServerCategoriesCell: UICollectionViewDelegateFlowLayout {
|
||||
//
|
||||
// func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: indexPath: %s", ((#file as NSString).lastPathComponent), #line, #function, indexPath.debugDescription)
|
||||
// collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
|
||||
// delegate?.pickServerCategoriesCell(self, collectionView: collectionView, didSelectItemAt: indexPath)
|
||||
// }
|
||||
//
|
||||
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||
// layoutIfNeeded()
|
||||
// return UIEdgeInsets(top: 0, left: metricView.frame.minX - collectionView.frame.minX, bottom: 0, right: collectionView.frame.maxX - metricView.frame.maxX)
|
||||
// }
|
||||
//
|
||||
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
||||
// return 16
|
||||
// }
|
||||
//
|
||||
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
// return CGSize(width: 60, height: 80)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//extension PickServerCategoriesCell {
|
||||
//
|
||||
// override func accessibilityElementCount() -> Int {
|
||||
// guard let diffableDataSource = diffableDataSource else { return 0 }
|
||||
// return diffableDataSource.snapshot().itemIdentifiers.count
|
||||
// }
|
||||
//
|
||||
// override func accessibilityElement(at index: Int) -> Any? {
|
||||
// guard let item = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return nil }
|
||||
// return item
|
||||
// }
|
||||
//
|
||||
//}
|
|
@ -1,171 +0,0 @@
|
|||
//
|
||||
// PickServerSearchCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
//protocol PickServerSearchCellDelegate: AnyObject {
|
||||
// func pickServerSearchCell(_ cell: PickServerSearchCell, searchTextDidChange searchText: String?)
|
||||
//}
|
||||
//
|
||||
//class PickServerSearchCell: UITableViewCell {
|
||||
//
|
||||
// weak var delegate: PickServerSearchCellDelegate?
|
||||
//
|
||||
// private var bgView: UIView = {
|
||||
// let view = UIView()
|
||||
// view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color
|
||||
// view.translatesAutoresizingMaskIntoConstraints = false
|
||||
// view.layer.maskedCorners = [
|
||||
// .layerMinXMinYCorner,
|
||||
// .layerMaxXMinYCorner
|
||||
// ]
|
||||
// view.layer.cornerCurve = .continuous
|
||||
// view.layer.cornerRadius = MastodonPickServerAppearance.tableViewCornerRadius
|
||||
// return view
|
||||
// }()
|
||||
//
|
||||
// private var textFieldBgView: UIView = {
|
||||
// let view = UIView()
|
||||
// view.backgroundColor = Asset.Colors.TextField.background.color
|
||||
// view.translatesAutoresizingMaskIntoConstraints = false
|
||||
// view.layer.masksToBounds = true
|
||||
// view.layer.cornerRadius = 6
|
||||
// view.layer.cornerCurve = .continuous
|
||||
// return view
|
||||
// }()
|
||||
//
|
||||
// let searchTextField: UITextField = {
|
||||
// let textField = UITextField()
|
||||
// textField.translatesAutoresizingMaskIntoConstraints = false
|
||||
// textField.leftView = {
|
||||
// let imageView = UIImageView(
|
||||
// image: UIImage(
|
||||
// systemName: "magnifyingglass",
|
||||
// withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .regular)
|
||||
// )
|
||||
// )
|
||||
// imageView.tintColor = Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)
|
||||
//
|
||||
// let containerView = UIView()
|
||||
// imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// containerView.addSubview(imageView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
|
||||
// imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
|
||||
// imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||
// ])
|
||||
//
|
||||
// let paddingView = UIView()
|
||||
// paddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// containerView.addSubview(paddingView)
|
||||
// NSLayoutConstraint.activate([
|
||||
// paddingView.topAnchor.constraint(equalTo: containerView.topAnchor),
|
||||
// paddingView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor),
|
||||
// paddingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
||||
// paddingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||
// paddingView.widthAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
|
||||
// ])
|
||||
// return containerView
|
||||
// }()
|
||||
// textField.leftViewMode = .always
|
||||
// textField.font = .systemFont(ofSize: 15, weight: .regular)
|
||||
// textField.tintColor = Asset.Colors.Label.primary.color
|
||||
// textField.textColor = Asset.Colors.Label.primary.color
|
||||
// textField.adjustsFontForContentSizeCategory = true
|
||||
// textField.attributedPlaceholder =
|
||||
// NSAttributedString(string: L10n.Scene.ServerPicker.Input.placeholder,
|
||||
// attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular),
|
||||
// .foregroundColor: Asset.Colors.Label.secondary.color.withAlphaComponent(0.6)])
|
||||
// textField.clearButtonMode = .whileEditing
|
||||
// textField.autocapitalizationType = .none
|
||||
// textField.autocorrectionType = .no
|
||||
// textField.returnKeyType = .done
|
||||
// textField.keyboardType = .URL
|
||||
// return textField
|
||||
// }()
|
||||
//
|
||||
// override func prepareForReuse() {
|
||||
// super.prepareForReuse()
|
||||
//
|
||||
// delegate = nil
|
||||
// }
|
||||
//
|
||||
// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
// super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
// _init()
|
||||
// }
|
||||
//
|
||||
// required init?(coder: NSCoder) {
|
||||
// super.init(coder: coder)
|
||||
// _init()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension PickServerSearchCell {
|
||||
// private func _init() {
|
||||
// selectionStyle = .none
|
||||
// backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color
|
||||
// configureMargin()
|
||||
//
|
||||
// searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
|
||||
// searchTextField.delegate = self
|
||||
//
|
||||
// contentView.addSubview(bgView)
|
||||
// contentView.addSubview(textFieldBgView)
|
||||
// contentView.addSubview(searchTextField)
|
||||
//
|
||||
// NSLayoutConstraint.activate([
|
||||
// bgView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||
// bgView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
// bgView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
||||
// bgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
//
|
||||
// textFieldBgView.leadingAnchor.constraint(equalTo: bgView.leadingAnchor, constant: 14),
|
||||
// textFieldBgView.topAnchor.constraint(equalTo: bgView.topAnchor, constant: 12),
|
||||
// bgView.trailingAnchor.constraint(equalTo: textFieldBgView.trailingAnchor, constant: 14),
|
||||
// bgView.bottomAnchor.constraint(equalTo: textFieldBgView.bottomAnchor, constant: 13),
|
||||
//
|
||||
// searchTextField.leadingAnchor.constraint(equalTo: textFieldBgView.leadingAnchor, constant: 11),
|
||||
// searchTextField.topAnchor.constraint(equalTo: textFieldBgView.topAnchor, constant: 4),
|
||||
// textFieldBgView.trailingAnchor.constraint(equalTo: searchTextField.trailingAnchor, constant: 11),
|
||||
// textFieldBgView.bottomAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 4),
|
||||
// ])
|
||||
// }
|
||||
//
|
||||
// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
// super.traitCollectionDidChange(previousTraitCollection)
|
||||
//
|
||||
// configureMargin()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension PickServerSearchCell {
|
||||
// private func configureMargin() {
|
||||
// switch traitCollection.horizontalSizeClass {
|
||||
// case .regular:
|
||||
// let margin = MastodonPickServerViewController.viewEdgeMargin
|
||||
// contentView.layoutMargins = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
|
||||
// default:
|
||||
// contentView.layoutMargins = .zero
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension PickServerSearchCell {
|
||||
// @objc private func textFieldDidChange(_ textField: UITextField) {
|
||||
// delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// MARK: - UITextFieldDelegate
|
||||
//extension PickServerSearchCell: UITextFieldDelegate {
|
||||
//
|
||||
// func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
// textField.resignFirstResponder()
|
||||
// return false
|
||||
// }
|
||||
//}
|
|
@ -8,6 +8,8 @@
|
|||
import UIKit
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum AutoCompleteSection: Equatable, Hashable {
|
||||
case main
|
||||
|
@ -80,7 +82,7 @@ extension AutoCompleteSection {
|
|||
}
|
||||
cell.subtitleLabel.text = "@" + account.acct
|
||||
cell.avatarImageView.isHidden = false
|
||||
cell.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: URL(string: account.avatar)))
|
||||
cell.avatarImageView.configure(configuration: .init(url: URL(string: account.avatar)))
|
||||
}
|
||||
|
||||
private static func configureEmoji(cell: AutoCompleteTableViewCell, emoji: Mastodon.Entity.Emoji, isFirst: Bool) {
|
||||
|
@ -90,7 +92,7 @@ extension AutoCompleteSection {
|
|||
// cell.subtitleLabel.text = isFirst ? L10n.Scene.Compose.AutoComplete.spaceToAdd : " "
|
||||
cell.subtitleLabel.text = " "
|
||||
cell.avatarImageView.isHidden = false
|
||||
cell.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: URL(string: emoji.url)))
|
||||
cell.avatarImageView.configure(configuration: .init(url: URL(string: emoji.url)))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@ import Foundation
|
|||
import Combine
|
||||
import CoreData
|
||||
import MastodonMeta
|
||||
import CoreDataStack
|
||||
|
||||
/// Note: update Equatable when change case
|
||||
enum ComposeStatusItem {
|
||||
case replyTo(statusObjectID: NSManagedObjectID)
|
||||
case input(replyToStatusObjectID: NSManagedObjectID?, attribute: ComposeStatusAttribute)
|
||||
case replyTo(record: ManagedObjectRecord<Status>)
|
||||
case input(replyTo: ManagedObjectRecord<Status>?, attribute: ComposeStatusAttribute)
|
||||
case attachment(attachmentAttribute: ComposeStatusAttachmentAttribute)
|
||||
case pollOption(pollOptionAttributes: [ComposeStatusPollItem.PollOptionAttribute], pollExpiresOptionAttribute: ComposeStatusPollItem.PollExpiresOptionAttribute)
|
||||
}
|
||||
|
@ -21,26 +22,21 @@ enum ComposeStatusItem {
|
|||
extension ComposeStatusItem: Hashable { }
|
||||
|
||||
extension ComposeStatusItem {
|
||||
final class ComposeStatusAttribute: Equatable, Hashable {
|
||||
final class ComposeStatusAttribute: Hashable {
|
||||
private let id = UUID()
|
||||
|
||||
let avatarURL = CurrentValueSubject<URL?, Never>(nil)
|
||||
let displayName = CurrentValueSubject<String?, Never>(nil)
|
||||
let emojiMeta = CurrentValueSubject<MastodonContent.Emojis, Never>([:])
|
||||
let username = CurrentValueSubject<String?, Never>(nil)
|
||||
let composeContent = CurrentValueSubject<String?, Never>(nil)
|
||||
|
||||
let isContentWarningComposing = CurrentValueSubject<Bool, Never>(false)
|
||||
let contentWarningContent = CurrentValueSubject<String, Never>("")
|
||||
@Published var author: ManagedObjectRecord<MastodonUser>?
|
||||
|
||||
@Published var composeContent: String?
|
||||
|
||||
@Published var isContentWarningComposing = false
|
||||
@Published var contentWarningContent = ""
|
||||
|
||||
static func == (lhs: ComposeStatusAttribute, rhs: ComposeStatusAttribute) -> Bool {
|
||||
return lhs.avatarURL.value == rhs.avatarURL.value &&
|
||||
lhs.displayName.value == rhs.displayName.value &&
|
||||
lhs.emojiMeta.value == rhs.emojiMeta.value &&
|
||||
lhs.username.value == rhs.username.value &&
|
||||
lhs.composeContent.value == rhs.composeContent.value &&
|
||||
lhs.isContentWarningComposing.value == rhs.isContentWarningComposing.value &&
|
||||
lhs.contentWarningContent.value == rhs.contentWarningContent.value
|
||||
return lhs.author == rhs.author
|
||||
&& lhs.composeContent == rhs.composeContent
|
||||
&& lhs.isContentWarningComposing == rhs.isContentWarningComposing
|
||||
&& lhs.contentWarningContent == rhs.contentWarningContent
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum ComposeStatusPollItem {
|
||||
case pollOption(attribute: PollOptionAttribute)
|
||||
|
|
|
@ -14,7 +14,7 @@ import MastodonMeta
|
|||
import AlamofireImage
|
||||
|
||||
enum ComposeStatusSection: Equatable, Hashable {
|
||||
case repliedTo
|
||||
case replyTo
|
||||
case status
|
||||
case attachment
|
||||
case poll
|
||||
|
@ -24,43 +24,44 @@ extension ComposeStatusSection {
|
|||
enum ComposeKind {
|
||||
case post
|
||||
case hashtag(hashtag: String)
|
||||
case mention(mastodonUserObjectID: NSManagedObjectID)
|
||||
case reply(repliedToStatusObjectID: NSManagedObjectID)
|
||||
case mention(user: ManagedObjectRecord<MastodonUser>)
|
||||
case reply(status: ManagedObjectRecord<Status>)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeStatusSection {
|
||||
|
||||
static func configureStatusContent(
|
||||
static func configure(
|
||||
cell: ComposeStatusContentTableViewCell,
|
||||
attribute: ComposeStatusItem.ComposeStatusAttribute
|
||||
) {
|
||||
// set avatar
|
||||
attribute.avatarURL
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { avatarURL in
|
||||
cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: avatarURL))
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
// set display name and username
|
||||
Publishers.CombineLatest3(
|
||||
attribute.displayName,
|
||||
attribute.emojiMeta,
|
||||
attribute.username
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { displayName, emojiMeta, username in
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: displayName ?? " ", emojis: emojiMeta)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.statusView.nameLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let metaContent = PlaintextMetaContent(string: " ")
|
||||
cell.statusView.nameLabel.configure(content: metaContent)
|
||||
}
|
||||
cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " "
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
// cell.prepa
|
||||
// // set avatar
|
||||
// attribute.avatarURL
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { avatarURL in
|
||||
// cell.statusView.configure(with: AvatarConfigurableViewConfiguration(avatarImageURL: avatarURL))
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
// // set display name and username
|
||||
// Publishers.CombineLatest3(
|
||||
// attribute.displayName,
|
||||
// attribute.emojiMeta,
|
||||
// attribute.username
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { displayName, emojiMeta, username in
|
||||
// do {
|
||||
// let mastodonContent = MastodonContent(content: displayName ?? " ", emojis: emojiMeta)
|
||||
// let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
// cell.statusView.nameLabel.configure(content: metaContent)
|
||||
// } catch {
|
||||
// let metaContent = PlaintextMetaContent(string: " ")
|
||||
// cell.statusView.nameLabel.configure(content: metaContent)
|
||||
// }
|
||||
// cell.statusView.usernameLabel.text = username.flatMap { "@" + $0 } ?? " "
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// FeedFetchedResultsController.swift
|
||||
// FeedFetchedResultsController
|
||||
//
|
||||
// Created by Cirno MainasuK on 2021-8-19.
|
||||
// Copyright © 2021 Twidere. All rights reserved.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
final public class FeedFetchedResultsController: NSObject {
|
||||
|
||||
public let logger = Logger(subsystem: "FeedFetchedResultsController", category: "DB")
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
public let fetchedResultsController: NSFetchedResultsController<Feed>
|
||||
|
||||
// input
|
||||
@Published public var predicate = Feed.predicate(kind: .none, acct: .none)
|
||||
|
||||
// output
|
||||
private let _objectIDs = PassthroughSubject<[NSManagedObjectID], Never>()
|
||||
@Published public var records: [ManagedObjectRecord<Feed>] = []
|
||||
|
||||
public init(managedObjectContext: NSManagedObjectContext) {
|
||||
self.fetchedResultsController = {
|
||||
let fetchRequest = Feed.sortedFetchRequest
|
||||
// make sure initial query return empty results
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
fetchRequest.shouldRefreshRefetchedObjects = true
|
||||
fetchRequest.fetchBatchSize = 15
|
||||
let controller = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: managedObjectContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
|
||||
return controller
|
||||
}()
|
||||
super.init()
|
||||
|
||||
// debounce output to prevent UI update issues
|
||||
_objectIDs
|
||||
.throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true)
|
||||
.map { objectIDs in objectIDs.map { ManagedObjectRecord(objectID: $0) } }
|
||||
.assign(to: &$records)
|
||||
|
||||
fetchedResultsController.delegate = self
|
||||
|
||||
$predicate
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] predicate in
|
||||
guard let self = self else { return }
|
||||
self.fetchedResultsController.fetchRequest.predicate = predicate
|
||||
do {
|
||||
try self.fetchedResultsController.performFetch()
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
deinit {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension FeedFetchedResultsController: NSFetchedResultsControllerDelegate {
|
||||
public func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference
|
||||
) {
|
||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
let snapshot = snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
|
||||
self._objectIDs.send(snapshot.itemIdentifiers)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,8 +21,9 @@ final class SearchHistoryFetchedResultController: NSObject {
|
|||
let userID = CurrentValueSubject<Mastodon.Entity.Status.ID?, Never>(nil)
|
||||
|
||||
// output
|
||||
let objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
|
||||
let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
@Published var records: [ManagedObjectRecord<SearchHistory>] = []
|
||||
|
||||
init(managedObjectContext: NSManagedObjectContext) {
|
||||
self.fetchedResultsController = {
|
||||
let fetchRequest = SearchHistory.sortedFetchRequest
|
||||
|
@ -38,12 +39,18 @@ final class SearchHistoryFetchedResultController: NSObject {
|
|||
return controller
|
||||
}()
|
||||
super.init()
|
||||
|
||||
// debounce output to prevent UI update issues
|
||||
_objectIDs
|
||||
.throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true)
|
||||
.map { objectIDs in objectIDs.map { ManagedObjectRecord(objectID: $0) } }
|
||||
.assign(to: &$records)
|
||||
|
||||
fetchedResultsController.delegate = self
|
||||
|
||||
Publishers.CombineLatest(
|
||||
self.domain.removeDuplicates(),
|
||||
self.userID.removeDuplicates()
|
||||
self.domain,
|
||||
self.userID
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] domain, userID in
|
||||
|
@ -67,6 +74,6 @@ extension SearchHistoryFetchedResultController: NSFetchedResultsControllerDelega
|
|||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
|
||||
let objects = fetchedResultsController.fetchedObjects ?? []
|
||||
self.objectIDs.value = objects.map { $0.objectID }
|
||||
self._objectIDs.value = objects.map { $0.objectID }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ final class StatusFetchedResultsController: NSObject {
|
|||
let statusIDs = CurrentValueSubject<[Mastodon.Entity.Status.ID], Never>([])
|
||||
|
||||
// output
|
||||
let objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
@Published var records: [ManagedObjectRecord<Status>] = []
|
||||
|
||||
init(managedObjectContext: NSManagedObjectContext, domain: String?, additionalTweetPredicate: NSPredicate?) {
|
||||
self.domain.value = domain ?? ""
|
||||
|
@ -43,11 +44,17 @@ final class StatusFetchedResultsController: NSObject {
|
|||
}()
|
||||
super.init()
|
||||
|
||||
// debounce output to prevent UI update issues
|
||||
_objectIDs
|
||||
.throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true)
|
||||
.map { objectIDs in objectIDs.map { ManagedObjectRecord(objectID: $0) } }
|
||||
.assign(to: &$records)
|
||||
|
||||
fetchedResultsController.delegate = self
|
||||
|
||||
Publishers.CombineLatest(
|
||||
self.domain.removeDuplicates().eraseToAnyPublisher(),
|
||||
self.statusIDs.removeDuplicates().eraseToAnyPublisher()
|
||||
self.domain.removeDuplicates(),
|
||||
self.statusIDs.removeDuplicates()
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] domain, ids in
|
||||
|
@ -68,6 +75,18 @@ final class StatusFetchedResultsController: NSObject {
|
|||
|
||||
}
|
||||
|
||||
extension StatusFetchedResultsController {
|
||||
|
||||
public func append(statusIDs: [Mastodon.Entity.Status.ID]) {
|
||||
var result = self.statusIDs.value
|
||||
for statusID in statusIDs where !result.contains(statusID) {
|
||||
result.append(statusID)
|
||||
}
|
||||
self.statusIDs.value = result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension StatusFetchedResultsController: NSFetchedResultsControllerDelegate {
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||
|
@ -82,6 +101,6 @@ extension StatusFetchedResultsController: NSFetchedResultsControllerDelegate {
|
|||
}
|
||||
.sorted { $0.0 < $1.0 }
|
||||
.map { $0.1.objectID }
|
||||
self.objectIDs.value = items
|
||||
self._objectIDs.value = items
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ final class UserFetchedResultsController: NSObject {
|
|||
let userIDs = CurrentValueSubject<[Mastodon.Entity.Account.ID], Never>([])
|
||||
|
||||
// output
|
||||
let objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([])
|
||||
@Published var records: [ManagedObjectRecord<MastodonUser>] = []
|
||||
|
||||
init(managedObjectContext: NSManagedObjectContext, domain: String?, additionalTweetPredicate: NSPredicate?) {
|
||||
self.domain.value = domain ?? ""
|
||||
|
@ -42,12 +43,18 @@ final class UserFetchedResultsController: NSObject {
|
|||
return controller
|
||||
}()
|
||||
super.init()
|
||||
|
||||
// debounce output to prevent UI update issues
|
||||
_objectIDs
|
||||
.throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true)
|
||||
.map { objectIDs in objectIDs.map { ManagedObjectRecord(objectID: $0) } }
|
||||
.assign(to: &$records)
|
||||
|
||||
fetchedResultsController.delegate = self
|
||||
|
||||
Publishers.CombineLatest(
|
||||
self.domain.removeDuplicates().eraseToAnyPublisher(),
|
||||
self.userIDs.removeDuplicates().eraseToAnyPublisher()
|
||||
self.domain.removeDuplicates(),
|
||||
self.userIDs.removeDuplicates()
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] domain, ids in
|
||||
|
@ -68,6 +75,18 @@ final class UserFetchedResultsController: NSObject {
|
|||
|
||||
}
|
||||
|
||||
extension UserFetchedResultsController {
|
||||
|
||||
public func append(userIDs: [Mastodon.Entity.Account.ID]) {
|
||||
var result = self.userIDs.value
|
||||
for userID in userIDs where !result.contains(userID) {
|
||||
result.append(userID)
|
||||
}
|
||||
self.userIDs.value = result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension UserFetchedResultsController: NSFetchedResultsControllerDelegate {
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||
|
@ -82,6 +101,6 @@ extension UserFetchedResultsController: NSFetchedResultsControllerDelegate {
|
|||
}
|
||||
.sorted { $0.0 < $1.0 }
|
||||
.map { $0.1.objectID }
|
||||
self.objectIDs.value = items
|
||||
self._objectIDs.value = items
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,50 +7,10 @@
|
|||
|
||||
import CoreData
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
|
||||
enum NotificationItem {
|
||||
case notification(objectID: NSManagedObjectID, attribute: Item.StatusAttribute)
|
||||
case notificationStatus(objectID: NSManagedObjectID, attribute: Item.StatusAttribute) // display notification status without card wrapper
|
||||
enum NotificationItem: Hashable {
|
||||
case feed(record: ManagedObjectRecord<Feed>)
|
||||
case feedLoader(record: ManagedObjectRecord<Feed>)
|
||||
case bottomLoader
|
||||
}
|
||||
|
||||
extension NotificationItem: Equatable {
|
||||
static func == (lhs: NotificationItem, rhs: NotificationItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.notification(let idLeft, _), .notification(let idRight, _)):
|
||||
return idLeft == idRight
|
||||
case (.notificationStatus(let idLeft, _), .notificationStatus(let idRight, _)):
|
||||
return idLeft == idRight
|
||||
case (.bottomLoader, .bottomLoader):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .notification(let id, _):
|
||||
hasher.combine(id)
|
||||
case .notificationStatus(let id, _):
|
||||
hasher.combine(id)
|
||||
case .bottomLoader:
|
||||
hasher.combine(String(describing: NotificationItem.bottomLoader.self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationItem {
|
||||
var statusObjectItem: StatusObjectItem? {
|
||||
switch self {
|
||||
case .notification(let objectID, _):
|
||||
return .mastodonNotification(objectID: objectID)
|
||||
case .notificationStatus(let objectID, _):
|
||||
return .mastodonNotification(objectID: objectID)
|
||||
case .bottomLoader:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,234 +13,290 @@ import MastodonSDK
|
|||
import UIKit
|
||||
import MetaTextKit
|
||||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum NotificationSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension NotificationSection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: NeedsDependency,
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
delegate: NotificationTableViewCellDelegate,
|
||||
statusTableViewCellDelegate: StatusTableViewCellDelegate
|
||||
|
||||
struct Configuration {
|
||||
weak var notificationTableViewCellDelegate: NotificationTableViewCellDelegate?
|
||||
}
|
||||
|
||||
static func diffableDataSource(
|
||||
tableView: UITableView,
|
||||
context: AppContext,
|
||||
configuration: Configuration
|
||||
) -> UITableViewDiffableDataSource<NotificationSection, NotificationItem> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) {
|
||||
[weak delegate, weak dependency]
|
||||
(tableView, indexPath, notificationItem) -> UITableViewCell? in
|
||||
guard let dependency = dependency else { return nil }
|
||||
switch notificationItem {
|
||||
case .notification(let objectID, let attribute):
|
||||
guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification,
|
||||
!notification.isDeleted
|
||||
else { return UITableViewCell() }
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell
|
||||
configure(
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
notification: notification,
|
||||
dependency: dependency,
|
||||
attribute: attribute
|
||||
)
|
||||
cell.delegate = delegate
|
||||
cell.isAccessibilityElement = true
|
||||
NotificationSection.configureStatusAccessibilityLabel(cell: cell)
|
||||
tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self))
|
||||
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||
|
||||
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
switch item {
|
||||
case .feed(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let feed = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: NotificationTableViewCell.ViewModel(value: .feed(feed)),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
return cell
|
||||
|
||||
case .notificationStatus(objectID: let objectID, attribute: let attribute):
|
||||
guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification,
|
||||
!notification.isDeleted,
|
||||
let status = notification.status,
|
||||
let requestUserID = dependency.context.authenticationService.activeMastodonAuthenticationBox.value?.userID
|
||||
else { return UITableViewCell() }
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
|
||||
// configure cell
|
||||
StatusSection.configureStatusTableViewCell(
|
||||
cell: cell,
|
||||
tableView: tableView,
|
||||
timelineContext: .notifications,
|
||||
dependency: dependency,
|
||||
readableLayoutFrame: tableView.readableContentGuide.layoutFrame,
|
||||
status: status,
|
||||
requestUserID: requestUserID,
|
||||
statusItemAttribute: attribute
|
||||
)
|
||||
cell.statusView.headerContainerView.isHidden = true // set header hide
|
||||
cell.statusView.actionToolbarContainer.isHidden = true // set toolbar hide
|
||||
cell.statusView.actionToolbarPlaceholderPaddingView.isHidden = false
|
||||
cell.delegate = statusTableViewCellDelegate
|
||||
cell.isAccessibilityElement = true
|
||||
StatusSection.configureStatusAccessibilityLabel(cell: cell)
|
||||
return cell
|
||||
|
||||
case .feedLoader(let record):
|
||||
return UITableViewCell()
|
||||
case .bottomLoader:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) as! TimelineBottomLoaderTableViewCell
|
||||
cell.startAnimating()
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||
cell.activityIndicatorView.startAnimating()
|
||||
return cell
|
||||
}
|
||||
// switch notificationItem {
|
||||
// case .notification(let objectID, let attribute):
|
||||
// guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification,
|
||||
// !notification.isDeleted
|
||||
// else { return UITableViewCell() }
|
||||
//
|
||||
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationStatusTableViewCell.self), for: indexPath) as! NotificationStatusTableViewCell
|
||||
// configure(
|
||||
// tableView: tableView,
|
||||
// cell: cell,
|
||||
// notification: notification,
|
||||
// dependency: dependency,
|
||||
// attribute: attribute
|
||||
// )
|
||||
// cell.delegate = delegate
|
||||
// cell.isAccessibilityElement = true
|
||||
// NotificationSection.configureStatusAccessibilityLabel(cell: cell)
|
||||
// return cell
|
||||
//
|
||||
// case .notificationStatus(objectID: let objectID, attribute: let attribute):
|
||||
// guard let notification = try? managedObjectContext.existingObject(with: objectID) as? MastodonNotification,
|
||||
// !notification.isDeleted,
|
||||
// let status = notification.status,
|
||||
// let requestUserID = dependency.context.authenticationService.activeMastodonAuthenticationBox.value?.userID
|
||||
// else { return UITableViewCell() }
|
||||
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
//
|
||||
// // configure cell
|
||||
// StatusSection.configureStatusTableViewCell(
|
||||
// cell: cell,
|
||||
// tableView: tableView,
|
||||
// timelineContext: .notifications,
|
||||
// dependency: dependency,
|
||||
// readableLayoutFrame: tableView.readableContentGuide.layoutFrame,
|
||||
// status: status,
|
||||
// requestUserID: requestUserID,
|
||||
// statusItemAttribute: attribute
|
||||
// )
|
||||
// cell.statusView.headerContainerView.isHidden = true // set header hide
|
||||
// cell.statusView.actionToolbarContainer.isHidden = true // set toolbar hide
|
||||
// cell.statusView.actionToolbarPlaceholderPaddingView.isHidden = false
|
||||
// cell.delegate = statusTableViewCellDelegate
|
||||
// cell.isAccessibilityElement = true
|
||||
// StatusSection.configureStatusAccessibilityLabel(cell: cell)
|
||||
// return cell
|
||||
//
|
||||
// case .bottomLoader:
|
||||
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) as! TimelineBottomLoaderTableViewCell
|
||||
// cell.startAnimating()
|
||||
// return cell
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationSection {
|
||||
|
||||
static func configure(
|
||||
context: AppContext,
|
||||
tableView: UITableView,
|
||||
cell: NotificationStatusTableViewCell,
|
||||
notification: MastodonNotification,
|
||||
dependency: NeedsDependency,
|
||||
attribute: Item.StatusAttribute
|
||||
cell: NotificationTableViewCell,
|
||||
viewModel: NotificationTableViewCell.ViewModel,
|
||||
configuration: Configuration
|
||||
) {
|
||||
// configure author
|
||||
cell.configure(
|
||||
with: AvatarConfigurableViewConfiguration(
|
||||
avatarImageURL: notification.account.avatarImageURL()
|
||||
)
|
||||
StatusSection.setupStatusPollDataSource(
|
||||
context: context,
|
||||
statusView: cell.notificationView.statusView
|
||||
)
|
||||
|
||||
func createActionImage() -> UIImage? {
|
||||
return UIImage(
|
||||
systemName: notification.notificationType.actionImageName,
|
||||
withConfiguration: UIImage.SymbolConfiguration(
|
||||
pointSize: 12, weight: .semibold
|
||||
)
|
||||
)?
|
||||
.withTintColor(.systemBackground)
|
||||
.af.imageAspectScaled(toFit: CGSize(width: 14, height: 14))
|
||||
}
|
||||
StatusSection.setupStatusPollDataSource(
|
||||
context: context,
|
||||
statusView: cell.notificationView.quoteStatusView
|
||||
)
|
||||
|
||||
cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color
|
||||
cell.avatarButton.badgeImageView.image = createActionImage()
|
||||
cell.traitCollectionDidChange
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] in
|
||||
guard let cell = cell else { return }
|
||||
cell.avatarButton.badgeImageView.image = createActionImage()
|
||||
}
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.notificationView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
// configure author name, notification description, timestamp
|
||||
let nameText = notification.account.displayNameWithFallback
|
||||
let titleLabelText: String = {
|
||||
switch notification.notificationType {
|
||||
case .favourite: return L10n.Scene.Notification.userFavoritedYourPost(nameText)
|
||||
case .follow: return L10n.Scene.Notification.userFollowedYou(nameText)
|
||||
case .followRequest: return L10n.Scene.Notification.userRequestedToFollowYou(nameText)
|
||||
case .mention: return L10n.Scene.Notification.userMentionedYou(nameText)
|
||||
case .poll: return L10n.Scene.Notification.userYourPollHasEnded(nameText)
|
||||
case .reblog: return L10n.Scene.Notification.userRebloggedYourPost(nameText)
|
||||
default: return ""
|
||||
}
|
||||
}()
|
||||
|
||||
do {
|
||||
let nameContent = MastodonContent(content: nameText, emojis: notification.account.emojiMeta)
|
||||
let nameMetaContent = try MastodonMetaContent.convert(document: nameContent)
|
||||
|
||||
let mastodonContent = MastodonContent(content: titleLabelText, emojis: notification.account.emojiMeta)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
|
||||
cell.titleLabel.configure(content: metaContent)
|
||||
|
||||
if let nameRange = metaContent.string.range(of: nameMetaContent.string) {
|
||||
let nsRange = NSRange(nameRange, in: metaContent.string)
|
||||
cell.titleLabel.textStorage.addAttributes([
|
||||
.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20),
|
||||
.foregroundColor: Asset.Colors.brandBlue.color,
|
||||
], range: nsRange)
|
||||
}
|
||||
|
||||
} catch {
|
||||
let metaContent = PlaintextMetaContent(string: titleLabelText)
|
||||
cell.titleLabel.configure(content: metaContent)
|
||||
}
|
||||
|
||||
let createAt = notification.createAt
|
||||
cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
||||
AppContext.shared.timestampUpdatePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] _ in
|
||||
guard let cell = cell else { return }
|
||||
cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
// configure follow request (if exist)
|
||||
if case .followRequest = notification.notificationType {
|
||||
cell.acceptButton.publisher(for: .touchUpInside)
|
||||
.sink { [weak cell] _ in
|
||||
guard let cell = cell else { return }
|
||||
cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.rejectButton.publisher(for: .touchUpInside)
|
||||
.sink { [weak cell] _ in
|
||||
guard let cell = cell else { return }
|
||||
cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.buttonStackView.isHidden = false
|
||||
} else {
|
||||
cell.buttonStackView.isHidden = true
|
||||
}
|
||||
|
||||
// configure status (if exist)
|
||||
if let status = notification.status {
|
||||
let frame = CGRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right,
|
||||
height: tableView.readableContentGuide.layoutFrame.height
|
||||
)
|
||||
StatusSection.configure(
|
||||
cell: cell,
|
||||
tableView: tableView,
|
||||
timelineContext: .notifications,
|
||||
dependency: dependency,
|
||||
readableLayoutFrame: frame,
|
||||
status: status,
|
||||
requestUserID: notification.userID,
|
||||
statusItemAttribute: attribute
|
||||
)
|
||||
cell.statusContainerView.isHidden = false
|
||||
cell.containerStackView.alignment = .top
|
||||
cell.containerStackViewBottomLayoutConstraint.constant = 0
|
||||
} else {
|
||||
if case .followRequest = notification.notificationType {
|
||||
cell.containerStackView.alignment = .top
|
||||
} else {
|
||||
cell.containerStackView.alignment = .center
|
||||
}
|
||||
cell.statusContainerView.isHidden = true
|
||||
cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view
|
||||
}
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
delegate: configuration.notificationTableViewCellDelegate
|
||||
)
|
||||
}
|
||||
|
||||
static func configureStatusAccessibilityLabel(cell: NotificationStatusTableViewCell) {
|
||||
// FIXME:
|
||||
cell.accessibilityLabel = {
|
||||
var accessibilityViews: [UIView?] = []
|
||||
accessibilityViews.append(contentsOf: [
|
||||
cell.titleLabel,
|
||||
cell.timestampLabel,
|
||||
cell.statusView
|
||||
])
|
||||
if !cell.statusContainerView.isHidden {
|
||||
if !cell.statusView.headerContainerView.isHidden {
|
||||
accessibilityViews.append(cell.statusView.headerInfoLabel)
|
||||
}
|
||||
accessibilityViews.append(contentsOf: [
|
||||
cell.statusView.nameMetaLabel,
|
||||
cell.statusView.dateLabel,
|
||||
cell.statusView.contentMetaText.textView,
|
||||
])
|
||||
}
|
||||
return accessibilityViews
|
||||
.compactMap { $0?.accessibilityLabel }
|
||||
.joined(separator: " ")
|
||||
}()
|
||||
}
|
||||
// static func configure(
|
||||
// tableView: UITableView,
|
||||
// cell: NotificationStatusTableViewCell,
|
||||
// notification: MastodonNotification,
|
||||
// dependency: NeedsDependency,
|
||||
// attribute: Item.StatusAttribute
|
||||
// ) {
|
||||
// // configure author
|
||||
// cell.configure(
|
||||
// with: AvatarConfigurableViewConfiguration(
|
||||
// avatarImageURL: notification.account.avatarImageURL()
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// func createActionImage() -> UIImage? {
|
||||
// return UIImage(
|
||||
// systemName: notification.notificationType.actionImageName,
|
||||
// withConfiguration: UIImage.SymbolConfiguration(
|
||||
// pointSize: 12, weight: .semibold
|
||||
// )
|
||||
// )?
|
||||
// .withTintColor(.systemBackground)
|
||||
// .af.imageAspectScaled(toFit: CGSize(width: 14, height: 14))
|
||||
// }
|
||||
//
|
||||
// cell.avatarButton.badgeImageView.backgroundColor = notification.notificationType.color
|
||||
// cell.avatarButton.badgeImageView.image = createActionImage()
|
||||
// cell.traitCollectionDidChange
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak cell] in
|
||||
// guard let cell = cell else { return }
|
||||
// cell.avatarButton.badgeImageView.image = createActionImage()
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
//
|
||||
// // configure author name, notification description, timestamp
|
||||
// let nameText = notification.account.displayNameWithFallback
|
||||
// let titleLabelText: String = {
|
||||
// switch notification.notificationType {
|
||||
// case .favourite: return L10n.Scene.Notification.userFavoritedYourPost(nameText)
|
||||
// case .follow: return L10n.Scene.Notification.userFollowedYou(nameText)
|
||||
// case .followRequest: return L10n.Scene.Notification.userRequestedToFollowYou(nameText)
|
||||
// case .mention: return L10n.Scene.Notification.userMentionedYou(nameText)
|
||||
// case .poll: return L10n.Scene.Notification.userYourPollHasEnded(nameText)
|
||||
// case .reblog: return L10n.Scene.Notification.userRebloggedYourPost(nameText)
|
||||
// default: return ""
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// do {
|
||||
// let nameContent = MastodonContent(content: nameText, emojis: notification.account.emojiMeta)
|
||||
// let nameMetaContent = try MastodonMetaContent.convert(document: nameContent)
|
||||
//
|
||||
// let mastodonContent = MastodonContent(content: titleLabelText, emojis: notification.account.emojiMeta)
|
||||
// let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
//
|
||||
// cell.titleLabel.configure(content: metaContent)
|
||||
//
|
||||
// if let nameRange = metaContent.string.range(of: nameMetaContent.string) {
|
||||
// let nsRange = NSRange(nameRange, in: metaContent.string)
|
||||
// cell.titleLabel.textStorage.addAttributes([
|
||||
// .font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 15, weight: .semibold), maximumPointSize: 20),
|
||||
// .foregroundColor: Asset.Colors.brandBlue.color,
|
||||
// ], range: nsRange)
|
||||
// }
|
||||
//
|
||||
// } catch {
|
||||
// let metaContent = PlaintextMetaContent(string: titleLabelText)
|
||||
// cell.titleLabel.configure(content: metaContent)
|
||||
// }
|
||||
//
|
||||
// let createAt = notification.createAt
|
||||
// cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
||||
// AppContext.shared.timestampUpdatePublisher
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak cell] _ in
|
||||
// guard let cell = cell else { return }
|
||||
// cell.timestampLabel.text = createAt.localizedSlowedTimeAgoSinceNow
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
//
|
||||
// // configure follow request (if exist)
|
||||
// if case .followRequest = notification.notificationType {
|
||||
// cell.acceptButton.publisher(for: .touchUpInside)
|
||||
// .sink { [weak cell] _ in
|
||||
// guard let cell = cell else { return }
|
||||
// cell.delegate?.notificationTableViewCell(cell, notification: notification, acceptButtonDidPressed: cell.acceptButton)
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
// cell.rejectButton.publisher(for: .touchUpInside)
|
||||
// .sink { [weak cell] _ in
|
||||
// guard let cell = cell else { return }
|
||||
// cell.delegate?.notificationTableViewCell(cell, notification: notification, rejectButtonDidPressed: cell.rejectButton)
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
// cell.buttonStackView.isHidden = false
|
||||
// } else {
|
||||
// cell.buttonStackView.isHidden = true
|
||||
// }
|
||||
//
|
||||
// // configure status (if exist)
|
||||
// if let status = notification.status {
|
||||
// let frame = CGRect(
|
||||
// x: 0,
|
||||
// y: 0,
|
||||
// width: tableView.readableContentGuide.layoutFrame.width - NotificationStatusTableViewCell.statusPadding.left - NotificationStatusTableViewCell.statusPadding.right,
|
||||
// height: tableView.readableContentGuide.layoutFrame.height
|
||||
// )
|
||||
// StatusSection.configure(
|
||||
// cell: cell,
|
||||
// tableView: tableView,
|
||||
// timelineContext: .notifications,
|
||||
// dependency: dependency,
|
||||
// readableLayoutFrame: frame,
|
||||
// status: status,
|
||||
// requestUserID: notification.userID,
|
||||
// statusItemAttribute: attribute
|
||||
// )
|
||||
// cell.statusContainerView.isHidden = false
|
||||
// cell.containerStackView.alignment = .top
|
||||
// cell.containerStackViewBottomLayoutConstraint.constant = 0
|
||||
// } else {
|
||||
// if case .followRequest = notification.notificationType {
|
||||
// cell.containerStackView.alignment = .top
|
||||
// } else {
|
||||
// cell.containerStackView.alignment = .center
|
||||
// }
|
||||
// cell.statusContainerView.isHidden = true
|
||||
// cell.containerStackViewBottomLayoutConstraint.constant = 5 // 5pt margin when no status view
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static func configureStatusAccessibilityLabel(cell: NotificationStatusTableViewCell) {
|
||||
// // FIXME:
|
||||
// cell.accessibilityLabel = {
|
||||
// var accessibilityViews: [UIView?] = []
|
||||
// accessibilityViews.append(contentsOf: [
|
||||
// cell.titleLabel,
|
||||
// cell.timestampLabel,
|
||||
// cell.statusView
|
||||
// ])
|
||||
// if !cell.statusContainerView.isHidden {
|
||||
// if !cell.statusView.headerContainerView.isHidden {
|
||||
// accessibilityViews.append(cell.statusView.headerInfoLabel)
|
||||
// }
|
||||
// accessibilityViews.append(contentsOf: [
|
||||
// cell.statusView.nameMetaLabel,
|
||||
// cell.statusView.dateLabel,
|
||||
// cell.statusView.contentMetaText.textView,
|
||||
// ])
|
||||
// }
|
||||
// return accessibilityViews
|
||||
// .compactMap { $0?.accessibilityLabel }
|
||||
// .joined(separator: " ")
|
||||
// }()
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
/// Note: update Equatable when change case
|
||||
enum CategoryPickerItem {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum CategoryPickerSection: Equatable, Hashable {
|
||||
case main
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum ServerRuleSection: Hashable {
|
||||
case header
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
//
|
||||
// PollItem.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
/// Note: update Equatable when change case
|
||||
enum PollItem {
|
||||
case option(objectID: NSManagedObjectID, attribute: Attribute)
|
||||
}
|
||||
|
||||
|
||||
extension PollItem {
|
||||
class Attribute: Hashable {
|
||||
|
||||
enum SelectState: Equatable, Hashable {
|
||||
case none
|
||||
case off
|
||||
case on
|
||||
}
|
||||
|
||||
enum VoteState: Equatable, Hashable {
|
||||
case hidden
|
||||
case reveal(voted: Bool, percentage: Double, animated: Bool)
|
||||
}
|
||||
|
||||
var selectState: SelectState
|
||||
var voteState: VoteState
|
||||
|
||||
init(selectState: SelectState, voteState: VoteState) {
|
||||
self.selectState = selectState
|
||||
self.voteState = voteState
|
||||
}
|
||||
|
||||
static func == (lhs: PollItem.Attribute, rhs: PollItem.Attribute) -> Bool {
|
||||
return lhs.selectState == rhs.selectState &&
|
||||
lhs.voteState == rhs.voteState
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(selectState)
|
||||
hasher.combine(voteState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PollItem: Equatable {
|
||||
static func == (lhs: PollItem, rhs: PollItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.option(let objectIDLeft, _), .option(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension PollItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .option(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
//
|
||||
// PollSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-2.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
import MastodonSDK
|
||||
|
||||
extension Mastodon.Entity.Attachment: Hashable {
|
||||
public static func == (lhs: Mastodon.Entity.Attachment, rhs: Mastodon.Entity.Attachment) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
||||
enum PollSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension PollSection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
managedObjectContext: NSManagedObjectContext
|
||||
) -> UITableViewDiffableDataSource<PollSection, PollItem> {
|
||||
return UITableViewDiffableDataSource<PollSection, PollItem>(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
switch item {
|
||||
case .option(let objectID, let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PollOptionTableViewCell.self), for: indexPath) as! PollOptionTableViewCell
|
||||
managedObjectContext.performAndWait {
|
||||
let option = managedObjectContext.object(with: objectID) as! PollOption
|
||||
PollSection.configure(cell: cell, pollOption: option, pollItemAttribute: attribute)
|
||||
|
||||
cell.isAccessibilityElement = true
|
||||
cell.accessibilityLabel = {
|
||||
var labels: [String] = [option.title]
|
||||
if let percentage = cell.pollOptionView.optionPercentageLabel.text {
|
||||
labels.append(percentage)
|
||||
}
|
||||
return labels.joined(separator: ",")
|
||||
}()
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PollSection {
|
||||
static func configure(
|
||||
cell: PollOptionTableViewCell,
|
||||
pollOption option: PollOption,
|
||||
pollItemAttribute attribute: PollItem.Attribute
|
||||
) {
|
||||
cell.pollOptionView.optionTextField.text = option.title
|
||||
configure(cell: cell, selectState: attribute.selectState)
|
||||
configure(cell: cell, voteState: attribute.voteState)
|
||||
cell.attribute = attribute
|
||||
cell.layoutIfNeeded()
|
||||
cell.updateTextAppearance()
|
||||
}
|
||||
}
|
||||
|
||||
extension PollSection {
|
||||
|
||||
static func configure(cell: PollOptionTableViewCell, selectState state: PollItem.Attribute.SelectState) {
|
||||
switch state {
|
||||
case .none:
|
||||
cell.pollOptionView.checkmarkBackgroundView.isHidden = true
|
||||
cell.pollOptionView.checkmarkImageView.isHidden = true
|
||||
case .off:
|
||||
ThemeService.shared.currentTheme
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] theme in
|
||||
guard let cell = cell else { return }
|
||||
cell.pollOptionView.checkmarkBackgroundView.backgroundColor = theme.tertiarySystemBackgroundColor
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderColor = theme.tableViewCellSelectionBackgroundColor.withAlphaComponent(0.3).cgColor
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderWidth = 1
|
||||
cell.pollOptionView.checkmarkBackgroundView.isHidden = false
|
||||
cell.pollOptionView.checkmarkImageView.isHidden = true
|
||||
case .on:
|
||||
ThemeService.shared.currentTheme
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] theme in
|
||||
guard let cell = cell else { return }
|
||||
cell.pollOptionView.checkmarkBackgroundView.backgroundColor = theme.tertiarySystemBackgroundColor
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderColor = UIColor.clear.cgColor
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderWidth = 0
|
||||
cell.pollOptionView.checkmarkBackgroundView.isHidden = false
|
||||
cell.pollOptionView.checkmarkImageView.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
static func configure(cell: PollOptionTableViewCell, voteState state: PollItem.Attribute.VoteState) {
|
||||
switch state {
|
||||
case .hidden:
|
||||
cell.pollOptionView.optionPercentageLabel.isHidden = true
|
||||
cell.pollOptionView.voteProgressStripView.isHidden = true
|
||||
cell.pollOptionView.voteProgressStripView.setProgress(0.0, animated: false)
|
||||
case .reveal(let voted, let percentage, let animated):
|
||||
cell.pollOptionView.optionPercentageLabel.isHidden = false
|
||||
cell.pollOptionView.optionPercentageLabel.text = String(Int(100 * percentage)) + "%"
|
||||
cell.pollOptionView.voteProgressStripView.isHidden = false
|
||||
cell.pollOptionView.voteProgressStripView.tintColor = voted ? Asset.Colors.brandBlue.color : Asset.Colors.Poll.disabled.color
|
||||
cell.pollOptionView.voteProgressStripView.setProgress(CGFloat(percentage), animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,24 +10,10 @@ import Combine
|
|||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
|
||||
enum ProfileFieldItem {
|
||||
case field(field: FieldValue, attribute: FieldItemAttribute)
|
||||
case addEntry(attribute: AddEntryItemAttribute)
|
||||
}
|
||||
|
||||
protocol ProfileFieldListSeparatorLineConfigurable: AnyObject {
|
||||
var isLast: Bool { get set }
|
||||
}
|
||||
|
||||
extension ProfileFieldItem {
|
||||
var listSeparatorLineConfigurable: ProfileFieldListSeparatorLineConfigurable? {
|
||||
switch self {
|
||||
case .field(_, let attribute):
|
||||
return attribute
|
||||
case .addEntry(let attribute):
|
||||
return attribute
|
||||
}
|
||||
}
|
||||
enum ProfileFieldItem: Hashable {
|
||||
case field(field: FieldValue)
|
||||
case editField(field: FieldValue)
|
||||
case addEntry
|
||||
}
|
||||
|
||||
extension ProfileFieldItem {
|
||||
|
@ -36,17 +22,29 @@ extension ProfileFieldItem {
|
|||
|
||||
var name: CurrentValueSubject<String, Never>
|
||||
var value: CurrentValueSubject<String, Never>
|
||||
|
||||
let emojiMeta: MastodonContent.Emojis
|
||||
|
||||
init(id: UUID = UUID(), name: String, value: String) {
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
name: String,
|
||||
value: String,
|
||||
emojiMeta: MastodonContent.Emojis
|
||||
) {
|
||||
self.id = id
|
||||
self.name = CurrentValueSubject(name)
|
||||
self.value = CurrentValueSubject(value)
|
||||
self.emojiMeta = emojiMeta
|
||||
}
|
||||
|
||||
static func == (lhs: ProfileFieldItem.FieldValue, rhs: ProfileFieldItem.FieldValue) -> Bool {
|
||||
static func == (
|
||||
lhs: ProfileFieldItem.FieldValue,
|
||||
rhs: ProfileFieldItem.FieldValue
|
||||
) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
&& lhs.name.value == rhs.name.value
|
||||
&& lhs.value.value == rhs.value.value
|
||||
&& lhs.emojiMeta == rhs.emojiMeta
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
|
@ -54,50 +52,3 @@ extension ProfileFieldItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileFieldItem {
|
||||
class FieldItemAttribute: Equatable, ProfileFieldListSeparatorLineConfigurable {
|
||||
let emojiMeta = CurrentValueSubject<MastodonContent.Emojis, Never>([:])
|
||||
|
||||
var isEditing = false
|
||||
var isLast = false
|
||||
|
||||
static func == (lhs: ProfileFieldItem.FieldItemAttribute, rhs: ProfileFieldItem.FieldItemAttribute) -> Bool {
|
||||
return lhs.isEditing == rhs.isEditing
|
||||
&& lhs.isLast == rhs.isLast
|
||||
}
|
||||
}
|
||||
|
||||
class AddEntryItemAttribute: Equatable, ProfileFieldListSeparatorLineConfigurable {
|
||||
var isLast = false
|
||||
|
||||
static func == (lhs: ProfileFieldItem.AddEntryItemAttribute, rhs: ProfileFieldItem.AddEntryItemAttribute) -> Bool {
|
||||
return lhs.isLast == rhs.isLast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileFieldItem: Equatable {
|
||||
static func == (lhs: ProfileFieldItem, rhs: ProfileFieldItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.field(let fieldLeft, let attributeLeft), .field(let fieldRight, let attributeRight)):
|
||||
return fieldLeft.id == fieldRight.id
|
||||
&& attributeLeft == attributeRight
|
||||
case (.addEntry(let attributeLeft), .addEntry(let attributeRight)):
|
||||
return attributeLeft == attributeRight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileFieldItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .field(let field, _):
|
||||
hasher.combine(field.id)
|
||||
case .addEntry:
|
||||
hasher.combine(String(describing: ProfileFieldItem.addEntry.self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,125 +9,124 @@ import os
|
|||
import UIKit
|
||||
import Combine
|
||||
import MastodonMeta
|
||||
import MastodonLocalization
|
||||
|
||||
enum ProfileFieldSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension ProfileFieldSection {
|
||||
static func collectionViewDiffableDataSource(
|
||||
for collectionView: UICollectionView,
|
||||
profileFieldCollectionViewCellDelegate: ProfileFieldCollectionViewCellDelegate,
|
||||
profileFieldAddEntryCollectionViewCellDelegate: ProfileFieldAddEntryCollectionViewCellDelegate
|
||||
|
||||
struct Configuration {
|
||||
weak var profileFieldCollectionViewCellDelegate: ProfileFieldCollectionViewCellDelegate?
|
||||
weak var profileFieldEditCollectionViewCellDelegate: ProfileFieldEditCollectionViewCellDelegate?
|
||||
}
|
||||
|
||||
static func diffableDataSource(
|
||||
collectionView: UICollectionView,
|
||||
context: AppContext,
|
||||
configuration: Configuration
|
||||
) -> UICollectionViewDiffableDataSource<ProfileFieldSection, ProfileFieldItem> {
|
||||
let dataSource = UICollectionViewDiffableDataSource<ProfileFieldSection, ProfileFieldItem>(collectionView: collectionView) {
|
||||
[
|
||||
weak profileFieldCollectionViewCellDelegate,
|
||||
weak profileFieldAddEntryCollectionViewCellDelegate
|
||||
] collectionView, indexPath, item in
|
||||
collectionView.register(ProfileFieldCollectionViewHeaderFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: ProfileFieldCollectionViewHeaderFooterView.headerReuseIdentifer)
|
||||
collectionView.register(ProfileFieldCollectionViewHeaderFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: ProfileFieldCollectionViewHeaderFooterView.footerReuseIdentifer)
|
||||
|
||||
let fieldCellRegistration = UICollectionView.CellRegistration<ProfileFieldCollectionViewCell, ProfileFieldItem> { cell, indexPath, item in
|
||||
guard case let .field(field) = item else { return }
|
||||
|
||||
// set key
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: field.name.value, emojis: field.emojiMeta)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.keyMetaLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let content = PlaintextMetaContent(string: field.name.value)
|
||||
cell.keyMetaLabel.configure(content: content)
|
||||
}
|
||||
|
||||
// set value
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: field.value.value, emojis: field.emojiMeta)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.valueMetaLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let content = PlaintextMetaContent(string: field.value.value)
|
||||
cell.valueMetaLabel.configure(content: content)
|
||||
}
|
||||
|
||||
// set background
|
||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
||||
backgroundConfiguration.backgroundColor = UIColor.secondarySystemBackground
|
||||
cell.backgroundConfiguration = backgroundConfiguration
|
||||
|
||||
cell.delegate = configuration.profileFieldCollectionViewCellDelegate
|
||||
}
|
||||
|
||||
let editFieldCellRegistration = UICollectionView.CellRegistration<ProfileFieldEditCollectionViewCell, ProfileFieldItem> { cell, indexPath, item in
|
||||
guard case let .editField(field) = item else { return }
|
||||
|
||||
cell.keyTextField.text = field.name.value
|
||||
cell.valueTextField.text = field.value.value
|
||||
|
||||
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.keyTextField)
|
||||
.compactMap { $0.object as? UITextField }
|
||||
.map { $0.text ?? "" }
|
||||
.removeDuplicates()
|
||||
.assign(to: \.value, on: field.name)
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: cell.valueTextField)
|
||||
.compactMap { $0.object as? UITextField }
|
||||
.map { $0.text ?? "" }
|
||||
.removeDuplicates()
|
||||
.assign(to: \.value, on: field.value)
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
// set background
|
||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
||||
backgroundConfiguration.backgroundColor = UIColor.secondarySystemBackground
|
||||
cell.backgroundConfiguration = backgroundConfiguration
|
||||
|
||||
cell.delegate = configuration.profileFieldEditCollectionViewCellDelegate
|
||||
}
|
||||
|
||||
let addEntryCellRegistration = UICollectionView.CellRegistration<ProfileFieldAddEntryCollectionViewCell, ProfileFieldItem> { cell, indexPath, item in
|
||||
guard case .addEntry = item else { return }
|
||||
|
||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
||||
backgroundConfiguration.backgroundColorTransformer = .init { [weak cell] _ in
|
||||
guard let cell = cell else {
|
||||
return .secondarySystemBackground
|
||||
}
|
||||
let state = cell.configurationState
|
||||
if state.isHighlighted || state.isSelected {
|
||||
return .secondarySystemBackground.withAlphaComponent(0.5)
|
||||
} else {
|
||||
return .secondarySystemBackground
|
||||
}
|
||||
}
|
||||
cell.backgroundConfiguration = backgroundConfiguration
|
||||
}
|
||||
|
||||
let dataSource = UICollectionViewDiffableDataSource<ProfileFieldSection, ProfileFieldItem>(collectionView: collectionView) { collectionView, indexPath, item in
|
||||
switch item {
|
||||
case .field(let field, let attribute):
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ProfileFieldCollectionViewCell.self), for: indexPath) as! ProfileFieldCollectionViewCell
|
||||
|
||||
// set key
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: field.name.value, emojis: attribute.emojiMeta.value)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.fieldView.titleMetaLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let content = PlaintextMetaContent(string: field.name.value)
|
||||
cell.fieldView.titleMetaLabel.configure(content: content)
|
||||
}
|
||||
cell.fieldView.titleTextField.text = field.name.value
|
||||
Publishers.CombineLatest(
|
||||
field.name.removeDuplicates(),
|
||||
attribute.emojiMeta.removeDuplicates()
|
||||
case .field:
|
||||
return collectionView.dequeueConfiguredReusableCell(
|
||||
using: fieldCellRegistration,
|
||||
for: indexPath,
|
||||
item: item
|
||||
)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak cell] name, emojiMeta in
|
||||
guard let cell = cell else { return }
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: name, emojis: emojiMeta)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.fieldView.titleMetaLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let content = PlaintextMetaContent(string: name)
|
||||
cell.fieldView.titleMetaLabel.configure(content: content)
|
||||
}
|
||||
// only bind label. The text field should only set once
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
|
||||
// set value
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: field.value.value, emojis: attribute.emojiMeta.value)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.fieldView.valueMetaLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let content = PlaintextMetaContent(string: field.value.value)
|
||||
cell.fieldView.valueMetaLabel.configure(content: content)
|
||||
}
|
||||
cell.fieldView.valueTextField.text = field.value.value
|
||||
Publishers.CombineLatest(
|
||||
field.value.removeDuplicates(),
|
||||
attribute.emojiMeta.removeDuplicates()
|
||||
case .editField:
|
||||
return collectionView.dequeueConfiguredReusableCell(
|
||||
using: editFieldCellRegistration,
|
||||
for: indexPath,
|
||||
item: item
|
||||
)
|
||||
case .addEntry:
|
||||
return collectionView.dequeueConfiguredReusableCell(
|
||||
using: addEntryCellRegistration,
|
||||
for: indexPath,
|
||||
item: item
|
||||
)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak cell] value, emojiMeta in
|
||||
guard let cell = cell else { return }
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: value, emojis: emojiMeta)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.fieldView.valueMetaLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let content = PlaintextMetaContent(string: value)
|
||||
cell.fieldView.valueMetaLabel.configure(content: content)
|
||||
}
|
||||
// only bind label. The text field should only set once
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
// bind editing
|
||||
if attribute.isEditing {
|
||||
cell.fieldView.name
|
||||
.removeDuplicates()
|
||||
.receive(on: RunLoop.main)
|
||||
.assign(to: \.value, on: field.name)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.fieldView.value
|
||||
.removeDuplicates()
|
||||
.receive(on: RunLoop.main)
|
||||
.assign(to: \.value, on: field.value)
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
// setup editing state
|
||||
cell.fieldView.titleTextField.isHidden = !attribute.isEditing
|
||||
cell.fieldView.valueTextField.isHidden = !attribute.isEditing
|
||||
cell.fieldView.titleMetaLabel.isHidden = attribute.isEditing
|
||||
cell.fieldView.valueMetaLabel.isHidden = attribute.isEditing
|
||||
|
||||
// set control hidden
|
||||
let isHidden = !attribute.isEditing
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update editing state: %s", ((#file as NSString).lastPathComponent), #line, #function, isHidden ? "true" : "false")
|
||||
cell.editButton.isHidden = isHidden
|
||||
cell.reorderBarImageView.isHidden = isHidden
|
||||
|
||||
// update separator line
|
||||
cell.bottomSeparatorLine.isHidden = attribute.isLast
|
||||
|
||||
cell.delegate = profileFieldCollectionViewCellDelegate
|
||||
|
||||
return cell
|
||||
|
||||
case .addEntry(let attribute):
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ProfileFieldAddEntryCollectionViewCell.self), for: indexPath) as! ProfileFieldAddEntryCollectionViewCell
|
||||
|
||||
cell.bottomSeparatorLine.isHidden = attribute.isLast
|
||||
cell.delegate = profileFieldAddEntryCollectionViewCellDelegate
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,6 +134,7 @@ extension ProfileFieldSection {
|
|||
switch kind {
|
||||
case UICollectionView.elementKindSectionHeader:
|
||||
let reusableView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ProfileFieldCollectionViewHeaderFooterView.headerReuseIdentifer, for: indexPath) as! ProfileFieldCollectionViewHeaderFooterView
|
||||
reusableView.frame.size.height = 20
|
||||
return reusableView
|
||||
case UICollectionView.elementKindSectionFooter:
|
||||
let reusableView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ProfileFieldCollectionViewHeaderFooterView.footerReuseIdentifer, for: indexPath) as! ProfileFieldCollectionViewHeaderFooterView
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
//
|
||||
// RecommendAccountSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/1.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import MetaTextKit
|
||||
import MastodonMeta
|
||||
import Combine
|
||||
|
||||
enum RecommendAccountSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
//extension RecommendAccountSection {
|
||||
// static func collectionViewDiffableDataSource(
|
||||
// for collectionView: UICollectionView,
|
||||
// dependency: NeedsDependency,
|
||||
// delegate: SearchRecommendAccountsCollectionViewCellDelegate,
|
||||
// managedObjectContext: NSManagedObjectContext
|
||||
// ) -> UICollectionViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> {
|
||||
// UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak delegate] collectionView, indexPath, objectID -> UICollectionViewCell? in
|
||||
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SearchRecommendAccountsCollectionViewCell.self), for: indexPath) as! SearchRecommendAccountsCollectionViewCell
|
||||
// managedObjectContext.performAndWait {
|
||||
// let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
||||
// configure(cell: cell, user: user, dependency: dependency)
|
||||
// }
|
||||
// cell.delegate = delegate
|
||||
// return cell
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static func configure(
|
||||
// cell: SearchRecommendAccountsCollectionViewCell,
|
||||
// user: MastodonUser,
|
||||
// dependency: NeedsDependency
|
||||
// ) {
|
||||
// configureContent(cell: cell, user: user)
|
||||
//
|
||||
// if let currentMastodonUser = dependency.context.authenticationService.activeMastodonAuthentication.value?.user {
|
||||
// configureFollowButton(with: user, currentMastodonUser: currentMastodonUser, followButton: cell.followButton)
|
||||
// }
|
||||
//
|
||||
// Publishers.CombineLatest(
|
||||
// ManagedObjectObserver.observe(object: user).eraseToAnyPublisher().mapError { $0 as Error },
|
||||
// dependency.context.authenticationService.activeMastodonAuthentication.setFailureType(to: Error.self)
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { _ in
|
||||
// // do nothing
|
||||
// } receiveValue: { [weak cell] change, authentication in
|
||||
// guard let cell = cell else { return }
|
||||
// guard case .update(let object) = change.changeType,
|
||||
// let user = object as? MastodonUser else { return }
|
||||
// guard let currentMastodonUser = authentication?.user else { return }
|
||||
//
|
||||
// configureFollowButton(with: user, currentMastodonUser: currentMastodonUser, followButton: cell.followButton)
|
||||
// }
|
||||
// .store(in: &cell.disposeBag)
|
||||
//
|
||||
// }
|
||||
//
|
||||
// static func configureContent(
|
||||
// cell: SearchRecommendAccountsCollectionViewCell,
|
||||
// user: MastodonUser
|
||||
// ) {
|
||||
// do {
|
||||
// let mastodonContent = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojis.asDictionary)
|
||||
// let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
// cell.displayNameLabel.configure(content: metaContent)
|
||||
// } catch {
|
||||
// let metaContent = PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||
// cell.displayNameLabel.configure(content: metaContent)
|
||||
// }
|
||||
// cell.acctLabel.text = "@" + user.acct
|
||||
// cell.avatarImageView.af.setImage(
|
||||
// withURL: user.avatarImageURLWithFallback(domain: user.domain),
|
||||
// placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
// imageTransition: .crossDissolve(0.2)
|
||||
// )
|
||||
// cell.headerImageView.af.setImage(
|
||||
// withURL: URL(string: user.header)!,
|
||||
// placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
// imageTransition: .crossDissolve(0.2)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// static func configureFollowButton(
|
||||
// with mastodonUser: MastodonUser,
|
||||
// currentMastodonUser: MastodonUser,
|
||||
// followButton: HighlightDimmableButton
|
||||
// ) {
|
||||
// let relationshipActionSet = relationShipActionSet(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser)
|
||||
// followButton.setTitle(relationshipActionSet.title, for: .normal)
|
||||
// }
|
||||
//
|
||||
// static func relationShipActionSet(
|
||||
// mastodonUser: MastodonUser,
|
||||
// currentMastodonUser: MastodonUser
|
||||
// ) -> ProfileViewModel.RelationshipActionOptionSet {
|
||||
// var relationshipActionSet = ProfileViewModel.RelationshipActionOptionSet([.follow])
|
||||
// let isFollowing = mastodonUser.followingBy.flatMap { $0.contains(currentMastodonUser) } ?? false
|
||||
// if isFollowing {
|
||||
// relationshipActionSet.insert(.following)
|
||||
// }
|
||||
//
|
||||
// let isPending = mastodonUser.followRequestedBy.flatMap { $0.contains(currentMastodonUser) } ?? false
|
||||
// if isPending {
|
||||
// relationshipActionSet.insert(.pending)
|
||||
// }
|
||||
//
|
||||
// let isBlocking = mastodonUser.blockingBy.flatMap { $0.contains(currentMastodonUser) } ?? false
|
||||
// if isBlocking {
|
||||
// relationshipActionSet.insert(.blocking)
|
||||
// }
|
||||
//
|
||||
// let isBlockedBy = currentMastodonUser.blockingBy.flatMap { $0.contains(mastodonUser) } ?? false
|
||||
// if isBlockedBy {
|
||||
// relationshipActionSet.insert(.blocked)
|
||||
// }
|
||||
// return relationshipActionSet
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//extension RecommendAccountSection {
|
||||
//
|
||||
// static func tableViewDiffableDataSource(
|
||||
// for tableView: UITableView,
|
||||
// managedObjectContext: NSManagedObjectContext,
|
||||
// viewModel: SuggestionAccountViewModel,
|
||||
// delegate: SuggestionAccountTableViewCellDelegate
|
||||
// ) -> UITableViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> {
|
||||
// UITableViewDiffableDataSource(tableView: tableView) { [weak viewModel, weak delegate] (tableView, indexPath, objectID) -> UITableViewCell? in
|
||||
// guard let viewModel = viewModel else { return nil }
|
||||
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell
|
||||
// let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
||||
// let isSelected = viewModel.selectedAccounts.value.contains(objectID)
|
||||
// cell.delegate = delegate
|
||||
// cell.config(with: user, isSelected: isSelected)
|
||||
// return cell
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// ReportItem.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-1-27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ReportItem: Hashable {
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// ReportSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/20.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import AVKit
|
||||
import os.log
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum ReportSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension ReportSection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: ReportViewController,
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
timestampUpdatePublisher: AnyPublisher<Date, Never>
|
||||
) -> UITableViewDiffableDataSource<ReportSection, ReportItem> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) {[
|
||||
weak dependency
|
||||
] tableView, indexPath, item -> UITableViewCell? in
|
||||
return UITableViewCell()
|
||||
guard let dependency = dependency else { return UITableViewCell() }
|
||||
|
||||
// switch item {
|
||||
// case .reportStatus(let objectID, let attribute):
|
||||
// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ReportedStatusTableViewCell.self), for: indexPath) as! ReportedStatusTableViewCell
|
||||
// cell.dependency = dependency
|
||||
// let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
// let requestUserID = activeMastodonAuthenticationBox?.userID ?? ""
|
||||
// managedObjectContext.performAndWait { [weak dependency] in
|
||||
// guard let dependency = dependency else { return }
|
||||
// let status = managedObjectContext.object(with: objectID) as! Status
|
||||
// StatusSection.configure(
|
||||
// cell: cell,
|
||||
// tableView: tableView,
|
||||
// timelineContext: .report,
|
||||
// dependency: dependency,
|
||||
// readableLayoutFrame: tableView.readableContentGuide.layoutFrame,
|
||||
// status: status,
|
||||
// requestUserID: requestUserID,
|
||||
// statusItemAttribute: attribute
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // defalut to select the report status
|
||||
// if attribute.isSelected {
|
||||
// tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
||||
// } else {
|
||||
// tableView.deselectRow(at: indexPath, animated: false)
|
||||
// }
|
||||
//
|
||||
// return cell
|
||||
// default:
|
||||
// return nil
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
//
|
||||
// RecommendAccountSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/1.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import MetaTextKit
|
||||
import MastodonMeta
|
||||
import Combine
|
||||
|
||||
enum RecommendAccountSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension RecommendAccountSection {
|
||||
static func collectionViewDiffableDataSource(
|
||||
for collectionView: UICollectionView,
|
||||
dependency: NeedsDependency,
|
||||
delegate: SearchRecommendAccountsCollectionViewCellDelegate,
|
||||
managedObjectContext: NSManagedObjectContext
|
||||
) -> UICollectionViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> {
|
||||
UICollectionViewDiffableDataSource(collectionView: collectionView) { [weak delegate] collectionView, indexPath, objectID -> UICollectionViewCell? in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SearchRecommendAccountsCollectionViewCell.self), for: indexPath) as! SearchRecommendAccountsCollectionViewCell
|
||||
managedObjectContext.performAndWait {
|
||||
let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
||||
configure(cell: cell, user: user, dependency: dependency)
|
||||
}
|
||||
cell.delegate = delegate
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
static func configure(
|
||||
cell: SearchRecommendAccountsCollectionViewCell,
|
||||
user: MastodonUser,
|
||||
dependency: NeedsDependency
|
||||
) {
|
||||
configureContent(cell: cell, user: user)
|
||||
|
||||
if let currentMastodonUser = dependency.context.authenticationService.activeMastodonAuthentication.value?.user {
|
||||
configureFollowButton(with: user, currentMastodonUser: currentMastodonUser, followButton: cell.followButton)
|
||||
}
|
||||
|
||||
Publishers.CombineLatest(
|
||||
ManagedObjectObserver.observe(object: user).eraseToAnyPublisher().mapError { $0 as Error },
|
||||
dependency.context.authenticationService.activeMastodonAuthentication.setFailureType(to: Error.self)
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { _ in
|
||||
// do nothing
|
||||
} receiveValue: { [weak cell] change, authentication in
|
||||
guard let cell = cell else { return }
|
||||
guard case .update(let object) = change.changeType,
|
||||
let user = object as? MastodonUser else { return }
|
||||
guard let currentMastodonUser = authentication?.user else { return }
|
||||
|
||||
configureFollowButton(with: user, currentMastodonUser: currentMastodonUser, followButton: cell.followButton)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
}
|
||||
|
||||
static func configureContent(
|
||||
cell: SearchRecommendAccountsCollectionViewCell,
|
||||
user: MastodonUser
|
||||
) {
|
||||
do {
|
||||
let mastodonContent = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojiMeta)
|
||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||
cell.displayNameLabel.configure(content: metaContent)
|
||||
} catch {
|
||||
let metaContent = PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||
cell.displayNameLabel.configure(content: metaContent)
|
||||
}
|
||||
cell.acctLabel.text = "@" + user.acct
|
||||
cell.avatarImageView.af.setImage(
|
||||
withURL: user.avatarImageURLWithFallback(domain: user.domain),
|
||||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
imageTransition: .crossDissolve(0.2)
|
||||
)
|
||||
cell.headerImageView.af.setImage(
|
||||
withURL: URL(string: user.header)!,
|
||||
placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
imageTransition: .crossDissolve(0.2)
|
||||
)
|
||||
}
|
||||
|
||||
static func configureFollowButton(
|
||||
with mastodonUser: MastodonUser,
|
||||
currentMastodonUser: MastodonUser,
|
||||
followButton: HighlightDimmableButton
|
||||
) {
|
||||
let relationshipActionSet = relationShipActionSet(mastodonUser: mastodonUser, currentMastodonUser: currentMastodonUser)
|
||||
followButton.setTitle(relationshipActionSet.title, for: .normal)
|
||||
}
|
||||
|
||||
static func relationShipActionSet(
|
||||
mastodonUser: MastodonUser,
|
||||
currentMastodonUser: MastodonUser
|
||||
) -> ProfileViewModel.RelationshipActionOptionSet {
|
||||
var relationshipActionSet = ProfileViewModel.RelationshipActionOptionSet([.follow])
|
||||
let isFollowing = mastodonUser.followingBy.flatMap { $0.contains(currentMastodonUser) } ?? false
|
||||
if isFollowing {
|
||||
relationshipActionSet.insert(.following)
|
||||
}
|
||||
|
||||
let isPending = mastodonUser.followRequestedBy.flatMap { $0.contains(currentMastodonUser) } ?? false
|
||||
if isPending {
|
||||
relationshipActionSet.insert(.pending)
|
||||
}
|
||||
|
||||
let isBlocking = mastodonUser.blockingBy.flatMap { $0.contains(currentMastodonUser) } ?? false
|
||||
if isBlocking {
|
||||
relationshipActionSet.insert(.blocking)
|
||||
}
|
||||
|
||||
let isBlockedBy = currentMastodonUser.blockingBy.flatMap { $0.contains(mastodonUser) } ?? false
|
||||
if isBlockedBy {
|
||||
relationshipActionSet.insert(.blocked)
|
||||
}
|
||||
return relationshipActionSet
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension RecommendAccountSection {
|
||||
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
viewModel: SuggestionAccountViewModel,
|
||||
delegate: SuggestionAccountTableViewCellDelegate
|
||||
) -> UITableViewDiffableDataSource<RecommendAccountSection, NSManagedObjectID> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [weak viewModel, weak delegate] (tableView, indexPath, objectID) -> UITableViewCell? in
|
||||
guard let viewModel = viewModel else { return nil }
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell
|
||||
let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
||||
let isSelected = viewModel.selectedAccounts.value.contains(objectID)
|
||||
cell.delegate = delegate
|
||||
cell.config(with: user, isSelected: isSelected)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
//
|
||||
// RecommendHashTagSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/4/1.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
|
||||
enum RecommendHashTagSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension RecommendHashTagSection {
|
||||
static func collectionViewDiffableDataSource(
|
||||
for collectionView: UICollectionView
|
||||
) -> UICollectionViewDiffableDataSource<RecommendHashTagSection, Mastodon.Entity.Tag> {
|
||||
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, tag -> UICollectionViewCell? in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SearchRecommendTagsCollectionViewCell.self), for: indexPath) as! SearchRecommendTagsCollectionViewCell
|
||||
cell.config(with: tag)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,35 +7,9 @@
|
|||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
|
||||
enum SearchHistoryItem {
|
||||
case account(objectID: NSManagedObjectID)
|
||||
case hashtag(objectID: NSManagedObjectID)
|
||||
case status(objectID: NSManagedObjectID, attribute: Item.StatusAttribute)
|
||||
}
|
||||
|
||||
extension SearchHistoryItem: Hashable {
|
||||
static func == (lhs: SearchHistoryItem, rhs: SearchHistoryItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.account(let objectIDLeft), account(let objectIDRight)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.hashtag(let objectIDLeft), hashtag(let objectIDRight)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.status(let objectIDLeft, _), status(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .account(let objectID):
|
||||
hasher.combine(objectID)
|
||||
case .hashtag(let objectID):
|
||||
hasher.combine(objectID)
|
||||
case .status(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
}
|
||||
}
|
||||
enum SearchHistoryItem: Hashable {
|
||||
case hashtag(ManagedObjectRecord<Tag>)
|
||||
case user(ManagedObjectRecord<MastodonUser>)
|
||||
}
|
||||
|
|
|
@ -13,28 +13,80 @@ enum SearchHistorySection: Hashable {
|
|||
}
|
||||
|
||||
extension SearchHistorySection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: NeedsDependency
|
||||
) -> UITableViewDiffableDataSource<SearchHistorySection, SearchHistoryItem> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
|
||||
struct Configuration {
|
||||
weak var searchHistorySectionHeaderCollectionReusableViewDelegate: SearchHistorySectionHeaderCollectionReusableViewDelegate?
|
||||
}
|
||||
|
||||
static func diffableDataSource(
|
||||
collectionView: UICollectionView,
|
||||
context: AppContext,
|
||||
configuration: Configuration
|
||||
) -> UICollectionViewDiffableDataSource<SearchHistorySection, SearchHistoryItem> {
|
||||
|
||||
let userCellRegister = UICollectionView.CellRegistration<SearchHistoryUserCollectionViewCell, ManagedObjectRecord<MastodonUser>> { cell, indexPath, item in
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = item.object(in: context.managedObjectContext) else { return }
|
||||
cell.configure(viewModel: .init(value: user))
|
||||
}
|
||||
}
|
||||
|
||||
let hashtagCellRegister = UICollectionView.CellRegistration<UICollectionViewListCell, ManagedObjectRecord<Tag>> { cell, indexPath, item in
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let hashtag = item.object(in: context.managedObjectContext) else { return }
|
||||
var contentConfiguration = cell.defaultContentConfiguration()
|
||||
contentConfiguration.text = "#" + hashtag.name
|
||||
cell.contentConfiguration = contentConfiguration
|
||||
}
|
||||
|
||||
var backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell()
|
||||
backgroundConfiguration.backgroundColorTransformer = .init { [weak cell] _ in
|
||||
guard let state = cell?.configurationState else {
|
||||
return ThemeService.shared.currentTheme.value.secondarySystemGroupedBackgroundColor
|
||||
}
|
||||
|
||||
if state.isHighlighted || state.isSelected {
|
||||
return ThemeService.shared.currentTheme.value.tableViewCellSelectionBackgroundColor
|
||||
}
|
||||
return ThemeService.shared.currentTheme.value.secondarySystemGroupedBackgroundColor
|
||||
}
|
||||
cell.backgroundConfiguration = backgroundConfiguration
|
||||
}
|
||||
|
||||
let dataSource = UICollectionViewDiffableDataSource<SearchHistorySection, SearchHistoryItem>(collectionView: collectionView) { collectionView, indexPath, item in
|
||||
switch item {
|
||||
case .account(let objectID):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchResultTableViewCell.self), for: indexPath) as! SearchResultTableViewCell
|
||||
if let user = try? dependency.context.managedObjectContext.existingObject(with: objectID) as? MastodonUser {
|
||||
cell.config(with: user)
|
||||
}
|
||||
return cell
|
||||
case .hashtag(let objectID):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchResultTableViewCell.self), for: indexPath) as! SearchResultTableViewCell
|
||||
if let hashtag = try? dependency.context.managedObjectContext.existingObject(with: objectID) as? Tag {
|
||||
cell.config(with: hashtag)
|
||||
}
|
||||
return cell
|
||||
case .status:
|
||||
// Should not show status in the history list
|
||||
return UITableViewCell()
|
||||
} // end switch
|
||||
} // end UITableViewDiffableDataSource
|
||||
case .user(let record):
|
||||
return collectionView.dequeueConfiguredReusableCell(
|
||||
using: userCellRegister,
|
||||
for: indexPath, item: record)
|
||||
case .hashtag(let record):
|
||||
return collectionView.dequeueConfiguredReusableCell(
|
||||
using: hashtagCellRegister,
|
||||
for: indexPath, item: record)
|
||||
}
|
||||
}
|
||||
|
||||
let trendHeaderRegister = UICollectionView.SupplementaryRegistration<SearchHistorySectionHeaderCollectionReusableView>(elementKind: UICollectionView.elementKindSectionHeader) { [weak dataSource] supplementaryView, elementKind, indexPath in
|
||||
supplementaryView.delegate = configuration.searchHistorySectionHeaderCollectionReusableViewDelegate
|
||||
|
||||
guard let dataSource = dataSource else { return }
|
||||
let sections = dataSource.snapshot().sectionIdentifiers
|
||||
guard indexPath.section < sections.count else { return }
|
||||
let section = sections[indexPath.section]
|
||||
}
|
||||
|
||||
dataSource.supplementaryViewProvider = { (collectionView: UICollectionView, elementKind: String, indexPath: IndexPath) in
|
||||
let fallback = UICollectionReusableView()
|
||||
|
||||
switch elementKind {
|
||||
case UICollectionView.elementKindSectionHeader:
|
||||
return collectionView.dequeueConfiguredReusableSupplementary(using: trendHeaderRegister, for: indexPath)
|
||||
default:
|
||||
assertionFailure()
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
return dataSource
|
||||
} // end func
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// SearchItem.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-1-18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
|
||||
enum SearchItem: Hashable {
|
||||
case trend(Mastodon.Entity.Tag)
|
||||
}
|
|
@ -5,14 +5,15 @@
|
|||
// Created by sxiaojian on 2021/4/6.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
enum SearchResultItem {
|
||||
enum SearchResultItem: Hashable {
|
||||
case user(ManagedObjectRecord<MastodonUser>)
|
||||
case status(ManagedObjectRecord<Status>)
|
||||
case hashtag(tag: Mastodon.Entity.Tag)
|
||||
case account(account: Mastodon.Entity.Account)
|
||||
case status(statusObjectID: NSManagedObjectID, attribute: Item.StatusAttribute)
|
||||
case bottomLoader(attribute: BottomLoaderAttribute)
|
||||
}
|
||||
|
||||
|
@ -26,7 +27,10 @@ extension SearchResultItem {
|
|||
self.isNoResult = isEmptyResult
|
||||
}
|
||||
|
||||
static func == (lhs: SearchResultItem.BottomLoaderAttribute, rhs: SearchResultItem.BottomLoaderAttribute) -> Bool {
|
||||
static func == (
|
||||
lhs: SearchResultItem.BottomLoaderAttribute,
|
||||
rhs: SearchResultItem.BottomLoaderAttribute
|
||||
) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
|
@ -35,60 +39,3 @@ extension SearchResultItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchResultItem: Equatable {
|
||||
static func == (lhs: SearchResultItem, rhs: SearchResultItem) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.hashtag(let tagLeft), .hashtag(let tagRight)):
|
||||
return tagLeft == tagRight
|
||||
case (.account(let accountLeft), .account(let accountRight)):
|
||||
return accountLeft == accountRight
|
||||
case (.status(let idLeft, _), .status(let idRight, _)):
|
||||
return idLeft == idRight
|
||||
case (.bottomLoader(let attributeLeft), .bottomLoader(let attributeRight)):
|
||||
return attributeLeft == attributeRight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchResultItem: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .account(let account):
|
||||
hasher.combine(String(describing: SearchResultItem.account.self))
|
||||
hasher.combine(account.id)
|
||||
case .hashtag(let tag):
|
||||
hasher.combine(String(describing: SearchResultItem.hashtag.self))
|
||||
hasher.combine(tag.name)
|
||||
case .status(let id, _):
|
||||
hasher.combine(id)
|
||||
case .bottomLoader(let attribute):
|
||||
hasher.combine(attribute)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchResultItem {
|
||||
var sortKey: String? {
|
||||
switch self {
|
||||
case .account(let account): return account.displayName.lowercased()
|
||||
case .hashtag(let hashtag): return hashtag.name.lowercased()
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchResultItem {
|
||||
var statusObjectItem: StatusObjectItem? {
|
||||
switch self {
|
||||
case .status(let objectID, _):
|
||||
return .status(objectID: objectID)
|
||||
case .hashtag,
|
||||
.account,
|
||||
.bottomLoader:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,51 +5,70 @@
|
|||
// Created by sxiaojian on 2021/4/6.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonUI
|
||||
|
||||
enum SearchResultSection: Equatable, Hashable {
|
||||
enum SearchResultSection: Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension SearchResultSection {
|
||||
|
||||
static let logger = Logger(subsystem: "SearchResultSection", category: "logic")
|
||||
|
||||
struct Configuration {
|
||||
weak var statusViewTableViewCellDelegate: StatusTableViewCellDelegate?
|
||||
weak var userTableViewCellDelegate: UserTableViewCellDelegate?
|
||||
}
|
||||
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: NeedsDependency,
|
||||
statusTableViewCellDelegate: StatusTableViewCellDelegate
|
||||
tableView: UITableView,
|
||||
context: AppContext,
|
||||
configuration: Configuration
|
||||
) -> UITableViewDiffableDataSource<SearchResultSection, SearchResultItem> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) { [
|
||||
weak statusTableViewCellDelegate
|
||||
] tableView, indexPath, item -> UITableViewCell? in
|
||||
tableView.register(UserTableViewCell.self, forCellReuseIdentifier: String(describing: UserTableViewCell.self))
|
||||
tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self))
|
||||
tableView.register(HashtagTableViewCell.self, forCellReuseIdentifier: String(describing: HashtagTableViewCell.self))
|
||||
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||
|
||||
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
switch item {
|
||||
case .account(let account):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchResultTableViewCell.self), for: indexPath) as! SearchResultTableViewCell
|
||||
cell.config(with: account)
|
||||
return cell
|
||||
case .hashtag(let tag):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchResultTableViewCell.self), for: indexPath) as! SearchResultTableViewCell
|
||||
cell.config(with: tag)
|
||||
return cell
|
||||
case .status(let statusObjectID, let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
if let status = try? dependency.context.managedObjectContext.existingObject(with: statusObjectID) as? Status {
|
||||
let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
let requestUserID = activeMastodonAuthenticationBox?.userID ?? ""
|
||||
StatusSection.configure(
|
||||
cell: cell,
|
||||
case .user(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
timelineContext: .search,
|
||||
dependency: dependency,
|
||||
readableLayoutFrame: tableView.readableContentGuide.layoutFrame,
|
||||
status: status,
|
||||
requestUserID: requestUserID,
|
||||
statusItemAttribute: attribute
|
||||
cell: cell,
|
||||
viewModel: .init(value: .user(user)),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
cell.delegate = statusTableViewCellDelegate
|
||||
return cell
|
||||
case .status(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let status = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .status(status)),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
return cell
|
||||
case .hashtag(let tag):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: HashtagTableViewCell.self)) as! HashtagTableViewCell
|
||||
cell.primaryLabel.configure(content: PlaintextMetaContent(string: "#" + tag.name))
|
||||
return cell
|
||||
case .bottomLoader(let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) as! TimelineBottomLoaderTableViewCell
|
||||
|
@ -63,7 +82,49 @@ extension SearchResultSection {
|
|||
cell.loadMoreLabel.isHidden = true
|
||||
}
|
||||
return cell
|
||||
} // end switch
|
||||
}
|
||||
} // end UITableViewDiffableDataSource
|
||||
} // end func
|
||||
}
|
||||
|
||||
extension SearchResultSection {
|
||||
|
||||
static func configure(
|
||||
context: AppContext,
|
||||
tableView: UITableView,
|
||||
cell: StatusTableViewCell,
|
||||
viewModel: StatusTableViewCell.ViewModel,
|
||||
configuration: Configuration
|
||||
) {
|
||||
StatusSection.setupStatusPollDataSource(
|
||||
context: context,
|
||||
statusView: cell.statusView
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
delegate: configuration.statusViewTableViewCellDelegate
|
||||
)
|
||||
}
|
||||
|
||||
static func configure(
|
||||
context: AppContext,
|
||||
tableView: UITableView,
|
||||
cell: UserTableViewCell,
|
||||
viewModel: UserTableViewCell.ViewModel,
|
||||
configuration: Configuration
|
||||
) {
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
delegate: configuration.userTableViewCellDelegate
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// SearchSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-1-18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
import MastodonLocalization
|
||||
|
||||
enum SearchSection: Hashable {
|
||||
case trend
|
||||
}
|
||||
|
||||
extension SearchSection {
|
||||
|
||||
static func diffableDataSource(
|
||||
collectionView: UICollectionView,
|
||||
context: AppContext
|
||||
) -> UICollectionViewDiffableDataSource<SearchSection, SearchItem> {
|
||||
|
||||
let trendCellRegister = UICollectionView.CellRegistration<TrendCollectionViewCell, Mastodon.Entity.Tag> { cell, indexPath, item in
|
||||
cell.primaryLabel.text = "#" + item.name
|
||||
cell.secondaryLabel.text = L10n.Scene.Search.Recommend.HashTag.peopleTalking(item.talkingPeopleCount ?? 0)
|
||||
|
||||
cell.lineChartView.data = (item.history ?? [])
|
||||
.sorted(by: { $0.day < $1.day }) // latest last
|
||||
.map { entry in
|
||||
guard let point = Int(entry.accounts) else {
|
||||
return .zero
|
||||
}
|
||||
return CGFloat(point)
|
||||
}
|
||||
}
|
||||
|
||||
let dataSource = UICollectionViewDiffableDataSource<SearchSection, SearchItem>(
|
||||
collectionView: collectionView
|
||||
) { collectionView, indexPath, item in
|
||||
switch item {
|
||||
case .trend(let hashtag):
|
||||
let cell = collectionView.dequeueConfiguredReusableCell(
|
||||
using: trendCellRegister,
|
||||
for: indexPath,
|
||||
item: hashtag
|
||||
)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
let trendHeaderRegister = UICollectionView.SupplementaryRegistration<TrendSectionHeaderCollectionReusableView>(elementKind: UICollectionView.elementKindSectionHeader) { supplementaryView, elementKind, indexPath in
|
||||
// do nothing
|
||||
}
|
||||
|
||||
dataSource.supplementaryViewProvider = { [weak dataSource] (collectionView: UICollectionView, elementKind: String, indexPath: IndexPath) in
|
||||
let fallback = UICollectionReusableView()
|
||||
guard let dataSource = dataSource else { return fallback }
|
||||
let sections = dataSource.snapshot().sectionIdentifiers
|
||||
guard indexPath.section < sections.count else { return fallback }
|
||||
let section = sections[indexPath.section]
|
||||
|
||||
switch elementKind {
|
||||
case UICollectionView.elementKindSectionHeader:
|
||||
switch section {
|
||||
case .trend:
|
||||
return collectionView.dequeueConfiguredReusableSupplementary(using: trendHeaderRegister, for: indexPath)
|
||||
}
|
||||
default:
|
||||
assertionFailure()
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
return dataSource
|
||||
} // end func
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum SettingsItem {
|
||||
case appearance(settingObjectID: NSManagedObjectID)
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
enum SettingsSection: Hashable {
|
||||
case appearance
|
||||
|
|
|
@ -1,198 +0,0 @@
|
|||
//
|
||||
// Item.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/1/27.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import DifferenceKit
|
||||
|
||||
/// Note: update Equatable when change case
|
||||
enum Item {
|
||||
// timeline
|
||||
case homeTimelineIndex(objectID: NSManagedObjectID, attribute: StatusAttribute)
|
||||
|
||||
// thread
|
||||
case root(statusObjectID: NSManagedObjectID, attribute: StatusAttribute)
|
||||
case reply(statusObjectID: NSManagedObjectID, attribute: StatusAttribute)
|
||||
case leaf(statusObjectID: NSManagedObjectID, attribute: StatusAttribute)
|
||||
case leafBottomLoader(statusObjectID: NSManagedObjectID)
|
||||
|
||||
// normal list
|
||||
case status(objectID: NSManagedObjectID, attribute: StatusAttribute)
|
||||
|
||||
// loader
|
||||
case homeMiddleLoader(upperTimelineIndexAnchorObjectID: NSManagedObjectID)
|
||||
case publicMiddleLoader(statusID: String)
|
||||
case topLoader
|
||||
case bottomLoader
|
||||
case emptyBottomLoader
|
||||
|
||||
case emptyStateHeader(attribute: EmptyStateHeaderAttribute)
|
||||
|
||||
// reports
|
||||
case reportStatus(objectID: NSManagedObjectID, attribute: ReportStatusAttribute)
|
||||
}
|
||||
|
||||
extension Item {
|
||||
class StatusAttribute {
|
||||
var isSeparatorLineHidden: Bool
|
||||
|
||||
/// is media loaded or not
|
||||
let isImageLoaded = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
/// flag for current sensitive content reveal state
|
||||
///
|
||||
/// - true: displaying sensitive content
|
||||
/// - false: displaying content warning overlay
|
||||
let isRevealing = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
init(isSeparatorLineHidden: Bool = false) {
|
||||
self.isSeparatorLineHidden = isSeparatorLineHidden
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyStateHeaderAttribute: Hashable {
|
||||
let id = UUID()
|
||||
let reason: Reason
|
||||
|
||||
enum Reason: Equatable {
|
||||
case noStatusFound
|
||||
case blocking(name: String?)
|
||||
case blocked(name: String?)
|
||||
case suspended(name: String?)
|
||||
|
||||
static func == (lhs: Item.EmptyStateHeaderAttribute.Reason, rhs: Item.EmptyStateHeaderAttribute.Reason) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.noStatusFound, noStatusFound): return true
|
||||
case (.blocking(let nameLeft), blocking(let nameRight)): return nameLeft == nameRight
|
||||
case (.blocked(let nameLeft), blocked(let nameRight)): return nameLeft == nameRight
|
||||
case (.suspended(let nameLeft), .suspended(let nameRight)): return nameLeft == nameRight
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(reason: Reason) {
|
||||
self.reason = reason
|
||||
}
|
||||
|
||||
static func == (lhs: Item.EmptyStateHeaderAttribute, rhs: Item.EmptyStateHeaderAttribute) -> Bool {
|
||||
return lhs.reason == rhs.reason
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
||||
class ReportStatusAttribute: StatusAttribute {
|
||||
var isSelected: Bool
|
||||
|
||||
init(isSeparatorLineHidden: Bool = false, isSelected: Bool = false) {
|
||||
self.isSelected = isSelected
|
||||
super.init(isSeparatorLineHidden: isSeparatorLineHidden)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Item: Equatable {
|
||||
static func == (lhs: Item, rhs: Item) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.homeTimelineIndex(let objectIDLeft, _), .homeTimelineIndex(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.root(let objectIDLeft, _), .root(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.reply(let objectIDLeft, _), .reply(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.leaf(let objectIDLeft, _), .leaf(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.leafBottomLoader(let objectIDLeft), .leafBottomLoader(let objectIDRight)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.status(let objectIDLeft, _), .status(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
case (.homeMiddleLoader(let upperLeft), .homeMiddleLoader(let upperRight)):
|
||||
return upperLeft == upperRight
|
||||
case (.publicMiddleLoader(let upperLeft), .publicMiddleLoader(let upperRight)):
|
||||
return upperLeft == upperRight
|
||||
case (.topLoader, .topLoader):
|
||||
return true
|
||||
case (.bottomLoader, .bottomLoader):
|
||||
return true
|
||||
case (.emptyBottomLoader, .emptyBottomLoader):
|
||||
return true
|
||||
case (.emptyStateHeader(let attributeLeft), .emptyStateHeader(let attributeRight)):
|
||||
return attributeLeft == attributeRight
|
||||
case (.reportStatus(let objectIDLeft, _), .reportStatus(let objectIDRight, _)):
|
||||
return objectIDLeft == objectIDRight
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Item: Hashable {
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .homeTimelineIndex(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
case .root(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
case .reply(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
case .leaf(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
case .leafBottomLoader(let objectID):
|
||||
hasher.combine(objectID)
|
||||
case .status(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
case .homeMiddleLoader(upperTimelineIndexAnchorObjectID: let upper):
|
||||
hasher.combine(String(describing: Item.homeMiddleLoader.self))
|
||||
hasher.combine(upper)
|
||||
case .publicMiddleLoader(let upper):
|
||||
hasher.combine(String(describing: Item.publicMiddleLoader.self))
|
||||
hasher.combine(upper)
|
||||
case .topLoader:
|
||||
hasher.combine(String(describing: Item.topLoader.self))
|
||||
case .bottomLoader:
|
||||
hasher.combine(String(describing: Item.bottomLoader.self))
|
||||
case .emptyBottomLoader:
|
||||
hasher.combine(String(describing: Item.emptyBottomLoader.self))
|
||||
case .emptyStateHeader(let attribute):
|
||||
hasher.combine(attribute)
|
||||
case .reportStatus(let objectID, _):
|
||||
hasher.combine(objectID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Item: Differentiable { }
|
||||
|
||||
extension Item {
|
||||
var statusObjectItem: StatusObjectItem? {
|
||||
switch self {
|
||||
case .homeTimelineIndex(let objectID, _):
|
||||
return .homeTimelineIndex(objectID: objectID)
|
||||
case .root(let objectID, _),
|
||||
.reply(let objectID, _),
|
||||
.leaf(let objectID, _),
|
||||
.status(let objectID, _),
|
||||
.reportStatus(let objectID, _):
|
||||
return .status(objectID: objectID)
|
||||
case .leafBottomLoader,
|
||||
.homeMiddleLoader,
|
||||
.publicMiddleLoader,
|
||||
.topLoader,
|
||||
.bottomLoader,
|
||||
.emptyBottomLoader,
|
||||
.emptyStateHeader:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
//
|
||||
// ReportSection.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by ihugo on 2021/4/20.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
import AVKit
|
||||
import os.log
|
||||
|
||||
enum ReportSection: Equatable, Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
extension ReportSection {
|
||||
static func tableViewDiffableDataSource(
|
||||
for tableView: UITableView,
|
||||
dependency: ReportViewController,
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
timestampUpdatePublisher: AnyPublisher<Date, Never>
|
||||
) -> UITableViewDiffableDataSource<ReportSection, Item> {
|
||||
UITableViewDiffableDataSource(tableView: tableView) {[
|
||||
weak dependency
|
||||
] tableView, indexPath, item -> UITableViewCell? in
|
||||
guard let dependency = dependency else { return UITableViewCell() }
|
||||
|
||||
switch item {
|
||||
case .reportStatus(let objectID, let attribute):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ReportedStatusTableViewCell.self), for: indexPath) as! ReportedStatusTableViewCell
|
||||
cell.dependency = dependency
|
||||
let activeMastodonAuthenticationBox = dependency.context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
let requestUserID = activeMastodonAuthenticationBox?.userID ?? ""
|
||||
managedObjectContext.performAndWait { [weak dependency] in
|
||||
guard let dependency = dependency else { return }
|
||||
let status = managedObjectContext.object(with: objectID) as! Status
|
||||
StatusSection.configure(
|
||||
cell: cell,
|
||||
tableView: tableView,
|
||||
timelineContext: .report,
|
||||
dependency: dependency,
|
||||
readableLayoutFrame: tableView.readableContentGuide.layoutFrame,
|
||||
status: status,
|
||||
requestUserID: requestUserID,
|
||||
statusItemAttribute: attribute
|
||||
)
|
||||
}
|
||||
|
||||
// defalut to select the report status
|
||||
if attribute.isSelected {
|
||||
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
||||
} else {
|
||||
tableView.deselectRow(at: indexPath, animated: false)
|
||||
}
|
||||
|
||||
return cell
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// StatusItem.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK on 2022-1-11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
|
||||
enum StatusItem: Hashable {
|
||||
case feed(record: ManagedObjectRecord<Feed>)
|
||||
case feedLoader(record: ManagedObjectRecord<Feed>)
|
||||
case status(record: ManagedObjectRecord<Status>)
|
||||
// case statusLoader(record: ManagedObjectRecord<Status>, context: StatusLoaderContext)
|
||||
case thread(Thread)
|
||||
case topLoader
|
||||
case bottomLoader
|
||||
}
|
||||
|
||||
//extension StatusItem {
|
||||
// final class StatusLoaderContext: Hashable {
|
||||
// let id = UUID()
|
||||
// @Published var isFetching = false
|
||||
//
|
||||
// static func == (
|
||||
// lhs: StatusItem.StatusLoaderContext,
|
||||
// rhs: StatusItem.StatusLoaderContext
|
||||
// ) -> Bool {
|
||||
// return lhs.id == rhs.id
|
||||
// }
|
||||
//
|
||||
// func hash(into hasher: inout Hasher) {
|
||||
// hasher.combine(id)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
extension StatusItem {
|
||||
enum Thread: Hashable {
|
||||
case root(context: Context)
|
||||
case reply(context: Context)
|
||||
case leaf(context: Context)
|
||||
|
||||
public var record: ManagedObjectRecord<Status> {
|
||||
switch self {
|
||||
case .root(let threadContext),
|
||||
.reply(let threadContext),
|
||||
.leaf(let threadContext):
|
||||
return threadContext.status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusItem.Thread {
|
||||
class Context: Hashable {
|
||||
let status: ManagedObjectRecord<Status>
|
||||
var displayUpperConversationLink: Bool
|
||||
var displayBottomConversationLink: Bool
|
||||
|
||||
init(
|
||||
status: ManagedObjectRecord<Status>,
|
||||
displayUpperConversationLink: Bool = false,
|
||||
displayBottomConversationLink: Bool = false
|
||||
) {
|
||||
self.status = status
|
||||
self.displayUpperConversationLink = displayUpperConversationLink
|
||||
self.displayBottomConversationLink = displayBottomConversationLink
|
||||
}
|
||||
|
||||
static func == (lhs: StatusItem.Thread.Context, rhs: StatusItem.Thread.Context) -> Bool {
|
||||
return lhs.status == rhs.status
|
||||
&& lhs.displayUpperConversationLink == rhs.displayUpperConversationLink
|
||||
&& lhs.displayBottomConversationLink == rhs.displayBottomConversationLink
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(status)
|
||||
hasher.combine(displayUpperConversationLink)
|
||||
hasher.combine(displayBottomConversationLink)
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue