forked from shadowfacts/Tusker
parent
e0d97cd2a8
commit
d13b517128
|
@ -12,10 +12,11 @@ import Pachyderm
|
|||
import Combine
|
||||
import OSLog
|
||||
import Sentry
|
||||
import CloudKit
|
||||
|
||||
fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentStore")
|
||||
|
||||
class MastodonCachePersistentStore: NSPersistentContainer {
|
||||
class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
|
||||
|
||||
private static let managedObjectModel: NSManagedObjectModel = {
|
||||
let url = Bundle.main.url(forResource: "Tusker", withExtension: "momd")!
|
||||
|
@ -46,6 +47,9 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
|||
let relationshipSubject = PassthroughSubject<String, Never>()
|
||||
|
||||
init(for accountInfo: LocalData.UserAccountInfo?, transient: Bool = false) {
|
||||
let group = DispatchGroup()
|
||||
var instancesToMigrate: [URL]? = nil
|
||||
var hashtagsToMigrate: [Hashtag]? = nil
|
||||
if transient {
|
||||
super.init(name: "transient_cache", managedObjectModel: MastodonCachePersistentStore.managedObjectModel)
|
||||
|
||||
|
@ -55,6 +59,24 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
|||
} else {
|
||||
super.init(name: "\(accountInfo!.persistenceKey)_cache", managedObjectModel: MastodonCachePersistentStore.managedObjectModel)
|
||||
|
||||
var localStoreLocation = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
localStoreLocation.appendPathComponent("\(accountInfo!.persistenceKey)_cache.sqlite", isDirectory: false)
|
||||
let localStoreDescription = NSPersistentStoreDescription(url: localStoreLocation)
|
||||
localStoreDescription.configuration = "Local"
|
||||
|
||||
var cloudStoreLocation = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
cloudStoreLocation.appendPathComponent("cloud.sqlite", isDirectory: false)
|
||||
let cloudStoreDescription = NSPersistentStoreDescription(url: cloudStoreLocation)
|
||||
cloudStoreDescription.configuration = "Cloud"
|
||||
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.space.vaccor.Tusker")
|
||||
options.databaseScope = .private
|
||||
cloudStoreDescription.cloudKitContainerOptions = options
|
||||
|
||||
persistentStoreDescriptions = [
|
||||
cloudStoreDescription,
|
||||
localStoreDescription,
|
||||
]
|
||||
|
||||
// workaround for migrating from using id in name to persistenceKey
|
||||
// can be removed after a sufficient time has passed
|
||||
if accountInfo!.id.contains("/") {
|
||||
|
@ -82,15 +104,65 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
|||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
// migrate saved data from local store to cloud store
|
||||
// this can be removed pre-app store release
|
||||
var defaultPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
defaultPath.appendPathComponent("\(accountInfo!.persistenceKey)_cache.sqlite", isDirectory: false)
|
||||
if FileManager.default.fileExists(atPath: defaultPath.path) {
|
||||
group.enter()
|
||||
let defaultDesc = NSPersistentStoreDescription(url: defaultPath)
|
||||
defaultDesc.configuration = "Default"
|
||||
let defaultPSC = NSPersistentContainer(name: "\(accountInfo!.persistenceKey)_cache", managedObjectModel: MastodonCachePersistentStore.managedObjectModel)
|
||||
defaultPSC.persistentStoreDescriptions = [defaultDesc]
|
||||
defaultPSC.loadPersistentStores { _, error in
|
||||
guard error == nil else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
defaultPSC.performBackgroundTask { context in
|
||||
if let instances = try? context.fetch(SavedInstance.fetchRequestWithoutAccountForMigrating()) {
|
||||
instancesToMigrate = instances.map(\.url)
|
||||
instances.forEach(context.delete(_:))
|
||||
}
|
||||
if let hashtags = try? context.fetch(SavedHashtag.fetchRequestWithoutAccountForMigrating()) {
|
||||
hashtagsToMigrate = hashtags.map { Hashtag(name: $0.name, url: $0.url) }
|
||||
hashtags.forEach(context.delete(_:))
|
||||
}
|
||||
if context.hasChanges {
|
||||
try? context.save()
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.wait()
|
||||
loadPersistentStores { (description, error) in
|
||||
if let error = error {
|
||||
logger.error("Unable to load persistent store: \(String(describing: error), privacy: .public)")
|
||||
fatalError("Unable to load persistent store")
|
||||
}
|
||||
|
||||
if description.configuration == "Cloud" {
|
||||
self.backgroundContext.perform {
|
||||
instancesToMigrate?.forEach({ url in
|
||||
_ = SavedInstance(url: url, account: accountInfo!, context: self.backgroundContext)
|
||||
})
|
||||
hashtagsToMigrate?.forEach({ hashtag in
|
||||
_ = SavedHashtag(hashtag: hashtag, account: accountInfo!, context: self.backgroundContext)
|
||||
})
|
||||
self.save(context: self.backgroundContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// changes to the Cloud CD model in development need this to be uncommented to update the CK schema
|
||||
// #if DEBUG
|
||||
// try! initializeCloudKitSchema(options: [])
|
||||
// #endif
|
||||
|
||||
viewContext.automaticallyMergesChangesFromParent = true
|
||||
viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
|
||||
|
||||
|
|
|
@ -14,24 +14,32 @@ import WebURLFoundationExtras
|
|||
@objc(SavedHashtag)
|
||||
public final class SavedHashtag: NSManagedObject {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<SavedHashtag> {
|
||||
@nonobjc class func fetchRequestWithoutAccountForMigrating() -> NSFetchRequest<SavedHashtag> {
|
||||
return NSFetchRequest<SavedHashtag>(entityName: "SavedHashtag")
|
||||
}
|
||||
|
||||
@nonobjc public class func fetchRequest(name: String) -> NSFetchRequest<SavedHashtag> {
|
||||
@nonobjc class func fetchRequest(account: LocalData.UserAccountInfo) -> NSFetchRequest<SavedHashtag> {
|
||||
let req = NSFetchRequest<SavedHashtag>(entityName: "SavedHashtag")
|
||||
req.predicate = NSPredicate(format: "name LIKE[cd] %@", name)
|
||||
req.predicate = NSPredicate(format: "accountID = %@", account.id)
|
||||
return req
|
||||
}
|
||||
|
||||
@nonobjc class func fetchRequest(name: String, account: LocalData.UserAccountInfo) -> NSFetchRequest<SavedHashtag> {
|
||||
let req = NSFetchRequest<SavedHashtag>(entityName: "SavedHashtag")
|
||||
req.predicate = NSPredicate(format: "name LIKE[cd] %@ AND accountID = %@", name, account.id)
|
||||
return req
|
||||
}
|
||||
|
||||
@NSManaged public var accountID: String
|
||||
@NSManaged public var name: String
|
||||
@NSManaged public var url: URL
|
||||
|
||||
}
|
||||
|
||||
extension SavedHashtag {
|
||||
convenience init(hashtag: Hashtag, context: NSManagedObjectContext) {
|
||||
convenience init(hashtag: Hashtag, account: LocalData.UserAccountInfo, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
self.accountID = account.id
|
||||
self.name = hashtag.name
|
||||
self.url = URL(hashtag.url)!
|
||||
}
|
||||
|
|
|
@ -12,23 +12,31 @@ import CoreData
|
|||
@objc(SavedInstance)
|
||||
public final class SavedInstance: NSManagedObject {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<SavedInstance> {
|
||||
@nonobjc class func fetchRequestWithoutAccountForMigrating() -> 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)
|
||||
@nonobjc class func fetchRequest(account: LocalData.UserAccountInfo) -> NSFetchRequest<SavedInstance> {
|
||||
let req = NSFetchRequest<SavedInstance>(entityName: "SavedInstance")
|
||||
req.predicate = NSPredicate(format: "accountID = %@", account.id)
|
||||
return req
|
||||
}
|
||||
|
||||
@nonobjc class func fetchRequest(url: URL, account: LocalData.UserAccountInfo) -> NSFetchRequest<SavedInstance> {
|
||||
let req = NSFetchRequest<SavedInstance>(entityName: "SavedInstance")
|
||||
req.predicate = NSPredicate(format: "url = %@ AND accountID = %@", url as NSURL, account.id)
|
||||
return req
|
||||
}
|
||||
|
||||
@NSManaged public var accountID: String
|
||||
@NSManaged public var url: URL
|
||||
|
||||
}
|
||||
|
||||
extension SavedInstance {
|
||||
convenience init(url: URL, context: NSManagedObjectContext) {
|
||||
convenience init(url: URL, account: LocalData.UserAccountInfo, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
self.accountID = account.id
|
||||
self.url = url
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21512" systemVersion="22A380" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22A380" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Account" representedClassName="AccountMO" syncable="YES">
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="avatar" optional="YES" attributeType="URI"/>
|
||||
|
@ -60,21 +60,13 @@
|
|||
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="relationship" inverseEntity="Account"/>
|
||||
</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>
|
||||
<attribute name="accountID" optional="YES" attributeType="String"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||
</entity>
|
||||
<entity name="SavedInstance" representedClassName="SavedInstance" syncable="YES">
|
||||
<attribute name="url" attributeType="URI"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="url"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
<attribute name="accountID" optional="YES" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||
</entity>
|
||||
<entity name="Status" representedClassName="StatusMO" syncable="YES">
|
||||
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
||||
|
@ -117,4 +109,17 @@
|
|||
<attribute name="timelineKind" attributeType="String" valueTransformerName="pachydermTimeline" customClassName="Tusker.TimelineContainer"/>
|
||||
<relationship name="statuses" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="Status" inverseName="timelines" inverseEntity="Status"/>
|
||||
</entity>
|
||||
<configuration name="Cloud" usedWithCloudKit="YES">
|
||||
<memberEntity name="SavedHashtag"/>
|
||||
<memberEntity name="SavedInstance"/>
|
||||
</configuration>
|
||||
<configuration name="Local">
|
||||
<memberEntity name="Account"/>
|
||||
<memberEntity name="Filter"/>
|
||||
<memberEntity name="FilterKeyword"/>
|
||||
<memberEntity name="FollowedHashtag"/>
|
||||
<memberEntity name="Relationship"/>
|
||||
<memberEntity name="Status"/>
|
||||
<memberEntity name="TimelineState"/>
|
||||
</configuration>
|
||||
</model>
|
|
@ -2,37 +2,6 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SentryDSN</key>
|
||||
<string>$(SENTRY_DSN)</string>
|
||||
<key>OSLogPreferences</key>
|
||||
<dict>
|
||||
<key>space.vaccor.Tusker</key>
|
||||
<dict>
|
||||
<key>DEFAULT-OPTIONS</key>
|
||||
<dict>
|
||||
<key>TTL</key>
|
||||
<dict>
|
||||
<key>Fault</key>
|
||||
<integer>30</integer>
|
||||
<key>Error</key>
|
||||
<integer>30</integer>
|
||||
<key>Debug</key>
|
||||
<integer>15</integer>
|
||||
<key>Info</key>
|
||||
<integer>30</integer>
|
||||
<key>Default</key>
|
||||
<integer>30</integer>
|
||||
</dict>
|
||||
<key>Level</key>
|
||||
<dict>
|
||||
<key>Persist</key>
|
||||
<string>Debug</string>
|
||||
<key>Enable</key>
|
||||
<string>Debug</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
@ -87,7 +56,7 @@
|
|||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Post videos from the camera.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Save photos directly from other people's posts.</string>
|
||||
<string>Save photos directly from other people's posts.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Post photos from the photo library.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
|
@ -102,6 +71,37 @@
|
|||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-profile</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.main-scene</string>
|
||||
</array>
|
||||
<key>OSLogPreferences</key>
|
||||
<dict>
|
||||
<key>space.vaccor.Tusker</key>
|
||||
<dict>
|
||||
<key>DEFAULT-OPTIONS</key>
|
||||
<dict>
|
||||
<key>Level</key>
|
||||
<dict>
|
||||
<key>Enable</key>
|
||||
<string>Debug</string>
|
||||
<key>Persist</key>
|
||||
<string>Debug</string>
|
||||
</dict>
|
||||
<key>TTL</key>
|
||||
<dict>
|
||||
<key>Debug</key>
|
||||
<integer>15</integer>
|
||||
<key>Default</key>
|
||||
<integer>30</integer>
|
||||
<key>Error</key>
|
||||
<integer>30</integer>
|
||||
<key>Fault</key>
|
||||
<integer>30</integer>
|
||||
<key>Info</key>
|
||||
<integer>30</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SentryDSN</key>
|
||||
<string>$(SENTRY_DSN)</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
@ -140,6 +140,7 @@
|
|||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
|
|
|
@ -400,7 +400,8 @@ struct ComposeAutocompleteHashtagsView: View {
|
|||
}
|
||||
|
||||
private func updateHashtags(searchResults: [Hashtag], trendingTags: [Hashtag], query: String) {
|
||||
let savedTags = ((try? mastodonController.persistentContainer.viewContext.fetch(SavedHashtag.fetchRequest())) ?? [])
|
||||
let req = SavedHashtag.fetchRequest(account: mastodonController.accountInfo!)
|
||||
let savedTags = ((try? mastodonController.persistentContainer.viewContext.fetch(req)) ?? [])
|
||||
.map { Hashtag(name: $0.name, url: $0.url) }
|
||||
|
||||
hashtags = (searchResults + savedTags + trendingTags)
|
||||
|
|
|
@ -206,7 +206,8 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
|
||||
@MainActor
|
||||
private func fetchHashtagItems(followed: [FollowedHashtag]) -> [Item] {
|
||||
let saved = (try? mastodonController.persistentContainer.viewContext.fetch(SavedHashtag.fetchRequest())) ?? []
|
||||
let req = SavedHashtag.fetchRequest(account: mastodonController.accountInfo!)
|
||||
let saved = (try? mastodonController.persistentContainer.viewContext.fetch(req)) ?? []
|
||||
var items = saved.map {
|
||||
Item.savedHashtag(Hashtag(name: $0.name, url: $0.url))
|
||||
}
|
||||
|
@ -219,7 +220,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
|
||||
@MainActor
|
||||
private func fetchSavedInstances() -> [SavedInstance] {
|
||||
let req = SavedInstance.fetchRequest()
|
||||
let req = SavedInstance.fetchRequest(account: mastodonController.accountInfo!)
|
||||
req.sortDescriptors = [NSSortDescriptor(key: "url.host", ascending: true)]
|
||||
do {
|
||||
return try mastodonController.persistentContainer.viewContext.fetch(req)
|
||||
|
@ -278,7 +279,8 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
|
||||
func removeSavedHashtag(_ hashtag: Hashtag) {
|
||||
let context = mastodonController.persistentContainer.viewContext
|
||||
if let hashtag = try? context.fetch(SavedHashtag.fetchRequest(name: hashtag.name)).first {
|
||||
let req = SavedHashtag.fetchRequest(name: hashtag.name, account: mastodonController.accountInfo!)
|
||||
if let hashtag = try? context.fetch(req).first {
|
||||
context.delete(hashtag)
|
||||
try! context.save()
|
||||
}
|
||||
|
@ -286,7 +288,8 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
|
||||
func removeSavedInstance(_ instanceURL: URL) {
|
||||
let context = mastodonController.persistentContainer.viewContext
|
||||
if let instance = try? context.fetch(SavedInstance.fetchRequest(url: instanceURL)).first {
|
||||
let req = SavedInstance.fetchRequest(url: instanceURL, account: mastodonController.accountInfo!)
|
||||
if let instance = try? context.fetch(req).first {
|
||||
context.delete(instance)
|
||||
try! context.save()
|
||||
}
|
||||
|
|
|
@ -232,7 +232,8 @@ class MainSidebarViewController: UIViewController {
|
|||
|
||||
@MainActor
|
||||
private func fetchHashtagItems(followed: [FollowedHashtag]) -> [Item] {
|
||||
let saved = (try? mastodonController.persistentContainer.viewContext.fetch(SavedHashtag.fetchRequest())) ?? []
|
||||
let req = SavedHashtag.fetchRequest(account: mastodonController.accountInfo!)
|
||||
let saved = (try? mastodonController.persistentContainer.viewContext.fetch(req)) ?? []
|
||||
var items = saved.map {
|
||||
Item.savedHashtag(Hashtag(name: $0.name, url: $0.url))
|
||||
}
|
||||
|
@ -245,7 +246,7 @@ class MainSidebarViewController: UIViewController {
|
|||
|
||||
@MainActor
|
||||
private func fetchSavedInstances() -> [SavedInstance] {
|
||||
let req = SavedInstance.fetchRequest()
|
||||
let req = SavedInstance.fetchRequest(account: mastodonController.accountInfo!)
|
||||
req.sortDescriptors = [NSSortDescriptor(key: "url.host", ascending: true)]
|
||||
do {
|
||||
return try mastodonController.persistentContainer.viewContext.fetch(req)
|
||||
|
|
|
@ -39,7 +39,7 @@ class OnboardingViewController: UINavigationController {
|
|||
|
||||
@MainActor
|
||||
private func tryLoginTo(instanceURL: URL) async throws {
|
||||
let mastodonController = MastodonController(instanceURL: instanceURL)
|
||||
let mastodonController = MastodonController(instanceURL: instanceURL, transient: true)
|
||||
let clientID: String
|
||||
let clientSecret: String
|
||||
do {
|
||||
|
|
|
@ -16,7 +16,8 @@ class HashtagTimelineViewController: TimelineViewController {
|
|||
var toggleSaveButton: UIBarButtonItem!
|
||||
|
||||
private var isHashtagSaved: Bool {
|
||||
mastodonController.persistentContainer.viewContext.objectExists(for: SavedHashtag.fetchRequest(name: hashtag.name))
|
||||
let req = SavedHashtag.fetchRequest(name: hashtag.name, account: mastodonController.accountInfo!)
|
||||
return mastodonController.persistentContainer.viewContext.objectExists(for: req)
|
||||
}
|
||||
|
||||
private var isHashtagFollowed: Bool {
|
||||
|
@ -47,10 +48,10 @@ class HashtagTimelineViewController: TimelineViewController {
|
|||
|
||||
private func toggleSave() {
|
||||
let context = mastodonController.persistentContainer.viewContext
|
||||
if let existing = try? context.fetch(SavedHashtag.fetchRequest(name: hashtag.name)).first {
|
||||
if let existing = try? context.fetch(SavedHashtag.fetchRequest(name: hashtag.name, account: mastodonController.accountInfo!)).first {
|
||||
context.delete(existing)
|
||||
} else {
|
||||
_ = SavedHashtag(hashtag: hashtag, context: context)
|
||||
_ = SavedHashtag(hashtag: hashtag, account: mastodonController.accountInfo!, context: context)
|
||||
}
|
||||
mastodonController.persistentContainer.save(context: context)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ class InstanceTimelineViewController: TimelineViewController {
|
|||
private var toggleSaveButton: UIBarButtonItem!
|
||||
|
||||
private var isInstanceSaved: Bool {
|
||||
parentMastodonController!.persistentContainer.viewContext.objectExists(for: SavedInstance.fetchRequest(url: instanceURL))
|
||||
let req = SavedInstance.fetchRequest(url: instanceURL, account: parentMastodonController!.accountInfo!)
|
||||
return parentMastodonController!.persistentContainer.viewContext.objectExists(for: req)
|
||||
}
|
||||
private var toggleSaveButtonTitle: String {
|
||||
if isInstanceSaved {
|
||||
|
@ -83,12 +84,13 @@ class InstanceTimelineViewController: TimelineViewController {
|
|||
// MARK: - Interaction
|
||||
@objc func toggleSaveButtonPressed() {
|
||||
let context = parentMastodonController!.persistentContainer.viewContext
|
||||
let existing = try? context.fetch(SavedInstance.fetchRequest(url: instanceURL)).first
|
||||
let req = SavedInstance.fetchRequest(url: instanceURL, account: parentMastodonController!.accountInfo!)
|
||||
let existing = try? context.fetch(req).first
|
||||
if let existing = existing {
|
||||
context.delete(existing)
|
||||
delegate?.didUnsaveInstance(url: instanceURL)
|
||||
} else {
|
||||
_ = SavedInstance(url: instanceURL, context: context)
|
||||
_ = SavedInstance(url: instanceURL, account: parentMastodonController!.accountInfo!, context: context)
|
||||
delegate?.didSaveInstance(url: instanceURL)
|
||||
}
|
||||
mastodonController.persistentContainer.save(context: context)
|
||||
|
|
|
@ -111,7 +111,7 @@ extension MenuActionProvider {
|
|||
mastodonController.loggedIn {
|
||||
let name = hashtag.name.lowercased()
|
||||
let context = mastodonController.persistentContainer.viewContext
|
||||
let existing = try? context.fetch(SavedHashtag.fetchRequest(name: name)).first
|
||||
let existing = try? context.fetch(SavedHashtag.fetchRequest(name: name, account: mastodonController.accountInfo!)).first
|
||||
let saveSubtitle = "Saved hashtags appear in the Explore section of Tusker"
|
||||
let saveImage = UIImage(systemName: existing != nil ? "minus" : "plus")
|
||||
actionsSection = [
|
||||
|
@ -119,7 +119,7 @@ extension MenuActionProvider {
|
|||
if let existing = existing {
|
||||
context.delete(existing)
|
||||
} else {
|
||||
_ = SavedHashtag(hashtag: hashtag, context: context)
|
||||
_ = SavedHashtag(hashtag: hashtag, account: mastodonController.accountInfo!, context: context)
|
||||
}
|
||||
mastodonController.persistentContainer.save(context: context)
|
||||
})
|
||||
|
|
|
@ -2,6 +2,18 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.space.vaccor.Tusker</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
<string>CloudKit</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
|
|
Loading…
Reference in New Issue