forked from shadowfacts/Tusker
Move saved instances and hashtags to CoreData
This commit is contained in:
parent
ed0643c4ad
commit
d3187ce2c4
|
@ -237,6 +237,9 @@
|
||||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||||
D6B93667281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */; };
|
D6B93667281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */; };
|
||||||
D6B9366B281EE77E00237D0E /* PollVoteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366A281EE77E00237D0E /* PollVoteButton.swift */; };
|
D6B9366B281EE77E00237D0E /* PollVoteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366A281EE77E00237D0E /* PollVoteButton.swift */; };
|
||||||
|
D6B9366D2828445000237D0E /* SavedInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366C2828444F00237D0E /* SavedInstance.swift */; };
|
||||||
|
D6B9366F2828452F00237D0E /* SavedHashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366E2828452F00237D0E /* SavedHashtag.swift */; };
|
||||||
|
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */; };
|
||||||
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */; };
|
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */; };
|
||||||
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */; };
|
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */; };
|
||||||
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */; };
|
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */; };
|
||||||
|
@ -579,6 +582,9 @@
|
||||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||||
D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarMyProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarMyProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6B9366A281EE77E00237D0E /* PollVoteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVoteButton.swift; sourceTree = "<group>"; };
|
D6B9366A281EE77E00237D0E /* PollVoteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVoteButton.swift; sourceTree = "<group>"; };
|
||||||
|
D6B9366C2828444F00237D0E /* SavedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedInstance.swift; sourceTree = "<group>"; };
|
||||||
|
D6B9366E2828452F00237D0E /* SavedHashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtag.swift; sourceTree = "<group>"; };
|
||||||
|
D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Helpers.swift"; sourceTree = "<group>"; };
|
||||||
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedTableViewController.swift; sourceTree = "<group>"; };
|
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedTableViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPageViewController.swift; sourceTree = "<group>"; };
|
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPageViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellnessPrefsView.swift; sourceTree = "<group>"; };
|
D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellnessPrefsView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -847,7 +853,10 @@
|
||||||
D60E2F232442372B005F8713 /* StatusMO.swift */,
|
D60E2F232442372B005F8713 /* StatusMO.swift */,
|
||||||
D60E2F252442372B005F8713 /* AccountMO.swift */,
|
D60E2F252442372B005F8713 /* AccountMO.swift */,
|
||||||
D6DF95C02533F5DE0027A9B6 /* RelationshipMO.swift */,
|
D6DF95C02533F5DE0027A9B6 /* RelationshipMO.swift */,
|
||||||
|
D6B9366E2828452F00237D0E /* SavedHashtag.swift */,
|
||||||
|
D6B9366C2828444F00237D0E /* SavedInstance.swift */,
|
||||||
D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */,
|
D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */,
|
||||||
|
D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */,
|
||||||
);
|
);
|
||||||
path = CoreData;
|
path = CoreData;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1793,6 +1802,8 @@
|
||||||
D6C143FD25354FD0007DC240 /* EmojiCollectionViewCell.swift in Sources */,
|
D6C143FD25354FD0007DC240 /* EmojiCollectionViewCell.swift in Sources */,
|
||||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */,
|
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */,
|
||||||
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */,
|
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */,
|
||||||
|
D6B9366F2828452F00237D0E /* SavedHashtag.swift in Sources */,
|
||||||
|
D6B9366D2828445000237D0E /* SavedInstance.swift in Sources */,
|
||||||
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */,
|
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */,
|
||||||
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */,
|
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */,
|
||||||
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */,
|
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */,
|
||||||
|
@ -1847,6 +1858,7 @@
|
||||||
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */,
|
D6E9CDA8281A427800BBC98E /* PostService.swift in Sources */,
|
||||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
||||||
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */,
|
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */,
|
||||||
|
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */,
|
||||||
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
||||||
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */,
|
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */,
|
||||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import CrashReporter
|
import CrashReporter
|
||||||
|
import CoreData
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
@ -27,6 +28,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
AudioSessionHelper.setDefault()
|
AudioSessionHelper.setDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let oldSavedData = SavedDataManager.load() {
|
||||||
|
do {
|
||||||
|
for account in oldSavedData.accountIDs {
|
||||||
|
guard let account = LocalData.shared.getAccount(id: account) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let controller = MastodonController.getForAccount(account)
|
||||||
|
try oldSavedData.migrateToCoreData(accountID: account.id, context: controller.persistentContainer.viewContext)
|
||||||
|
if controller.persistentContainer.viewContext.hasChanges {
|
||||||
|
try controller.persistentContainer.viewContext.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try SavedDataManager.destroy()
|
||||||
|
} catch {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,8 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
fatalError("Unable to load persistent store: \(error)")
|
fatalError("Unable to load persistent store: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func status(for id: String, in context: NSManagedObjectContext? = nil) -> StatusMO? {
|
func status(for id: String, in context: NSManagedObjectContext? = nil) -> StatusMO? {
|
||||||
|
@ -215,4 +217,43 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func managedObjectsDidChange(_ notification: Foundation.Notification) {
|
||||||
|
let changes = hasChangedSavedHashtagsOrInstances(notification)
|
||||||
|
if changes.hashtags {
|
||||||
|
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
|
||||||
|
}
|
||||||
|
if changes.instances {
|
||||||
|
NotificationCenter.default.post(name: .savedInstancesChanged, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hasChangedSavedHashtagsOrInstances(_ notification: Foundation.Notification) -> (hashtags: Bool, instances: Bool) {
|
||||||
|
var changes: (hashtags: Bool, instances: Bool) = (false, false)
|
||||||
|
if let inserted = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject> {
|
||||||
|
for object in inserted {
|
||||||
|
if object is SavedHashtag {
|
||||||
|
changes.hashtags = true
|
||||||
|
} else if object is SavedInstance {
|
||||||
|
changes.instances = true
|
||||||
|
}
|
||||||
|
if changes.hashtags && changes.instances {
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let deleted = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject> {
|
||||||
|
for object in deleted {
|
||||||
|
if object is SavedHashtag {
|
||||||
|
changes.hashtags = true
|
||||||
|
} else if object is SavedInstance {
|
||||||
|
changes.instances = true
|
||||||
|
}
|
||||||
|
if changes.hashtags && changes.instances {
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// NSManagedObjectContext+Helpers.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/9/22.
|
||||||
|
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
extension NSManagedObjectContext {
|
||||||
|
func objectExists<T: NSFetchRequestResult>(for request: NSFetchRequest<T>) -> Bool {
|
||||||
|
switch try? count(for: request) {
|
||||||
|
case nil, 0, NSNotFound:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// SavedHashtag.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/8/22.
|
||||||
|
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
@objc(SavedHashtag)
|
||||||
|
public final class SavedHashtag: NSManagedObject {
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<SavedHashtag> {
|
||||||
|
return NSFetchRequest<SavedHashtag>(entityName: "SavedHashtag")
|
||||||
|
}
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest(name: String) -> NSFetchRequest<SavedHashtag> {
|
||||||
|
let req = NSFetchRequest<SavedHashtag>(entityName: "SavedHashtag")
|
||||||
|
req.predicate = NSPredicate(format: "name = %@", name)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
@NSManaged public var name: String
|
||||||
|
@NSManaged public var url: URL
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SavedHashtag {
|
||||||
|
convenience init(hashtag: Hashtag, context: NSManagedObjectContext) {
|
||||||
|
self.init(context: context)
|
||||||
|
self.name = hashtag.name
|
||||||
|
self.url = hashtag.url
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// SavedInstance.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/8/22.
|
||||||
|
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
@objc(SavedInstance)
|
||||||
|
public final class SavedInstance: NSManagedObject {
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<SavedInstance> {
|
||||||
|
return NSFetchRequest<SavedInstance>(entityName: "SavedInstance")
|
||||||
|
}
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest(url: URL) -> NSFetchRequest<SavedInstance> {
|
||||||
|
let req = fetchRequest()
|
||||||
|
req.predicate = NSPredicate(format: "url = %@", url as NSURL)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
@NSManaged public var url: URL
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SavedInstance {
|
||||||
|
convenience init(url: URL, context: NSManagedObjectContext) {
|
||||||
|
self.init(context: context)
|
||||||
|
self.url = url
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,23 @@
|
||||||
<attribute name="showingReblogs" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="showingReblogs" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="relationship" inverseEntity="Account"/>
|
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="relationship" inverseEntity="Account"/>
|
||||||
</entity>
|
</entity>
|
||||||
|
<entity name="SavedHashtag" representedClassName="SavedHashtag" syncable="YES">
|
||||||
|
<attribute name="name" attributeType="String"/>
|
||||||
|
<attribute name="url" attributeType="URI"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="name"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
|
<entity name="SavedInstance" representedClassName="SavedInstance" syncable="YES">
|
||||||
|
<attribute name="url" attributeType="URI"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="url"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
<entity name="Status" representedClassName="StatusMO" syncable="YES">
|
<entity name="Status" representedClassName="StatusMO" syncable="YES">
|
||||||
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
||||||
<attribute name="attachmentsData" attributeType="Binary"/>
|
<attribute name="attachmentsData" attributeType="Binary"/>
|
||||||
|
@ -77,7 +94,9 @@
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="343"/>
|
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="343"/>
|
||||||
<element name="Relationship" positionX="63" positionY="135" width="128" height="208"/>
|
<element name="Relationship" positionX="63" positionY="135" width="128" height="194"/>
|
||||||
<element name="Status" positionX="-63" positionY="-18" width="128" height="449"/>
|
<element name="Status" positionX="-63" positionY="-18" width="128" height="449"/>
|
||||||
|
<element name="SavedInstance" positionX="63" positionY="144" width="128" height="44"/>
|
||||||
|
<element name="SavedHashtag" positionX="72" positionY="153" width="128" height="59"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
|
@ -8,101 +8,68 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
import CoreData
|
||||||
|
|
||||||
class SavedDataManager: Codable {
|
class SavedDataManager: Codable {
|
||||||
private(set) static var shared: SavedDataManager = load()
|
|
||||||
|
|
||||||
private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
private static var archiveURL = SavedDataManager.documentsDirectory.appendingPathComponent("saved_data").appendingPathExtension("plist")
|
private static var archiveURL = SavedDataManager.documentsDirectory.appendingPathComponent("saved_data").appendingPathExtension("plist")
|
||||||
|
|
||||||
static func save() {
|
static func load() -> SavedDataManager? {
|
||||||
DispatchQueue.global(qos: .utility).async {
|
|
||||||
let encoder = PropertyListEncoder()
|
|
||||||
let data = try? encoder.encode(shared)
|
|
||||||
try? data?.write(to: archiveURL, options: .noFileProtection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func load() -> SavedDataManager {
|
|
||||||
let decoder = PropertyListDecoder()
|
let decoder = PropertyListDecoder()
|
||||||
if let data = try? Data(contentsOf: archiveURL),
|
if let data = try? Data(contentsOf: archiveURL),
|
||||||
let savedHashtagsManager = try? decoder.decode(Self.self, from: data) {
|
let savedHashtagsManager = try? decoder.decode(Self.self, from: data) {
|
||||||
return savedHashtagsManager
|
return savedHashtagsManager
|
||||||
}
|
}
|
||||||
return SavedDataManager()
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
static func destroy() throws {
|
||||||
|
try FileManager.default.removeItem(at: archiveURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
private var savedHashtags: [String: [Hashtag]] = [:] {
|
private(set) var savedHashtags: [String: [Hashtag]] = [:]
|
||||||
didSet {
|
private(set) var savedInstances: [String: [URL]] = [:]
|
||||||
SavedDataManager.save()
|
|
||||||
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
|
var accountIDs: Set<String> {
|
||||||
|
var s = Set<String>()
|
||||||
|
savedHashtags.keys.forEach { s.insert($0) }
|
||||||
|
savedInstances.keys.forEach { s.insert($0) }
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
private func save() {
|
||||||
|
let encoder = PropertyListEncoder()
|
||||||
|
let data = try? encoder.encode(self)
|
||||||
|
try? data?.write(to: SavedDataManager.archiveURL, options: .noFileProtection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateToCoreData(accountID: String, context: NSManagedObjectContext) throws {
|
||||||
|
var changed = false
|
||||||
|
|
||||||
|
if let hashtags = savedHashtags[accountID] {
|
||||||
|
let objects = hashtags.map {
|
||||||
|
["url": $0.url, "name": $0.name]
|
||||||
|
}
|
||||||
|
let hashtagsReq = NSBatchInsertRequest(entity: SavedHashtag.entity(), objects: objects)
|
||||||
|
try context.execute(hashtagsReq)
|
||||||
|
savedHashtags.removeValue(forKey: accountID)
|
||||||
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private var savedInstances: [String: [URL]] = [:] {
|
if let instances = savedInstances[accountID] {
|
||||||
didSet {
|
let objects = instances.map {
|
||||||
SavedDataManager.save()
|
["url": $0]
|
||||||
NotificationCenter.default.post(name: .savedInstancesChanged, object: nil)
|
}
|
||||||
|
let instancesReq = NSBatchInsertRequest(entity: SavedInstance.entity(), objects: objects)
|
||||||
|
try context.execute(instancesReq)
|
||||||
|
savedInstances.removeValue(forKey: accountID)
|
||||||
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func sortedHashtags(for account: LocalData.UserAccountInfo) -> [Hashtag] {
|
if changed {
|
||||||
if let hashtags = savedHashtags[account.id] {
|
save()
|
||||||
return hashtags.sorted(by: { $0.name < $1.name })
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSaved(hashtag: Hashtag, for account: LocalData.UserAccountInfo) -> Bool {
|
|
||||||
return savedHashtags[account.id]?.contains(hashtag) ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
func add(hashtag: Hashtag, for account: LocalData.UserAccountInfo) {
|
|
||||||
if isSaved(hashtag: hashtag, for: account) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if var saved = savedHashtags[account.id] {
|
|
||||||
saved.append(hashtag)
|
|
||||||
savedHashtags[account.id] = saved
|
|
||||||
} else {
|
|
||||||
savedHashtags[account.id] = [hashtag]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove(hashtag: Hashtag, for account: LocalData.UserAccountInfo) {
|
|
||||||
guard isSaved(hashtag: hashtag, for: account) else { return }
|
|
||||||
if var saved = savedHashtags[account.id] {
|
|
||||||
saved.removeAll(where: { $0.name == hashtag.name })
|
|
||||||
savedHashtags[account.id] = saved
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func savedInstances(for account: LocalData.UserAccountInfo) -> [URL] {
|
|
||||||
return savedInstances[account.id] ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSaved(instance url: URL, for account: LocalData.UserAccountInfo) -> Bool {
|
|
||||||
return savedInstances[account.id]?.contains(url) ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
func add(instance url: URL, for account: LocalData.UserAccountInfo) {
|
|
||||||
if isSaved(instance: url, for: account) { return }
|
|
||||||
if var saved = savedInstances[account.id] {
|
|
||||||
saved.append(url)
|
|
||||||
savedInstances[account.id] = saved
|
|
||||||
} else {
|
|
||||||
savedInstances[account.id] = [url]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove(instance url: URL, for account: LocalData.UserAccountInfo) {
|
|
||||||
guard isSaved(instance: url, for: account) else { return }
|
|
||||||
if var saved = savedInstances[account.id] {
|
|
||||||
saved.removeAll(where: { $0 == url })
|
|
||||||
savedInstances[account.id] = saved
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,7 +376,8 @@ struct ComposeAutocompleteHashtagsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateHashtags(searchResults: [Hashtag], trendingTags: [Hashtag], query: String) {
|
private func updateHashtags(searchResults: [Hashtag], trendingTags: [Hashtag], query: String) {
|
||||||
let savedTags = SavedDataManager.shared.sortedHashtags(for: mastodonController.accountInfo!)
|
let savedTags = ((try? mastodonController.persistentContainer.viewContext.fetch(SavedHashtag.fetchRequest())) ?? [])
|
||||||
|
.map { Hashtag(name: $0.name, url: $0.url) }
|
||||||
|
|
||||||
hashtags = (searchResults + savedTags + trendingTags)
|
hashtags = (searchResults + savedTags + trendingTags)
|
||||||
.map { (tag) -> (Hashtag, (matched: Bool, score: Int)) in
|
.map { (tag) -> (Hashtag, (matched: Bool, score: Int)) in
|
||||||
|
|
|
@ -86,7 +86,9 @@ class AddSavedHashtagViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func selectHashtag(_ hashtag: Hashtag) {
|
private func selectHashtag(_ hashtag: Hashtag) {
|
||||||
SavedDataManager.shared.add(hashtag: hashtag, for: mastodonController.accountInfo!)
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
|
_ = SavedHashtag(hashtag: hashtag, context: context)
|
||||||
|
try! context.save()
|
||||||
presentingViewController!.dismiss(animated: true)
|
presentingViewController!.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
import CoreData
|
||||||
|
|
||||||
class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||||
|
|
||||||
|
@ -134,8 +135,6 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func applyInitialSnapshot() {
|
private func applyInitialSnapshot() {
|
||||||
let account = mastodonController.accountInfo!
|
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
snapshot.appendSections(Section.allCases.filter { $0 != .discover })
|
snapshot.appendSections(Section.allCases.filter { $0 != .discover })
|
||||||
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
||||||
|
@ -144,9 +143,15 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||||
addDiscoverSection(to: &snapshot)
|
addDiscoverSection(to: &snapshot)
|
||||||
}
|
}
|
||||||
snapshot.appendItems([.addList], toSection: .lists)
|
snapshot.appendItems([.addList], toSection: .lists)
|
||||||
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags)
|
let hashtags = fetchSavedHashtags().map {
|
||||||
|
Item.savedHashtag(Hashtag(name: $0.name, url: $0.url))
|
||||||
|
}
|
||||||
|
snapshot.appendItems(hashtags, toSection: .savedHashtags)
|
||||||
snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags)
|
snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags)
|
||||||
snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) }, toSection: .savedInstances)
|
let instances = fetchSavedInstances().map {
|
||||||
|
Item.savedInstance($0.url)
|
||||||
|
}
|
||||||
|
snapshot.appendItems(instances, toSection: .savedInstances)
|
||||||
snapshot.appendItems([.findInstance], toSection: .savedInstances)
|
snapshot.appendItems([.findInstance], toSection: .savedInstances)
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
|
||||||
|
@ -190,20 +195,46 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func fetchSavedHashtags() -> [SavedHashtag] {
|
||||||
|
let req = SavedHashtag.fetchRequest()
|
||||||
|
req.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
|
||||||
|
do {
|
||||||
|
return try mastodonController.persistentContainer.viewContext.fetch(req)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func fetchSavedInstances() -> [SavedInstance] {
|
||||||
|
let req = SavedInstance.fetchRequest()
|
||||||
|
req.sortDescriptors = [NSSortDescriptor(key: "url.host", ascending: true)]
|
||||||
|
do {
|
||||||
|
return try mastodonController.persistentContainer.viewContext.fetch(req)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func savedHashtagsChanged() {
|
@objc private func savedHashtagsChanged() {
|
||||||
let account = mastodonController.accountInfo!
|
|
||||||
var snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedHashtags))
|
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedHashtags))
|
||||||
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags)
|
let hashtags = fetchSavedHashtags().map {
|
||||||
|
Item.savedHashtag(Hashtag(name: $0.name, url: $0.url))
|
||||||
|
}
|
||||||
|
snapshot.appendItems(hashtags, toSection: .savedHashtags)
|
||||||
snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags)
|
snapshot.appendItems([.addSavedHashtag], toSection: .savedHashtags)
|
||||||
dataSource.apply(snapshot)
|
dataSource.apply(snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func savedInstancesChanged() {
|
@objc private func savedInstancesChanged() {
|
||||||
let account = mastodonController.accountInfo!
|
|
||||||
var snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedInstances))
|
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .savedInstances))
|
||||||
snapshot.appendItems(SavedDataManager.shared.savedInstances(for: account).map { .savedInstance($0) }, toSection: .savedInstances)
|
let instances = fetchSavedInstances().map {
|
||||||
|
Item.savedInstance($0.url)
|
||||||
|
}
|
||||||
|
snapshot.appendItems(instances, toSection: .savedInstances)
|
||||||
snapshot.appendItems([.findInstance], toSection: .savedInstances)
|
snapshot.appendItems([.findInstance], toSection: .savedInstances)
|
||||||
dataSource.apply(snapshot)
|
dataSource.apply(snapshot)
|
||||||
}
|
}
|
||||||
|
@ -249,13 +280,19 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeSavedHashtag(_ hashtag: Hashtag) {
|
func removeSavedHashtag(_ hashtag: Hashtag) {
|
||||||
let account = mastodonController.accountInfo!
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
SavedDataManager.shared.remove(hashtag: hashtag, for: account)
|
if let hashtag = try? context.fetch(SavedHashtag.fetchRequest(name: hashtag.name)).first {
|
||||||
|
context.delete(hashtag)
|
||||||
|
try! context.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeSavedInstance(_ instanceURL: URL) {
|
func removeSavedInstance(_ instanceURL: URL) {
|
||||||
let account = mastodonController.accountInfo!
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
SavedDataManager.shared.remove(instance: instanceURL, for: account)
|
if let instance = try? context.fetch(SavedInstance.fetchRequest(url: instanceURL)).first {
|
||||||
|
context.delete(instance)
|
||||||
|
try! context.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func trailingSwipeActionsForCell(at indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
private func trailingSwipeActionsForCell(at indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
|
|
|
@ -218,14 +218,38 @@ class MainSidebarViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func fetchSavedHashtags() -> [SavedHashtag] {
|
||||||
|
let req = SavedHashtag.fetchRequest()
|
||||||
|
req.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
|
||||||
|
do {
|
||||||
|
return try mastodonController.persistentContainer.viewContext.fetch(req)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func fetchSavedInstances() -> [SavedInstance] {
|
||||||
|
let req = SavedInstance.fetchRequest()
|
||||||
|
req.sortDescriptors = [NSSortDescriptor(key: "url.host", ascending: true)]
|
||||||
|
do {
|
||||||
|
return try mastodonController.persistentContainer.viewContext.fetch(req)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func reloadSavedHashtags() {
|
@objc private func reloadSavedHashtags() {
|
||||||
let selected = collectionView.indexPathsForSelectedItems?.first
|
let selected = collectionView.indexPathsForSelectedItems?.first
|
||||||
|
|
||||||
var hashtagsSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
var hashtagsSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||||
hashtagsSnapshot.append([.savedHashtagsHeader])
|
hashtagsSnapshot.append([.savedHashtagsHeader])
|
||||||
hashtagsSnapshot.expand([.savedHashtagsHeader])
|
hashtagsSnapshot.expand([.savedHashtagsHeader])
|
||||||
let sortedHashtags = SavedDataManager.shared.sortedHashtags(for: mastodonController.accountInfo!)
|
let hashtags = fetchSavedHashtags().map {
|
||||||
hashtagsSnapshot.append(sortedHashtags.map { .savedHashtag($0) }, to: .savedHashtagsHeader)
|
Item.savedHashtag(Hashtag(name: $0.name, url: $0.url))
|
||||||
|
}
|
||||||
|
hashtagsSnapshot.append(hashtags, to: .savedHashtagsHeader)
|
||||||
hashtagsSnapshot.append([.addSavedHashtag], to: .savedHashtagsHeader)
|
hashtagsSnapshot.append([.addSavedHashtag], to: .savedHashtagsHeader)
|
||||||
self.dataSource.apply(hashtagsSnapshot, to: .savedHashtags, animatingDifferences: false) {
|
self.dataSource.apply(hashtagsSnapshot, to: .savedHashtags, animatingDifferences: false) {
|
||||||
if let selected = selected {
|
if let selected = selected {
|
||||||
|
@ -240,8 +264,10 @@ class MainSidebarViewController: UIViewController {
|
||||||
var instancesSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
var instancesSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
|
||||||
instancesSnapshot.append([.savedInstancesHeader])
|
instancesSnapshot.append([.savedInstancesHeader])
|
||||||
instancesSnapshot.expand([.savedInstancesHeader])
|
instancesSnapshot.expand([.savedInstancesHeader])
|
||||||
let sortedInstances = SavedDataManager.shared.savedInstances(for: mastodonController.accountInfo!)
|
let instances = fetchSavedInstances().map {
|
||||||
instancesSnapshot.append(sortedInstances.map { .savedInstance($0) }, to: .savedInstancesHeader)
|
Item.savedInstance($0.url)
|
||||||
|
}
|
||||||
|
instancesSnapshot.append(instances, to: .savedInstancesHeader)
|
||||||
instancesSnapshot.append([.addSavedInstance], to: .savedInstancesHeader)
|
instancesSnapshot.append([.addSavedInstance], to: .savedInstancesHeader)
|
||||||
self.dataSource.apply(instancesSnapshot, to: .savedInstances, animatingDifferences: false) {
|
self.dataSource.apply(instancesSnapshot, to: .savedInstances, animatingDifferences: false) {
|
||||||
if let selected = selected {
|
if let selected = selected {
|
||||||
|
@ -555,6 +581,7 @@ extension MainSidebarViewController: UICollectionViewDragDelegate {
|
||||||
extension MainSidebarViewController: InstanceTimelineViewControllerDelegate {
|
extension MainSidebarViewController: InstanceTimelineViewControllerDelegate {
|
||||||
func didSaveInstance(url: URL) {
|
func didSaveInstance(url: URL) {
|
||||||
dismiss(animated: true) {
|
dismiss(animated: true) {
|
||||||
|
self.select(item: .savedInstance(url), animated: true)
|
||||||
self.sidebarDelegate?.sidebar(self, didSelectItem: .savedInstance(url))
|
self.sidebarDelegate?.sidebar(self, didSelectItem: .savedInstance(url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,17 @@ class HashtagTimelineViewController: TimelineTableViewController {
|
||||||
|
|
||||||
var toggleSaveButton: UIBarButtonItem!
|
var toggleSaveButton: UIBarButtonItem!
|
||||||
var toggleSaveButtonTitle: String {
|
var toggleSaveButtonTitle: String {
|
||||||
if SavedDataManager.shared.isSaved(hashtag: hashtag, for: mastodonController.accountInfo!) {
|
if isHashtagSaved {
|
||||||
return NSLocalizedString("Unsave", comment: "unsave hashtag button")
|
return NSLocalizedString("Unsave", comment: "unsave hashtag button")
|
||||||
} else {
|
} else {
|
||||||
return NSLocalizedString("Save", comment: "save hashtag button")
|
return NSLocalizedString("Save", comment: "save hashtag button")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isHashtagSaved: Bool {
|
||||||
|
mastodonController.persistentContainer.viewContext.objectExists(for: SavedHashtag.fetchRequest(name: hashtag.name))
|
||||||
|
}
|
||||||
|
|
||||||
init(for hashtag: Hashtag, mastodonController: MastodonController) {
|
init(for hashtag: Hashtag, mastodonController: MastodonController) {
|
||||||
self.hashtag = hashtag
|
self.hashtag = hashtag
|
||||||
|
|
||||||
|
@ -48,11 +52,13 @@ class HashtagTimelineViewController: TimelineTableViewController {
|
||||||
// MARK: - Interaction
|
// MARK: - Interaction
|
||||||
|
|
||||||
@objc func toggleSaveButtonPressed() {
|
@objc func toggleSaveButtonPressed() {
|
||||||
if SavedDataManager.shared.isSaved(hashtag: hashtag, for: mastodonController.accountInfo!) {
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
SavedDataManager.shared.remove(hashtag: hashtag, for: mastodonController.accountInfo!)
|
if let existing = try? context.fetch(SavedHashtag.fetchRequest(name: hashtag.name)).first {
|
||||||
|
context.delete(existing)
|
||||||
} else {
|
} else {
|
||||||
SavedDataManager.shared.add(hashtag: hashtag, for: mastodonController.accountInfo!)
|
_ = SavedHashtag(hashtag: hashtag, context: context)
|
||||||
}
|
}
|
||||||
|
try! context.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,13 @@ class InstanceTimelineViewController: TimelineTableViewController {
|
||||||
let instanceURL: URL
|
let instanceURL: URL
|
||||||
let instanceMastodonController: MastodonController
|
let instanceMastodonController: MastodonController
|
||||||
|
|
||||||
var toggleSaveButton: UIBarButtonItem!
|
private var toggleSaveButton: UIBarButtonItem!
|
||||||
var toggleSaveButtonTitle: String {
|
|
||||||
if SavedDataManager.shared.isSaved(instance: instanceURL, for: parentMastodonController!.accountInfo!) {
|
private var isInstanceSaved: Bool {
|
||||||
|
parentMastodonController!.persistentContainer.viewContext.objectExists(for: SavedInstance.fetchRequest(url: instanceURL))
|
||||||
|
}
|
||||||
|
private var toggleSaveButtonTitle: String {
|
||||||
|
if isInstanceSaved {
|
||||||
return NSLocalizedString("Unsave", comment: "unsave instance button")
|
return NSLocalizedString("Unsave", comment: "unsave instance button")
|
||||||
} else {
|
} else {
|
||||||
return NSLocalizedString("Save", comment: "save instance button")
|
return NSLocalizedString("Save", comment: "save instance button")
|
||||||
|
@ -81,13 +85,16 @@ class InstanceTimelineViewController: TimelineTableViewController {
|
||||||
|
|
||||||
// MARK: - Interaction
|
// MARK: - Interaction
|
||||||
@objc func toggleSaveButtonPressed() {
|
@objc func toggleSaveButtonPressed() {
|
||||||
if SavedDataManager.shared.isSaved(instance: instanceURL, for: parentMastodonController!.accountInfo!) {
|
let context = parentMastodonController!.persistentContainer.viewContext
|
||||||
SavedDataManager.shared.remove(instance: instanceURL, for: parentMastodonController!.accountInfo!)
|
let existing = try? context.fetch(SavedInstance.fetchRequest(url: instanceURL)).first
|
||||||
|
if let existing = existing {
|
||||||
|
context.delete(existing)
|
||||||
delegate?.didUnsaveInstance(url: instanceURL)
|
delegate?.didUnsaveInstance(url: instanceURL)
|
||||||
} else {
|
} else {
|
||||||
SavedDataManager.shared.add(instance: instanceURL, for: parentMastodonController!.accountInfo!)
|
_ = SavedInstance(url: instanceURL, context: context)
|
||||||
delegate?.didSaveInstance(url: instanceURL)
|
delegate?.didSaveInstance(url: instanceURL)
|
||||||
}
|
}
|
||||||
|
try? context.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,18 +97,24 @@ extension MenuActionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
func actionsForHashtag(_ hashtag: Hashtag, sourceView: UIView?) -> [UIMenuElement] {
|
func actionsForHashtag(_ hashtag: Hashtag, sourceView: UIView?) -> [UIMenuElement] {
|
||||||
let account = mastodonController!.accountInfo!
|
let actionsSection: [UIMenuElement]
|
||||||
let saved = SavedDataManager.shared.isSaved(hashtag: hashtag, for: account)
|
if let mastodonController = mastodonController,
|
||||||
|
mastodonController.loggedIn {
|
||||||
let actionsSection = [
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
createAction(identifier: "save", title: saved ? "Unsave Hashtag" : "Save Hashtag", systemImageName: "number", handler: { (_) in
|
let existing = try? context.fetch(SavedHashtag.fetchRequest(name: hashtag.name)).first
|
||||||
if saved {
|
actionsSection = [
|
||||||
SavedDataManager.shared.remove(hashtag: hashtag, for: account)
|
createAction(identifier: "save", title: existing != nil ? "Unsave Hashtag" : "Save Hashtag", systemImageName: "number", handler: { (_) in
|
||||||
} else {
|
if let existing = existing {
|
||||||
SavedDataManager.shared.add(hashtag: hashtag, for: account)
|
context.delete(existing)
|
||||||
}
|
} else {
|
||||||
})
|
_ = SavedHashtag(hashtag: hashtag, context: context)
|
||||||
]
|
}
|
||||||
|
try! context.save()
|
||||||
|
})
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
actionsSection = []
|
||||||
|
}
|
||||||
|
|
||||||
let shareSection = actionsForURL(hashtag.url, sourceView: sourceView)
|
let shareSection = actionsForURL(hashtag.url, sourceView: sourceView)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue