fix: tag and searchHistory repeated save in CoreDate

This commit is contained in:
sunxiaojian 2021-04-08 12:22:05 +08:00
parent 27b698a97a
commit 0dab9acd91
11 changed files with 225 additions and 65 deletions

View File

@ -149,6 +149,7 @@
<entity name="SearchHistory" representedClassName=".SearchHistory" syncable="YES"> <entity name="SearchHistory" representedClassName=".SearchHistory" syncable="YES">
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/> <attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/> <attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/> <relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser"/>
<relationship name="hashtag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag"/> <relationship name="hashtag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag"/>
</entity> </entity>
@ -194,24 +195,25 @@
<attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/> <attribute name="createAt" attributeType="Date" defaultDateTimeInterval="631123200" usesScalarValueType="NO"/>
<attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/> <attribute name="identifier" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="name" attributeType="String"/> <attribute name="name" attributeType="String"/>
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="url" attributeType="String"/> <attribute name="url" attributeType="String"/>
<relationship name="histories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="History" inverseName="tag" inverseEntity="History"/> <relationship name="histories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="History" inverseName="tag" inverseEntity="History"/>
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="tags" inverseEntity="Status"/> <relationship name="statuses" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="tags" inverseEntity="Status"/>
</entity> </entity>
<elements> <elements>
<element name="Application" positionX="0" positionY="0" width="0" height="0"/> <element name="Application" positionX="0" positionY="0" width="128" height="104"/>
<element name="Attachment" positionX="0" positionY="0" width="0" height="0"/> <element name="Attachment" positionX="0" positionY="0" width="128" height="254"/>
<element name="Emoji" positionX="0" positionY="0" width="0" height="0"/> <element name="Emoji" positionX="0" positionY="0" width="128" height="149"/>
<element name="History" positionX="0" positionY="0" width="0" height="0"/> <element name="History" positionX="0" positionY="0" width="128" height="119"/>
<element name="HomeTimelineIndex" positionX="0" positionY="0" width="0" height="0"/> <element name="HomeTimelineIndex" positionX="0" positionY="0" width="128" height="134"/>
<element name="MastodonAuthentication" positionX="0" positionY="0" width="0" height="0"/> <element name="MastodonAuthentication" positionX="0" positionY="0" width="128" height="209"/>
<element name="MastodonUser" positionX="0" positionY="0" width="0" height="0"/> <element name="MastodonUser" positionX="0" positionY="0" width="128" height="659"/>
<element name="Mention" positionX="0" positionY="0" width="0" height="0"/> <element name="Mention" positionX="0" positionY="0" width="128" height="134"/>
<element name="Poll" positionX="0" positionY="0" width="0" height="0"/> <element name="Poll" positionX="0" positionY="0" width="128" height="194"/>
<element name="PollOption" positionX="0" positionY="0" width="0" height="0"/> <element name="PollOption" positionX="0" positionY="0" width="128" height="134"/>
<element name="PrivateNote" positionX="0" positionY="0" width="0" height="0"/> <element name="PrivateNote" positionX="0" positionY="0" width="128" height="89"/>
<element name="SearchHistory" positionX="0" positionY="0" width="0" height="0"/> <element name="SearchHistory" positionX="0" positionY="0" width="128" height="104"/>
<element name="Status" positionX="0" positionY="0" width="0" height="0"/> <element name="Status" positionX="0" positionY="0" width="128" height="569"/>
<element name="Tag" positionX="0" positionY="0" width="0" height="0"/> <element name="Tag" positionX="0" positionY="0" width="128" height="134"/>
</elements> </elements>
</model> </model>

View File

