forked from zelo72/mastodon-ios
feat: add recent search history in sidebar
This commit is contained in:
parent
d35f163623
commit
1da803fb97
CoreDataStack
CoreData.xcdatamodeld
Entity
Mastodon.xcodeproj
Mastodon
Info.plist
Scene
Account/Cell
HashtagTimeline
HomeTimeline
Root
Search/SearchDetail
SearchHistory
SearchResult
TableViewCell
Settings
Service/APIService/CoreData
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>CoreData 2.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,298 @@
|
|||
<?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="">
|
||||
<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="Attachment" representedClassName=".Attachment" syncable="YES">
|
||||
<attribute name="blurhash" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="descriptionString" optional="YES" attributeType="String"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="index" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="metaData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="previewRemoteURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="previewURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="remoteURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="textURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="typeRaw" attributeType="String"/>
|
||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="mediaAttachments" 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="History" representedClassName=".History" syncable="YES">
|
||||
<attribute name="accounts" optional="YES" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="day" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="identifier" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="uses" optional="YES" attributeType="String"/>
|
||||
<relationship name="tag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag" inverseName="histories" inverseEntity="Tag"/>
|
||||
</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" inverseName="homeTimelineIndexes" inverseEntity="Status"/>
|
||||
</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="user" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mastodonAuthentication" inverseEntity="MastodonUser"/>
|
||||
</entity>
|
||||
<entity name="MastodonNotification" representedClassName=".MastodonNotification" syncable="YES">
|
||||
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<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="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="inNotifications" inverseEntity="Status"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</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="emojisData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="fieldsData" 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" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="blockingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="blockingBy" optional="YES" 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" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="domainBlocking" inverseEntity="MastodonUser"/>
|
||||
<relationship name="endorsed" optional="YES" 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" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="favouritedBy" inverseEntity="Status"/>
|
||||
<relationship name="following" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followingBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="following" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequested" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="followRequestedBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="followRequestedBy" optional="YES" 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" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="mutedBy" inverseEntity="Status"/>
|
||||
<relationship name="muting" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mutingBy" inverseEntity="MastodonUser"/>
|
||||
<relationship name="mutingBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muting" inverseEntity="MastodonUser"/>
|
||||
<relationship name="notifications" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonNotification" inverseName="account" inverseEntity="MastodonNotification"/>
|
||||
<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" optional="YES" 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" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PollOption" inverseName="votedBy" inverseEntity="PollOption"/>
|
||||
<relationship name="votePolls" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Poll" inverseName="votedBy" inverseEntity="Poll"/>
|
||||
</entity>
|
||||
<entity name="Mention" representedClassName=".Mention" syncable="YES">
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="index" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="status" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="mentions" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<entity name="Poll" representedClassName=".Poll" syncable="YES">
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expired" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="expiresAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<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="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="appearanceRaw" attributeType="String"/>
|
||||
<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="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="emojisData" 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="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" attributeType="String"/>
|
||||
<attribute name="visibility" optional="YES" attributeType="String"/>
|
||||
<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" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="bookmarked" inverseEntity="MastodonUser"/>
|
||||
<relationship name="favouritedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
|
||||
<relationship name="homeTimelineIndexes" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="HomeTimelineIndex" inverseName="status" inverseEntity="HomeTimelineIndex"/>
|
||||
<relationship name="inNotifications" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MastodonNotification" inverseName="status" inverseEntity="MastodonNotification"/>
|
||||
<relationship name="mediaAttachments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Attachment" inverseName="status" inverseEntity="Attachment"/>
|
||||
<relationship name="mentions" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Mention" inverseName="status" inverseEntity="Mention"/>
|
||||
<relationship name="mutedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
|
||||
<relationship name="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" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Status" inverseName="reblog" inverseEntity="Status"/>
|
||||
<relationship name="rebloggedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="reblogged" inverseEntity="MastodonUser"/>
|
||||
<relationship name="replyFrom" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="replyTo" inverseEntity="Status"/>
|
||||
<relationship name="replyTo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="replyFrom" inverseEntity="Status"/>
|
||||
<relationship name="searchHistories" toMany="YES" deletionRule="Nullify" 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="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="histories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="History" inverseName="tag" inverseEntity="History"/>
|
||||
<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="Attachment" positionX="0" positionY="0" width="128" height="254"/>
|
||||
<element name="DomainBlock" positionX="45" positionY="162" width="128" height="89"/>
|
||||
<element name="Emoji" positionX="0" positionY="0" width="128" height="134"/>
|
||||
<element name="History" positionX="0" positionY="0" width="128" height="119"/>
|
||||
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
|
||||
<element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/>
|
||||
<element name="MastodonNotification" positionX="9" positionY="162" width="128" height="164"/>
|
||||
<element name="MastodonUser" positionX="0" positionY="0" width="128" height="734"/>
|
||||
<element name="Mention" positionX="0" positionY="0" width="128" height="149"/>
|
||||
<element name="Poll" positionX="0" positionY="0" width="128" height="194"/>
|
||||
<element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
|
||||
<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="179"/>
|
||||
<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="134"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -43,11 +43,11 @@ final public class MastodonUser: NSManagedObject {
|
|||
// one-to-one relationship
|
||||
@NSManaged public private(set) var pinnedStatus: Status?
|
||||
@NSManaged public private(set) var mastodonAuthentication: MastodonAuthentication?
|
||||
@NSManaged public private(set) var searchHistory: SearchHistory?
|
||||
|
||||
// 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>?
|
||||
|
@ -274,6 +274,15 @@ extension MastodonUser {
|
|||
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -16,7 +16,7 @@ public final class SearchHistory: NSManagedObject {
|
|||
@NSManaged public private(set) var createAt: Date
|
||||
@NSManaged public private(set) var updatedAt: Date
|
||||
|
||||
// one-to-one relationship
|
||||
// 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?
|
||||
|
@ -31,10 +31,10 @@ extension SearchHistory {
|
|||
setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.updatedAt))
|
||||
}
|
||||
|
||||
public override func willSave() {
|
||||
super.willSave()
|
||||
setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.updatedAt))
|
||||
}
|
||||
// public override func willSave() {
|
||||
// super.willSave()
|
||||
// setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.updatedAt))
|
||||
// }
|
||||
|
||||
@discardableResult
|
||||
public static func insert(
|
||||
|
|
|
@ -52,18 +52,18 @@ public final class Status: NSManagedObject {
|
|||
// one-to-one relationship
|
||||
@NSManaged public private(set) var pinnedBy: MastodonUser?
|
||||
@NSManaged public private(set) var poll: Poll?
|
||||
@NSManaged public private(set) var searchHistory: SearchHistory?
|
||||
|
||||
// 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 tags: Set<Tag>?
|
||||
@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?
|
||||
|
@ -81,7 +81,6 @@ extension Status {
|
|||
replyTo: Status?,
|
||||
poll: Poll?,
|
||||
mentions: [Mention]?,
|
||||
tags: [Tag]?,
|
||||
mediaAttachments: [Attachment]?,
|
||||
favouritedBy: MastodonUser?,
|
||||
rebloggedBy: MastodonUser?,
|
||||
|
@ -126,9 +125,6 @@ extension Status {
|
|||
if let mentions = mentions {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.mentions)).addObjects(from: mentions)
|
||||
}
|
||||
if let tags = tags {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.tags)).addObjects(from: tags)
|
||||
}
|
||||
if let mediaAttachments = mediaAttachments {
|
||||
status.mutableSetValue(forKey: #keyPath(Status.mediaAttachments)).addObjects(from: mediaAttachments)
|
||||
}
|
||||
|
|
|
@ -18,13 +18,12 @@ public final class Tag: NSManagedObject {
|
|||
@NSManaged public private(set) var url: String
|
||||
|
||||
// one-to-one relationship
|
||||
@NSManaged public private(set) var searchHistory: SearchHistory?
|
||||
|
||||
// many-to-many relationship
|
||||
@NSManaged public private(set) var statuses: Set<Status>?
|
||||
|
||||
// one-to-many relationship
|
||||
@NSManaged public private(set) var histories: Set<History>?
|
||||
@NSManaged public private(set) var searchHistories: Set<SearchHistory>
|
||||
}
|
||||
|
||||
public extension Tag {
|
||||
|
@ -55,6 +54,15 @@ public extension 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
|
||||
|
|
|
@ -1352,6 +1352,7 @@
|
|||
DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteViewController+Provider.swift"; sourceTree = "<group>"; };
|
||||
DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewControllerAspect.swift; sourceTree = "<group>"; };
|
||||
DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreference.swift; sourceTree = "<group>"; };
|
||||
DBF156DD27006F5D00EC00B7 /* CoreData 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "CoreData 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DBF1D24D269DAF5D00C1C08A /* SearchDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDetailViewController.swift; sourceTree = "<group>"; };
|
||||
DBF1D250269DB01200C1C08A /* SearchHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistoryViewController.swift; sourceTree = "<group>"; };
|
||||
DBF1D256269DBAC600C1C08A /* SearchDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDetailViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -6129,9 +6130,10 @@
|
|||
DB89BA3525C1145C008580ED /* CoreData.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DBF156DD27006F5D00EC00B7 /* CoreData 2.xcdatamodel */,
|
||||
DB89BA3625C1145C008580ED /* CoreData.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DB89BA3625C1145C008580ED /* CoreData.xcdatamodel */;
|
||||
currentVersion = DBF156DD27006F5D00EC00B7 /* CoreData 2.xcdatamodel */;
|
||||
path = CoreData.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>42</integer>
|
||||
<integer>38</integer>
|
||||
</dict>
|
||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>43</integer>
|
||||
<integer>35</integer>
|
||||
</dict>
|
||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -97,7 +97,7 @@
|
|||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>44</integer>
|
||||
<integer>37</integer>
|
||||
</dict>
|
||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -117,7 +117,7 @@
|
|||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>41</integer>
|
||||
<integer>36</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDuration</key>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
|
|
@ -109,6 +109,9 @@ extension AccountListTableViewCell {
|
|||
separatorLine.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
|
||||
])
|
||||
|
||||
badgeButton.setBadge(number: 0)
|
||||
checkmarkImageView.isHidden = true
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -85,16 +85,20 @@ final class HashtagTimelineViewModel: NSObject {
|
|||
return
|
||||
}
|
||||
let query = Mastodon.API.V2.Search.Query(q: hashtag, type: .hashtags)
|
||||
context.apiService.search(domain: activeMastodonAuthenticationBox.domain, query: query, mastodonAuthenticationBox: activeMastodonAuthenticationBox)
|
||||
.sink { _ in
|
||||
|
||||
} receiveValue: { [weak self] response in
|
||||
let matchedTag = response.value.hashtags.first { tag -> Bool in
|
||||
return tag.name == self?.hashtag
|
||||
}
|
||||
self?.hashtagEntity.send(matchedTag)
|
||||
context.apiService.search(
|
||||
domain: activeMastodonAuthenticationBox.domain,
|
||||
query: query,
|
||||
mastodonAuthenticationBox: activeMastodonAuthenticationBox
|
||||
)
|
||||
.sink { _ in
|
||||
|
||||
} receiveValue: { [weak self] response in
|
||||
let matchedTag = response.value.hashtags.first { tag -> Bool in
|
||||
return tag.name == self?.hashtag
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
self?.hashtagEntity.send(matchedTag)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,17 @@ extension HomeTimelineViewController {
|
|||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] displaySettingBarButtonItem in
|
||||
guard let self = self else { return }
|
||||
#if DEBUG
|
||||
// display debug menu
|
||||
self.navigationItem.leftBarButtonItem = {
|
||||
let barButtonItem = UIBarButtonItem()
|
||||
barButtonItem.image = UIImage(systemName: "ellipsis.circle")
|
||||
barButtonItem.menu = self.debugMenu
|
||||
return barButtonItem
|
||||
}()
|
||||
#else
|
||||
self.navigationItem.leftBarButtonItem = displaySettingBarButtonItem ? self.settingBarButtonItem : nil
|
||||
#endif
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
navigationItem.titleView = titleView
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
|
||||
final class RootSplitViewController: UISplitViewController, NeedsDependency {
|
||||
|
||||
|
@ -117,6 +118,7 @@ extension RootSplitViewController {
|
|||
|
||||
// MARK: - SidebarViewControllerDelegate
|
||||
extension RootSplitViewController: SidebarViewControllerDelegate {
|
||||
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) {
|
||||
|
||||
guard let index = MainTabBarController.Tab.allCases.firstIndex(of: tab) else {
|
||||
|
@ -126,6 +128,30 @@ extension RootSplitViewController: SidebarViewControllerDelegate {
|
|||
currentSupplementaryTab = tab
|
||||
setViewController(supplementaryViewControllers[index], for: .supplementary)
|
||||
}
|
||||
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectSearchHistory searchHistoryViewModel: SidebarViewModel.SearchHistoryViewModel) {
|
||||
// self.sidebarViewController(sidebarViewController, didSelectTab: .search)
|
||||
|
||||
let supplementaryViewController = viewController(for: .supplementary)
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
managedObjectContext.perform {
|
||||
let searchHistory = managedObjectContext.object(with: searchHistoryViewModel.searchHistoryObjectID) as! SearchHistory
|
||||
if let account = searchHistory.account {
|
||||
DispatchQueue.main.async {
|
||||
let profileViewModel = CachedProfileViewModel(context: self.context, mastodonUser: account)
|
||||
self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: supplementaryViewController, transition: .show)
|
||||
}
|
||||
} else if let hashtag = searchHistory.hashtag {
|
||||
DispatchQueue.main.async {
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: self.context, hashtag: hashtag.name)
|
||||
self.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: supplementaryViewController, transition: .show)
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UISplitViewControllerDelegate
|
||||
|
|
|
@ -12,6 +12,7 @@ import CoreDataStack
|
|||
|
||||
protocol SidebarViewControllerDelegate: AnyObject {
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab)
|
||||
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectSearchHistory searchHistoryViewModel: SidebarViewModel.SearchHistoryViewModel)
|
||||
}
|
||||
|
||||
final class SidebarViewController: UIViewController, NeedsDependency {
|
||||
|
@ -34,6 +35,13 @@ final class SidebarViewController: UIViewController, NeedsDependency {
|
|||
static func createLayout() -> UICollectionViewLayout {
|
||||
let layout = UICollectionViewCompositionalLayout() { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
|
||||
var configuration = UICollectionLayoutListConfiguration(appearance: .sidebar)
|
||||
if sectionIndex == SidebarViewModel.Section.tab.rawValue {
|
||||
// with indentation
|
||||
configuration.headerMode = .none
|
||||
} else {
|
||||
// remove indentation
|
||||
configuration.headerMode = .firstItemInSection
|
||||
}
|
||||
configuration.showsSeparators = false
|
||||
let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
|
||||
return section
|
||||
|
@ -59,6 +67,11 @@ extension SidebarViewController {
|
|||
.sink { [weak self] activeMastodonAuthenticationBox in
|
||||
guard let self = self else { return }
|
||||
let domain = activeMastodonAuthenticationBox?.domain
|
||||
self.navigationItem.backBarButtonItem = {
|
||||
let barButtonItem = UIBarButtonItem()
|
||||
barButtonItem.image = UIImage(systemName: "sidebar.leading")
|
||||
return barButtonItem
|
||||
}()
|
||||
self.navigationItem.title = domain
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
@ -121,6 +134,10 @@ extension SidebarViewController: UICollectionViewDelegate {
|
|||
switch item {
|
||||
case .tab(let tab):
|
||||
delegate?.sidebarViewController(self, didSelectTab: tab)
|
||||
case .searchHistory(let viewModel):
|
||||
delegate?.sidebarViewController(self, didSelectSearchHistory: viewModel)
|
||||
case .header:
|
||||
break
|
||||
case .account(let viewModel):
|
||||
assert(Thread.isMainThread)
|
||||
let authentication = context.managedObjectContext.object(with: viewModel.authenticationObjectID) as! MastodonAuthentication
|
||||
|
@ -133,9 +150,6 @@ extension SidebarViewController: UICollectionViewDelegate {
|
|||
.store(in: &disposeBag)
|
||||
case .addAccount:
|
||||
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
|
||||
default:
|
||||
// TODO:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,30 +18,51 @@ final class SidebarViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
|
||||
let searchHistoryFetchedResultController: SearchHistoryFetchedResultController
|
||||
|
||||
// output
|
||||
var diffableDataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||
let activeMastodonAuthenticationObjectID = CurrentValueSubject<NSManagedObjectID?, Never>(nil)
|
||||
|
||||
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
self.searchHistoryFetchedResultController = SearchHistoryFetchedResultController(managedObjectContext: context.managedObjectContext)
|
||||
|
||||
context.authenticationService.activeMastodonAuthentication
|
||||
.sink { [weak self] authentication in
|
||||
guard let self = self else { return }
|
||||
// bind search history
|
||||
self.searchHistoryFetchedResultController.domain.value = authentication?.domain
|
||||
self.searchHistoryFetchedResultController.userID.value = authentication?.userID
|
||||
|
||||
// bind objectID
|
||||
self.activeMastodonAuthenticationObjectID.value = authentication?.objectID
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
try? searchHistoryFetchedResultController.fetchedResultsController.performFetch()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SidebarViewModel {
|
||||
enum Section: Hashable, CaseIterable {
|
||||
enum Section: Int, Hashable, CaseIterable {
|
||||
case tab
|
||||
case account
|
||||
}
|
||||
|
||||
enum Item: Hashable {
|
||||
case tab(MainTabBarController.Tab)
|
||||
case searchHistory(SearchHistoryViewModel)
|
||||
case header(HeaderViewModel)
|
||||
case account(AccountViewModel)
|
||||
case addAccount
|
||||
}
|
||||
|
||||
struct SearchHistoryViewModel: Hashable {
|
||||
let searchHistoryObjectID: NSManagedObjectID
|
||||
}
|
||||
|
||||
struct HeaderViewModel: Hashable {
|
||||
let title: String
|
||||
}
|
||||
|
@ -59,7 +80,9 @@ extension SidebarViewModel {
|
|||
func setupDiffableDataSource(
|
||||
collectionView: UICollectionView
|
||||
) {
|
||||
let tabCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, MainTabBarController.Tab> { (cell, indexPath, item) in
|
||||
let tabCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, MainTabBarController.Tab> { [weak self] cell, indexPath, item in
|
||||
guard let self = self else { return }
|
||||
|
||||
let imageURL: URL? = {
|
||||
switch item {
|
||||
case .me:
|
||||
|
@ -79,13 +102,76 @@ extension SidebarViewModel {
|
|||
return PlaintextMetaContent(string: item.title)
|
||||
}
|
||||
}()
|
||||
let needsOutlineDisclosure = item == .search
|
||||
cell.item = SidebarListContentView.Item(
|
||||
image: item.sidebarImage,
|
||||
imageURL: imageURL,
|
||||
headline: headline,
|
||||
subheadline: nil
|
||||
subheadline: nil,
|
||||
needsOutlineDisclosure: needsOutlineDisclosure
|
||||
)
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
|
||||
switch item {
|
||||
case .notification:
|
||||
Publishers.CombineLatest(
|
||||
self.context.authenticationService.activeMastodonAuthentication,
|
||||
self.context.notificationService.unreadNotificationCountDidUpdate
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] authentication, _ in
|
||||
guard let cell = cell else { return }
|
||||
let hasUnreadPushNotification: Bool = authentication.flatMap { authentication in
|
||||
let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.userAccessToken)
|
||||
return count > 0
|
||||
} ?? false
|
||||
|
||||
let image = hasUnreadPushNotification ? UIImage(systemName: "bell.badge")! : UIImage(systemName: "bell")!
|
||||
cell._contentView?.imageView.image = image
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let searchHistoryCellRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, SearchHistoryViewModel> { [weak self] cell, indexPath, item in
|
||||
guard let self = self else { return }
|
||||
let managedObjectContext = self.searchHistoryFetchedResultController.fetchedResultsController.managedObjectContext
|
||||
|
||||
guard let searchHistory = try? managedObjectContext.existingObject(with: item.searchHistoryObjectID) as? SearchHistory else { return }
|
||||
|
||||
if let account = searchHistory.account {
|
||||
let headline: MetaContent = {
|
||||
do {
|
||||
let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojiMeta)
|
||||
return try MastodonMetaContent.convert(document: content)
|
||||
} catch {
|
||||
return PlaintextMetaContent(string: account.displayNameWithFallback)
|
||||
}
|
||||
}()
|
||||
cell.item = SidebarListContentView.Item(
|
||||
image: .placeholder(color: .systemFill),
|
||||
imageURL: account.avatarImageURL(),
|
||||
headline: headline,
|
||||
subheadline: PlaintextMetaContent(string: "@" + account.acctWithDomain),
|
||||
needsOutlineDisclosure: false
|
||||
)
|
||||
} else if let hashtag = searchHistory.hashtag {
|
||||
let image = UIImage(systemName: "number.square.fill")!.withRenderingMode(.alwaysTemplate)
|
||||
let headline = PlaintextMetaContent(string: "#" + hashtag.name)
|
||||
cell.item = SidebarListContentView.Item(
|
||||
image: image,
|
||||
imageURL: nil,
|
||||
headline: headline,
|
||||
subheadline: nil,
|
||||
needsOutlineDisclosure: false
|
||||
)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
}
|
||||
|
||||
let headerRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, HeaderViewModel> { (cell, indexPath, item) in
|
||||
|
@ -95,7 +181,9 @@ extension SidebarViewModel {
|
|||
cell.accessories = [.outlineDisclosure()]
|
||||
}
|
||||
|
||||
let accountRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, AccountViewModel> { (cell, indexPath, item) in
|
||||
let accountRegistration = UICollectionView.CellRegistration<SidebarListCollectionViewCell, AccountViewModel> { [weak self] (cell, indexPath, item) in
|
||||
guard let self = self else { return }
|
||||
|
||||
let authentication = AppContext.shared.managedObjectContext.object(with: item.authenticationObjectID) as! MastodonAuthentication
|
||||
let user = authentication.user
|
||||
let imageURL = user.avatarImageURL()
|
||||
|
@ -111,15 +199,37 @@ extension SidebarViewModel {
|
|||
image: .placeholder(color: .systemFill),
|
||||
imageURL: imageURL,
|
||||
headline: headline,
|
||||
subheadline: PlaintextMetaContent(string: "@" + user.acctWithDomain)
|
||||
subheadline: PlaintextMetaContent(string: "@" + user.acctWithDomain),
|
||||
needsOutlineDisclosure: false
|
||||
)
|
||||
cell.setNeedsUpdateConfiguration()
|
||||
|
||||
// FIXME: use notification, not timer
|
||||
let accessToken = authentication.userAccessToken
|
||||
AppContext.shared.timestampUpdatePublisher
|
||||
.map { _ in UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken) }
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] count in
|
||||
guard let cell = cell else { return }
|
||||
cell._contentView?.badgeButton.setBadge(number: count)
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
let authenticationObjectID = item.authenticationObjectID
|
||||
self.activeMastodonAuthenticationObjectID
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak cell] objectID in
|
||||
guard let cell = cell else { return }
|
||||
cell._contentView?.checkmarkImageView.isHidden = authenticationObjectID != objectID
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
}
|
||||
|
||||
let addAccountRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, AddAccountViewModel> { (cell, indexPath, item) in
|
||||
var content = cell.defaultContentConfiguration()
|
||||
content.text = L10n.Scene.AccountList.addAccount
|
||||
content.image = nil
|
||||
content.image = UIImage(systemName: "plus.square.fill")!
|
||||
cell.contentConfiguration = content
|
||||
cell.accessories = []
|
||||
}
|
||||
|
@ -128,6 +238,8 @@ extension SidebarViewModel {
|
|||
switch item {
|
||||
case .tab(let tab):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: tabCellRegistration, for: indexPath, item: tab)
|
||||
case .searchHistory(let viewModel):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: searchHistoryCellRegistration, for: indexPath, item: viewModel)
|
||||
case .header(let viewModel):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: headerRegistration, for: indexPath, item: viewModel)
|
||||
case .account(let viewModel):
|
||||
|
@ -164,6 +276,38 @@ extension SidebarViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
// update .search tab
|
||||
searchHistoryFetchedResultController.objectIDs
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] objectIDs in
|
||||
guard let self = self else { return }
|
||||
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||
|
||||
// update .search tab
|
||||
var sectionSnapshot = diffableDataSource.snapshot(for: .tab)
|
||||
|
||||
// remove children
|
||||
let searchHistorySnapshot = sectionSnapshot.snapshot(of: .tab(.search))
|
||||
sectionSnapshot.delete(searchHistorySnapshot.items)
|
||||
|
||||
// append children
|
||||
let managedObjectContext = self.searchHistoryFetchedResultController.fetchedResultsController.managedObjectContext
|
||||
let items: [Item] = objectIDs.compactMap { objectID -> Item? in
|
||||
guard let searchHistory = try? managedObjectContext.existingObject(with: objectID) as? SearchHistory else { return nil }
|
||||
guard searchHistory.account != nil || searchHistory.hashtag != nil else { return nil }
|
||||
let viewModel = SearchHistoryViewModel(searchHistoryObjectID: objectID)
|
||||
return Item.searchHistory(viewModel)
|
||||
}
|
||||
sectionSnapshot.append(Array(items.prefix(5)), to: .tab(.search))
|
||||
sectionSnapshot.expand([.tab(.search)])
|
||||
|
||||
// apply snapshot
|
||||
diffableDataSource.apply(sectionSnapshot, to: .tab, animatingDifferences: false)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// update .me tab and .account section
|
||||
context.authenticationService.mastodonAuthentications
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] authentications in
|
||||
|
|
|
@ -6,11 +6,29 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
final class SidebarListCollectionViewCell: UICollectionViewListCell {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
var item: SidebarListContentView.Item?
|
||||
|
||||
var _contentView: SidebarListContentView? {
|
||||
guard let view = contentView as? SidebarListContentView else {
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
disposeBag.removeAll()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
|
@ -44,5 +62,19 @@ extension SidebarListCollectionViewCell {
|
|||
|
||||
|
||||
backgroundConfiguration = newBackgroundConfiguration
|
||||
|
||||
let needsOutlineDisclosure = item?.needsOutlineDisclosure ?? false
|
||||
if !needsOutlineDisclosure {
|
||||
accessories = []
|
||||
} else {
|
||||
let tintColor: UIColor = state.isHighlighted || state.isSelected ? .white : Asset.Colors.brandBlue.color
|
||||
accessories = [
|
||||
UICellAccessory.outlineDisclosure(
|
||||
displayed: .always,
|
||||
options: UICellAccessory.OutlineDisclosureOptions(tintColor: tintColor),
|
||||
actionHandler: nil
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,13 @@ final class SidebarListContentView: UIView, UIContentView {
|
|||
let animationImageView = FLAnimatedImageView() // for animation image
|
||||
let headlineLabel = MetaLabel(style: .sidebarHeadline(isSelected: false))
|
||||
let subheadlineLabel = MetaLabel(style: .sidebarSubheadline(isSelected: false))
|
||||
let badgeButton = BadgeButton()
|
||||
let checkmarkImageView: UIImageView = {
|
||||
let image = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 17, weight: .semibold))
|
||||
let imageView = UIImageView(image: image)
|
||||
imageView.tintColor = .label
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private var currentConfiguration: ContentConfiguration!
|
||||
var configuration: UIContentConfiguration {
|
||||
|
@ -85,7 +92,7 @@ extension SidebarListContentView {
|
|||
NSLayoutConstraint.activate([
|
||||
textContainer.topAnchor.constraint(equalTo: topAnchor, constant: 10),
|
||||
textContainer.leadingAnchor.constraint(equalTo: imageViewContainer.trailingAnchor, constant: 10),
|
||||
textContainer.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
// textContainer.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
bottomAnchor.constraint(equalTo: textContainer.bottomAnchor, constant: 12),
|
||||
])
|
||||
|
||||
|
@ -96,11 +103,32 @@ extension SidebarListContentView {
|
|||
subheadlineLabel.setContentHuggingPriority(.required - 10, for: .vertical)
|
||||
subheadlineLabel.setContentCompressionResistancePriority(.required - 10, for: .vertical)
|
||||
|
||||
badgeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(badgeButton)
|
||||
NSLayoutConstraint.activate([
|
||||
badgeButton.leadingAnchor.constraint(equalTo: textContainer.trailingAnchor, constant: 4),
|
||||
badgeButton.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
badgeButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 16).priority(.required - 1),
|
||||
badgeButton.widthAnchor.constraint(equalTo: badgeButton.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
])
|
||||
badgeButton.setContentHuggingPriority(.required - 10, for: .horizontal)
|
||||
badgeButton.setContentCompressionResistancePriority(.required - 10, for: .horizontal)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
imageViewContainer.heightAnchor.constraint(equalTo: headlineLabel.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
imageViewContainer.widthAnchor.constraint(equalTo: imageViewContainer.heightAnchor, multiplier: 1.0).priority(.required - 1),
|
||||
])
|
||||
|
||||
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(checkmarkImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
checkmarkImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
checkmarkImageView.leadingAnchor.constraint(equalTo: badgeButton.trailingAnchor, constant: 16),
|
||||
checkmarkImageView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
|
||||
])
|
||||
checkmarkImageView.setContentHuggingPriority(.required - 9, for: .horizontal)
|
||||
checkmarkImageView.setContentCompressionResistancePriority(.required - 9, for: .horizontal)
|
||||
|
||||
animationImageView.isUserInteractionEnabled = false
|
||||
headlineLabel.isUserInteractionEnabled = false
|
||||
subheadlineLabel.isUserInteractionEnabled = false
|
||||
|
@ -109,6 +137,9 @@ extension SidebarListContentView {
|
|||
animationImageView.contentMode = .scaleAspectFit
|
||||
imageView.tintColor = Asset.Colors.brandBlue.color
|
||||
animationImageView.tintColor = Asset.Colors.brandBlue.color
|
||||
|
||||
badgeButton.setBadge(number: 0)
|
||||
checkmarkImageView.isHidden = true
|
||||
}
|
||||
|
||||
private func apply(configuration: ContentConfiguration) {
|
||||
|
@ -160,6 +191,8 @@ extension SidebarListContentView {
|
|||
let headline: MetaContent
|
||||
let subheadline: MetaContent?
|
||||
|
||||
let needsOutlineDisclosure: Bool
|
||||
|
||||
static func == (lhs: SidebarListContentView.Item, rhs: SidebarListContentView.Item) -> Bool {
|
||||
return lhs.isSelected == rhs.isSelected
|
||||
&& lhs.image == rhs.image
|
||||
|
|
|
@ -98,14 +98,19 @@ extension SearchHistoryViewModel {
|
|||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
managedObjectContext.performChanges {
|
||||
guard let user = try? managedObjectContext.existingObject(with: objectID) as? MastodonUser else { return }
|
||||
if let searchHistory = user.searchHistory {
|
||||
if let searchHistory = user.findSearchHistory(domain: box.domain, userID: box.userID) {
|
||||
searchHistory.update(updatedAt: Date())
|
||||
} else {
|
||||
SearchHistory.insert(into: managedObjectContext, property: property, account: user)
|
||||
}
|
||||
}
|
||||
.sink { result in
|
||||
// do nothing
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
case .success:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &context.disposeBag)
|
||||
|
||||
|
@ -113,14 +118,19 @@ extension SearchHistoryViewModel {
|
|||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
managedObjectContext.performChanges {
|
||||
guard let hashtag = try? managedObjectContext.existingObject(with: objectID) as? Tag else { return }
|
||||
if let searchHistory = hashtag.searchHistory {
|
||||
if let searchHistory = hashtag.findSearchHistory(domain: box.domain, userID: box.userID) {
|
||||
searchHistory.update(updatedAt: Date())
|
||||
} else {
|
||||
SearchHistory.insert(into: managedObjectContext, property: property, hashtag: hashtag)
|
||||
_ = SearchHistory.insert(into: managedObjectContext, property: property, hashtag: hashtag)
|
||||
}
|
||||
}
|
||||
.sink { result in
|
||||
// do nothing
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
case .success:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &context.disposeBag)
|
||||
|
||||
|
|
|
@ -146,44 +146,57 @@ extension SearchResultViewModel {
|
|||
let domain = box.domain
|
||||
|
||||
switch item {
|
||||
case .account(let account):
|
||||
case .account(let entity):
|
||||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
managedObjectContext.performChanges {
|
||||
let (user, _) = APIService.CoreData.createOrMergeMastodonUser(
|
||||
into: managedObjectContext,
|
||||
for: nil,
|
||||
in: domain,
|
||||
entity: account,
|
||||
entity: entity,
|
||||
userCache: nil,
|
||||
networkDate: Date(),
|
||||
log: OSLog.api
|
||||
)
|
||||
if let searchHistory = user.searchHistory {
|
||||
if let searchHistory = user.findSearchHistory(domain: box.domain, userID: box.userID) {
|
||||
searchHistory.update(updatedAt: Date())
|
||||
} else {
|
||||
SearchHistory.insert(into: managedObjectContext, property: property, account: user)
|
||||
}
|
||||
}
|
||||
.sink { result in
|
||||
// do nothing
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
case .success:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &context.disposeBag)
|
||||
|
||||
case .hashtag(let hashtag):
|
||||
case .hashtag(let entity):
|
||||
let managedObjectContext = context.backgroundManagedObjectContext
|
||||
var tag: Tag?
|
||||
managedObjectContext.performChanges {
|
||||
let (hashtag, _) = APIService.CoreData.createOrMergeTag(
|
||||
into: managedObjectContext,
|
||||
entity: hashtag
|
||||
entity: entity
|
||||
)
|
||||
if let searchHistory = hashtag.searchHistory {
|
||||
tag = hashtag
|
||||
if let searchHistory = hashtag.findSearchHistory(domain: box.domain, userID: box.userID) {
|
||||
searchHistory.update(updatedAt: Date())
|
||||
} else {
|
||||
SearchHistory.insert(into: managedObjectContext, property: property, hashtag: hashtag)
|
||||
_ = SearchHistory.insert(into: managedObjectContext, property: property, hashtag: hashtag)
|
||||
}
|
||||
}
|
||||
.sink { result in
|
||||
// do nothing
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
assertionFailure(error.localizedDescription)
|
||||
case .success:
|
||||
print(tag?.searchHistories)
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &context.disposeBag)
|
||||
|
||||
|
|
|
@ -104,6 +104,10 @@ extension SearchResultTableViewCell {
|
|||
separatorLine.heightAnchor.constraint(equalToConstant: UIView.separatorLineHeight(of: contentView)),
|
||||
])
|
||||
resetSeparatorLineLayout()
|
||||
|
||||
_titleLabel.isUserInteractionEnabled = false
|
||||
_subTitleLabel.isUserInteractionEnabled = false
|
||||
_imageView.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
|
|
|
@ -545,7 +545,7 @@ extension SettingsViewController: ASWebAuthenticationPresentationContextProvidin
|
|||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
extension SettingsViewController: UIAdaptivePresentationControllerDelegate {
|
||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||
return .formSheet
|
||||
return .pageSheet
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,15 +86,9 @@ extension APIService.CoreData {
|
|||
let object = Poll.insert(into: managedObjectContext, property: Poll.Property(id: poll.id, expiresAt: poll.expiresAt, expired: poll.expired, multiple: poll.multiple, votesCount: poll.votesCount, votersCount: poll.votersCount, networkDate: networkDate), votedBy: votedBy, options: options)
|
||||
return object
|
||||
}
|
||||
let metions = entity.mentions?.enumerated().compactMap { index, mention -> Mention in
|
||||
let mentions = entity.mentions?.enumerated().compactMap { index, mention -> Mention in
|
||||
Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url), index: index)
|
||||
}
|
||||
let tags = entity.tags?.compactMap { tag -> Tag in
|
||||
let histories = tag.history?.compactMap { history -> History in
|
||||
History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
|
||||
}
|
||||
return Tag.insert(into: managedObjectContext, property: Tag.Property(name: tag.name, url: tag.url, histories: histories))
|
||||
}
|
||||
let mediaAttachments: [Attachment]? = {
|
||||
let encoder = JSONEncoder()
|
||||
var attachments: [Attachment] = []
|
||||
|
@ -117,8 +111,7 @@ extension APIService.CoreData {
|
|||
application: application,
|
||||
replyTo: replyTo,
|
||||
poll: poll,
|
||||
mentions: metions,
|
||||
tags: tags,
|
||||
mentions: mentions,
|
||||
mediaAttachments: mediaAttachments,
|
||||
favouritedBy: (entity.favourited ?? false) ? requestMastodonUser : nil,
|
||||
rebloggedBy: (entity.reblogged ?? false) ? requestMastodonUser : nil,
|
||||
|
|
|
@ -15,7 +15,7 @@ extension APIService.CoreData {
|
|||
into managedObjectContext: NSManagedObjectContext,
|
||||
entity: Mastodon.Entity.Tag
|
||||
) -> (Tag: Tag, isCreated: Bool) {
|
||||
// fetch old mastodon user
|
||||
// fetch old hashtag
|
||||
let oldTag: Tag? = {
|
||||
let request = Tag.sortedFetchRequest
|
||||
request.predicate = Tag.predicate(name: entity.name)
|
||||
|
|
Loading…
Reference in New Issue