@ -40,6 +40,26 @@ public extension History {
} }
} }
public extension History {
func update(day: Date) {
if self.day != day {
self.day = day
}
}
func update(uses: String) {
if self.uses != uses {
self.uses = uses
}
}
func update(accounts: String) {
if self.accounts != accounts {
self.accounts = accounts
}
}
}
public extension History { public extension History {
struct Property { struct Property {
public let day: Date public let day: Date

View File

@ -12,6 +12,7 @@ public final class SearchHistory: NSManagedObject {
public typealias ID = UUID public typealias ID = UUID
@NSManaged public private(set) var identifier: ID @NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var createAt: Date @NSManaged public private(set) var createAt: Date
@NSManaged public private(set) var updatedAt: Date
@NSManaged public private(set) var account: MastodonUser? @NSManaged public private(set) var account: MastodonUser?
@NSManaged public private(set) var hashtag: Tag? @NSManaged public private(set) var hashtag: Tag?
@ -22,6 +23,13 @@ extension SearchHistory {
public override func awakeFromInsert() { public override func awakeFromInsert() {
super.awakeFromInsert() super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: #keyPath(SearchHistory.identifier)) 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 @discardableResult
@ -31,7 +39,6 @@ extension SearchHistory {
) -> SearchHistory { ) -> SearchHistory {
let searchHistory: SearchHistory = context.insertObject() let searchHistory: SearchHistory = context.insertObject()
searchHistory.account = account searchHistory.account = account
searchHistory.createAt = Date()
return searchHistory return searchHistory
} }
@ -42,13 +49,18 @@ extension SearchHistory {
) -> SearchHistory { ) -> SearchHistory {
let searchHistory: SearchHistory = context.insertObject() let searchHistory: SearchHistory = context.insertObject()
searchHistory.hashtag = hashtag searchHistory.hashtag = hashtag
searchHistory.createAt = Date()
return searchHistory return searchHistory
} }
} }
public extension SearchHistory {
func update(updatedAt: Date) {
setValue(updatedAt, forKey: #keyPath(SearchHistory.updatedAt))
}
}
extension SearchHistory: Managed { extension SearchHistory: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] { public static var defaultSortDescriptors: [NSSortDescriptor] {
return [NSSortDescriptor(keyPath: \SearchHistory.createAt, ascending: false)] return [NSSortDescriptor(keyPath: \SearchHistory.updatedAt, ascending: false)]
} }
} }

View File

@ -12,6 +12,7 @@ public final class Tag: NSManagedObject {
public typealias ID = UUID public typealias ID = UUID
@NSManaged public private(set) var identifier: ID @NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var createAt: Date @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 name: String
@NSManaged public private(set) var url: String @NSManaged public private(set) var url: String
@ -23,14 +24,21 @@ public final class Tag: NSManagedObject {
@NSManaged public private(set) var histories: Set<History>? @NSManaged public private(set) var histories: Set<History>?
} }
extension Tag { public extension Tag {
public override func awakeFromInsert() { override func awakeFromInsert() {
super.awakeFromInsert() super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: #keyPath(Tag.identifier)) 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 @discardableResult
public static func insert( static func insert(
into context: NSManagedObjectContext, into context: NSManagedObjectContext,
property: Property property: Property
) -> Tag { ) -> Tag {
@ -44,8 +52,8 @@ extension Tag {
} }
} }
extension Tag { public extension Tag {
public struct Property { struct Property {
public let name: String public let name: String
public let url: String public let url: String
public let histories: [History]? public let histories: [History]?
@ -58,8 +66,36 @@ extension Tag {
} }
} }
extension Tag: Managed { public extension Tag {
public static var defaultSortDescriptors: [NSSortDescriptor] { func updateHistory(index: Int, day: Date, uses: String, account: String) {
return [NSSortDescriptor(keyPath: \Tag.createAt, ascending: false)] 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)
} }
} }

View File

@ -30,7 +30,6 @@
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; }; 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; }; 18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; }; 2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; };
2D0B7A0B261D5A5600B44727 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0B7A0A261D5A5600B44727 /* Array.swift */; };
2D0B7A1D261D839600B44727 /* SearchHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0B7A1C261D839600B44727 /* SearchHistory.swift */; }; 2D0B7A1D261D839600B44727 /* SearchHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0B7A1C261D839600B44727 /* SearchHistory.swift */; };
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* StatusView.swift */; }; 2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* StatusView.swift */; };
2D152A9225C2980C009AA50C /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A9125C2980C009AA50C /* UIFont.swift */; }; 2D152A9225C2980C009AA50C /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A9125C2980C009AA50C /* UIFont.swift */; };
@ -92,6 +91,7 @@
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; }; 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; };
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; }; 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; };
2D7631B325C159F700929FB9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631B225C159F700929FB9 /* Item.swift */; }; 2D7631B325C159F700929FB9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631B225C159F700929FB9 /* Item.swift */; };
2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D79E700261EA5550011E398 /* APIService+CoreData+Tag.swift */; };
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */; }; 2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */; };
2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */; }; 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */; };
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */; }; 2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */; };
@ -401,7 +401,6 @@
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSearchCell.swift; sourceTree = "<group>"; }; 0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSearchCell.swift; sourceTree = "<group>"; };
0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = "<group>"; }; 0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = "<group>"; };
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = "<group>"; }; 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = "<group>"; };
2D0B7A0A261D5A5600B44727 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
2D0B7A1C261D839600B44727 /* SearchHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistory.swift; sourceTree = "<group>"; }; 2D0B7A1C261D839600B44727 /* SearchHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistory.swift; sourceTree = "<group>"; };
2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; }; 2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; }; 2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
@ -460,6 +459,7 @@
2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = "<group>"; }; 2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = "<group>"; };
2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; }; 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; }; 2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
2D79E700261EA5550011E398 /* APIService+CoreData+Tag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+Tag.swift"; sourceTree = "<group>"; };
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewControllerAppearance.swift; sourceTree = "<group>"; }; 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewControllerAppearance.swift; sourceTree = "<group>"; };
2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModelNavigationDelegateShim.swift; sourceTree = "<group>"; }; 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModelNavigationDelegateShim.swift; sourceTree = "<group>"; };
2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarTitleViewModel.swift; sourceTree = "<group>"; }; 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarTitleViewModel.swift; sourceTree = "<group>"; };
@ -1321,6 +1321,7 @@
2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Status.swift */, 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Status.swift */,
DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */, DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */,
DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */, DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */,
2D79E700261EA5550011E398 /* APIService+CoreData+Tag.swift */,
); );
path = CoreData; path = CoreData;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1550,7 +1551,6 @@
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */, 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */,
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */, DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */,
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */, DB0140CE25C42AEE00F9F3CF /* OSLog.swift */,
2D0B7A0A261D5A5600B44727 /* Array.swift */,
DB68A06225E905E000CFDF14 /* UIApplication.swift */, DB68A06225E905E000CFDF14 /* UIApplication.swift */,
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */, DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */,
2D42FF8E25C8228A004A627A /* UIButton.swift */, 2D42FF8E25C8228A004A627A /* UIButton.swift */,
@ -2207,6 +2207,7 @@
2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */, 2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */,
DB49A62B25FF36C700B98345 /* APIService+CustomEmoji.swift in Sources */, DB49A62B25FF36C700B98345 /* APIService+CustomEmoji.swift in Sources */,
DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */, DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */,
2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */,
2D939AB525EDD8A90076FA61 /* String.swift in Sources */, 2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */, DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
DBE3CDBB261C427900430CC6 /* TimelineHeaderTableViewCell.swift in Sources */, DBE3CDBB261C427900430CC6 /* TimelineHeaderTableViewCell.swift in Sources */,
@ -2284,7 +2285,6 @@
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
2D0B7A0B261D5A5600B44727 /* Array.swift in Sources */,
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */, DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */, DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */, DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,

View File

@ -1,20 +0,0 @@
//
// Array.swift
// Mastodon
//
// Created by sxiaojian on 2021/4/7.
//
import Foundation
public extension Array where Element: Equatable {
func removeDuplicate() -> Array {
return self.enumerated().filter { (index,value) -> Bool in
return self.firstIndex(of: value) == index
}.map { (_, value) in
value
}
}
}

View File

@ -82,7 +82,8 @@ extension SearchRecommendTagsCollectionViewCell {
peopleLabel.text = "" peopleLabel.text = ""
return return
} }
let recentHistory = historys[0...2]
let recentHistory = historys.prefix(2)
let peopleAreTalking = recentHistory.compactMap({ Int($0.accounts) }).reduce(0, +) let peopleAreTalking = recentHistory.compactMap({ Int($0.accounts) }).reduce(0, +)
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking)) let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
peopleLabel.text = string peopleLabel.text = string

View File

@ -89,7 +89,8 @@ extension SearchViewModel.LoadOldestState {
var newAccounts = [Mastodon.Entity.Account]() var newAccounts = [Mastodon.Entity.Account]()
newAccounts.append(contentsOf: oldSearchResult.accounts) newAccounts.append(contentsOf: oldSearchResult.accounts)
newAccounts.append(contentsOf: result.value.accounts) newAccounts.append(contentsOf: result.value.accounts)
viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: newAccounts.removeDuplicate(), statuses: oldSearchResult.statuses, hashtags: oldSearchResult.hashtags) newAccounts.removeDuplicates()
viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: newAccounts, statuses: oldSearchResult.statuses, hashtags: oldSearchResult.hashtags)
stateMachine.enter(Idle.self) stateMachine.enter(Idle.self)
} }
case Mastodon.API.Search.SearchType.hashtags: case Mastodon.API.Search.SearchType.hashtags:
@ -99,7 +100,8 @@ extension SearchViewModel.LoadOldestState {
var newTags = [Mastodon.Entity.Tag]() var newTags = [Mastodon.Entity.Tag]()
newTags.append(contentsOf: oldSearchResult.hashtags) newTags.append(contentsOf: oldSearchResult.hashtags)
newTags.append(contentsOf: result.value.hashtags) newTags.append(contentsOf: result.value.hashtags)
viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: oldSearchResult.accounts, statuses: oldSearchResult.statuses, hashtags: newTags.removeDuplicate()) newTags.removeDuplicates()
viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: oldSearchResult.accounts, statuses: oldSearchResult.statuses, hashtags: newTags)
stateMachine.enter(Idle.self) stateMachine.enter(Idle.self)
} }
default: default:

View File

@ -234,6 +234,7 @@ final class SearchViewModel: NSObject {
} }
func saveItemToCoreData(item: SearchResultItem) { func saveItemToCoreData(item: SearchResultItem) {
let searchHistories = self.fetchSearchHistory()
_ = context.managedObjectContext.performChanges { [weak self] in _ = context.managedObjectContext.performChanges { [weak self] in
guard let self = self else { return } guard let self = self else { return }
switch item { switch item {
@ -255,15 +256,55 @@ final class SearchViewModel: NSObject {
} }
}() }()
let (mastodonUser, _) = APIService.CoreData.createOrMergeMastodonUser(into: self.context.managedObjectContext, for: requestMastodonUser, in: activeMastodonAuthenticationBox.domain, entity: account, userCache: nil, networkDate: Date(), log: OSLog.api) let (mastodonUser, _) = APIService.CoreData.createOrMergeMastodonUser(into: self.context.managedObjectContext, for: requestMastodonUser, in: activeMastodonAuthenticationBox.domain, entity: account, userCache: nil, networkDate: Date(), log: OSLog.api)
SearchHistory.insert(into: self.context.managedObjectContext, account: mastodonUser) if let searchHistories = searchHistories {
let history = searchHistories.first { history -> Bool in
guard let account = history.account else { return false }
return account.objectID == mastodonUser.objectID
}
if let history = history {
history.update(updatedAt: Date())
} else {
SearchHistory.insert(into: self.context.managedObjectContext, account: mastodonUser)
}
} else {
SearchHistory.insert(into: self.context.managedObjectContext, account: mastodonUser)
}
case .hashtag(let tag): case .hashtag(let tag):
let histories = tag.history?[0 ... 2].compactMap { history -> History in let (tagInCoreData,_) = APIService.CoreData.createOrMergeTag(into: self.context.managedObjectContext, entity: tag)
History.insert(into: self.context.managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts)) if let searchHistories = searchHistories {
let history = searchHistories.first { history -> Bool in
guard let hashtag = history.hashtag else { return false }
return hashtag.objectID == tagInCoreData.objectID
}
if let history = history {
history.update(updatedAt: Date())
} else {
SearchHistory.insert(into: self.context.managedObjectContext, hashtag: tagInCoreData)
}
} else {
SearchHistory.insert(into: self.context.managedObjectContext, hashtag: tagInCoreData)
}
case .accountObjectID(let accountObjectID):
if let searchHistories = searchHistories {
let history = searchHistories.first { history -> Bool in
guard let account = history.account else { return false }
return account.objectID == accountObjectID
}
if let history = history {
history.update(updatedAt: Date())
}
}
case .hashtagObjectID(let hashtagObjectID):
if let searchHistories = searchHistories {
let history = searchHistories.first { history -> Bool in
guard let hashtag = history.hashtag else { return false }
return hashtag.objectID == hashtagObjectID
}
if let history = history {
history.update(updatedAt: Date())
}
} }
let tagInCoreData = Tag.insert(into: self.context.managedObjectContext, property: Tag.Property(name: tag.name, url: tag.url, histories: histories))
SearchHistory.insert(into: self.context.managedObjectContext, hashtag: tagInCoreData)
default: default:
break break
} }

View File

@ -96,7 +96,7 @@ extension SearchingTableViewCell {
_subTitleLabel.text = "" _subTitleLabel.text = ""
return return
} }
let recentHistory = historys[0 ... 2] let recentHistory = historys.prefix(2)
let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +) let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +)
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking)) let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
_subTitleLabel.text = string _subTitleLabel.text = string
@ -112,7 +112,7 @@ extension SearchingTableViewCell {
_subTitleLabel.text = "" _subTitleLabel.text = ""
return return
} }
let recentHistory = historys[0 ... 2] let recentHistory = historys.prefix(2)
let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +) let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +)
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking)) let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
_subTitleLabel.text = string _subTitleLabel.text = string

View File

@ -0,0 +1,66 @@
//
// APIService+CoreData+Tag.swift
// Mastodon
//
// Created by sxiaojian on 2021/4/8.
//
import CoreData
import CoreDataStack
import Foundation
import MastodonSDK
extension APIService.CoreData {
static func createOrMergeTag(
into managedObjectContext: NSManagedObjectContext,
entity: Mastodon.Entity.Tag
) -> (Tag: Tag, isCreated: Bool) {
// fetch old mastodon user
let oldTag: Tag? = {
let request = Tag.sortedFetchRequest
request.predicate = Tag.predicate(name: entity.name)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try managedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
if let oldTag = oldTag {
APIService.CoreData.merge(tag: oldTag, entity: entity, into: managedObjectContext)
return (oldTag, false)
} else {
let histories = entity.history?.prefix(2).compactMap { history -> History in
History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
}
let tagInCoreData = Tag.insert(into: managedObjectContext, property: Tag.Property(name: entity.name, url: entity.url, histories: histories))
return (tagInCoreData, true)
}
}
static func merge(tag:Tag,entity:Mastodon.Entity.Tag,into managedObjectContext: NSManagedObjectContext) {
tag.update(url: tag.url)
guard let tagHistories = tag.histories else { return }
guard let entityHistories = entity.history?.prefix(2) else { return }
let entityHistoriesCount = entityHistories.count
if entityHistoriesCount == 0 {
return
}
for n in 0..<tagHistories.count {
if n < entityHistories.count {
let entityHistory = entityHistories[n]
tag.updateHistory(index: n, day: entityHistory.day, uses: entityHistory.uses, account: entityHistory.accounts)
}
}
if entityHistoriesCount <= tagHistories.count {
return
}
for n in 1...(entityHistoriesCount - tagHistories.count) {
let entityHistory = entityHistories[entityHistoriesCount - n]
tag.appendHistory(history: History.insert(into: managedObjectContext, property: History.Property(day: entityHistory.day, uses: entityHistory.uses, accounts: entityHistory.accounts)))
}
}
}