diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
index 25579daa..bf87198e 100644
--- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
+++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
@@ -155,13 +155,14 @@
-
+
+
@@ -202,7 +203,7 @@
-
+
@@ -212,7 +213,7 @@
-
+
@@ -244,10 +245,10 @@
+
-
-
+
\ No newline at end of file
diff --git a/CoreDataStack/Entity/Setting.swift b/CoreDataStack/Entity/Setting.swift
index a4907f0a..671f9bab 100644
--- a/CoreDataStack/Entity/Setting.swift
+++ b/CoreDataStack/Entity/Setting.swift
@@ -8,11 +8,11 @@
import CoreData
import Foundation
-@objc(Setting)
public final class Setting: NSManagedObject {
@NSManaged public var appearance: String?
@NSManaged public var triggerBy: String?
@NSManaged public var domain: String?
+ @NSManaged public var userID: String?
@NSManaged public private(set) var createdAt: Date
@NSManaged public private(set) var updatedAt: Date
@@ -40,6 +40,7 @@ public extension Setting {
setting.appearance = property.appearance
setting.triggerBy = property.triggerBy
setting.domain = property.domain
+ setting.userID = property.userID
return setting
}
@@ -61,11 +62,13 @@ public extension Setting {
public let appearance: String
public let triggerBy: String
public let domain: String
+ public let userID: String
- public init(appearance: String, triggerBy: String, domain: String) {
+ public init(appearance: String, triggerBy: String, domain: String, userID: String) {
self.appearance = appearance
self.triggerBy = triggerBy
self.domain = domain
+ self.userID = userID
}
}
}
@@ -77,8 +80,11 @@ extension Setting: Managed {
}
extension Setting {
- public static func predicate(domain: String) -> NSPredicate {
- return NSPredicate(format: "%K == %@", #keyPath(Setting.domain), domain)
+ public static func predicate(domain: String, userID: String) -> NSPredicate {
+ return NSPredicate(format: "%K == %@ AND %K == %@",
+ #keyPath(Setting.domain), domain,
+ #keyPath(Setting.userID), userID
+ )
}
}
diff --git a/CoreDataStack/Entity/Subscription.swift b/CoreDataStack/Entity/Subscription.swift
index 7d7a7457..8ced945d 100644
--- a/CoreDataStack/Entity/Subscription.swift
+++ b/CoreDataStack/Entity/Subscription.swift
@@ -9,7 +9,6 @@
import Foundation
import CoreData
-@objc(Subscription)
public final class Subscription: NSManagedObject {
@NSManaged public var id: String
@NSManaged public var endpoint: String
@@ -95,8 +94,8 @@ extension Subscription: Managed {
extension Subscription {
- public static func predicate(id: String) -> NSPredicate {
- return NSPredicate(format: "%K == %@", #keyPath(Subscription.id), id)
+ public static func predicate(type: String) -> NSPredicate {
+ return NSPredicate(format: "%K == %@", #keyPath(Subscription.type), type)
}
}
diff --git a/CoreDataStack/Entity/SubscriptionAlerts.swift b/CoreDataStack/Entity/SubscriptionAlerts.swift
index d1169104..f5abf495 100644
--- a/CoreDataStack/Entity/SubscriptionAlerts.swift
+++ b/CoreDataStack/Entity/SubscriptionAlerts.swift
@@ -20,7 +20,7 @@ public final class SubscriptionAlerts: NSManagedObject {
@NSManaged public private(set) var updatedAt: Date
// MARK: - relationships
- @NSManaged public var pushSubscription: Subscription?
+ @NSManaged public var subscription: Subscription?
}
public extension SubscriptionAlerts {
diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift
index a9b1f0b8..4615f92a 100644
--- a/Mastodon/Scene/Settings/SettingsViewController.swift
+++ b/Mastodon/Scene/Settings/SettingsViewController.swift
@@ -46,7 +46,7 @@ class SettingsViewController: UIViewController, NeedsDependency {
UIAction(title: noOne, image: UIImage(systemName: "nosign"), attributes: []) { [weak self] action in
self?.updateTrigger(by: noOne)
},
- ].reversed()
+ ]
)
return menu
}
@@ -344,10 +344,15 @@ extension SettingsViewController: UITableViewDelegate {
// Update setting into core data
extension SettingsViewController {
func updateTrigger(by who: String) {
+ guard self.viewModel.triggerBy != who else { return }
guard let setting = self.viewModel.setting.value else { return }
- _ = context.managedObjectContext.performChanges {
- setting.update(triggerBy: who)
+ setting.update(triggerBy: who)
+ // trigger to call `subscription` API with POST method
+ // confirm the local data is correct even if request failed
+ // The asynchronous execution is to solve the problem of dropped frames for animations.
+ DispatchQueue.main.async { [weak self] in
+ self?.viewModel.setting.value = setting
}
}
@@ -356,34 +361,35 @@ extension SettingsViewController {
guard let settings = self.viewModel.setting.value else { return }
guard let triggerBy = settings.triggerBy else { return }
- guard let alerts = settings.subscription?.first(where: { (s) -> Bool in
+ if let alerts = settings.subscription?.first(where: { (s) -> Bool in
return s.type == settings.triggerBy
- })?.alert else {
- return
+ })?.alert {
+ var alertValues = [Bool?]()
+ alertValues.append(alerts.favourite?.boolValue)
+ alertValues.append(alerts.follow?.boolValue)
+ alertValues.append(alerts.reblog?.boolValue)
+ alertValues.append(alerts.mention?.boolValue)
+
+ // need to update `alerts` to make update API with correct parameter
+ switch title {
+ case L10n.Scene.Settings.Section.Notifications.favorites:
+ alertValues[0] = isOn
+ alerts.favourite = NSNumber(booleanLiteral: isOn)
+ case L10n.Scene.Settings.Section.Notifications.follows:
+ alertValues[1] = isOn
+ alerts.follow = NSNumber(booleanLiteral: isOn)
+ case L10n.Scene.Settings.Section.Notifications.boosts:
+ alertValues[2] = isOn
+ alerts.reblog = NSNumber(booleanLiteral: isOn)
+ case L10n.Scene.Settings.Section.Notifications.mentions:
+ alertValues[3] = isOn
+ alerts.mention = NSNumber(booleanLiteral: isOn)
+ default: break
+ }
+ self.viewModel.updateSubscriptionSubject.send((triggerBy: triggerBy, values: alertValues))
+ } else if let alertValues = self.viewModel.notificationDefaultValue[triggerBy] {
+ self.viewModel.updateSubscriptionSubject.send((triggerBy: triggerBy, values: alertValues))
}
- var alertValues = [Bool?]()
- alertValues.append(alerts.favourite?.boolValue)
- alertValues.append(alerts.follow?.boolValue)
- alertValues.append(alerts.reblog?.boolValue)
- alertValues.append(alerts.mention?.boolValue)
-
- // need to update `alerts` to make update API with correct parameter
- switch title {
- case L10n.Scene.Settings.Section.Notifications.favorites:
- alertValues[0] = isOn
- alerts.favourite = NSNumber(booleanLiteral: isOn)
- case L10n.Scene.Settings.Section.Notifications.follows:
- alertValues[1] = isOn
- alerts.follow = NSNumber(booleanLiteral: isOn)
- case L10n.Scene.Settings.Section.Notifications.boosts:
- alertValues[2] = isOn
- alerts.reblog = NSNumber(booleanLiteral: isOn)
- case L10n.Scene.Settings.Section.Notifications.mentions:
- alertValues[3] = isOn
- alerts.mention = NSNumber(booleanLiteral: isOn)
- default: break
- }
- self.viewModel.updateSubscriptionSubject.send((triggerBy: triggerBy, values: alertValues))
}
}
@@ -435,7 +441,7 @@ extension SettingsViewController {
guard let setting: Setting? = {
let domain = box.domain
let request = Setting.sortedFetchRequest
- request.predicate = Setting.predicate(domain: domain)
+ request.predicate = Setting.predicate(domain: domain, userID: box.userID)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
diff --git a/Mastodon/Scene/Settings/SettingsViewModel.swift b/Mastodon/Scene/Settings/SettingsViewModel.swift
index b61334f4..470617ae 100644
--- a/Mastodon/Scene/Settings/SettingsViewModel.swift
+++ b/Mastodon/Scene/Settings/SettingsViewModel.swift
@@ -29,7 +29,7 @@ class SettingsViewModel: NSObject, NeedsDependency {
if let box =
self.context.authenticationService.activeMastodonAuthenticationBox.value {
let domain = box.domain
- fetchRequest.predicate = Setting.predicate(domain: domain)
+ fetchRequest.predicate = Setting.predicate(domain: domain, userID: box.userID)
}
fetchRequest.fetchLimit = 1
@@ -78,6 +78,9 @@ class SettingsViewModel: NSObject, NeedsDependency {
return Mastodon.API.privacyURL(domain: box.domain)
}()
+ /// to store who trigger the notification.
+ var triggerBy: String?
+
struct Input {
}
@@ -121,12 +124,14 @@ class SettingsViewModel: NSObject, NeedsDependency {
follow: values[1],
reblog: values[2],
mention: values[3],
- poll: nil)
+ poll: nil
+ )
self.context.apiService.changeSubscription(
domain: domain,
mastodonAuthenticationBox: activeMastodonAuthenticationBox,
query: query,
- triggerBy: triggerBy
+ triggerBy: triggerBy,
+ userID: activeMastodonAuthenticationBox.userID
)
.sink { (_) in
} receiveValue: { (_) in
@@ -164,7 +169,8 @@ class SettingsViewModel: NSObject, NeedsDependency {
domain: domain,
mastodonAuthenticationBox: activeMastodonAuthenticationBox,
query: query,
- triggerBy: triggerBy
+ triggerBy: triggerBy,
+ userID: activeMastodonAuthenticationBox.userID
)
.sink { (_) in
} receiveValue: { (_) in
@@ -178,13 +184,6 @@ class SettingsViewModel: NSObject, NeedsDependency {
// request subsription data for updating or initialization
requestSubscription()
-
- do {
- try fetchResultsController.performFetch()
- setting.value = fetchResultsController.fetchedObjects?.first
- } catch {
- assertionFailure(error.localizedDescription)
- }
return nil
}
@@ -213,12 +212,12 @@ class SettingsViewModel: NSObject, NeedsDependency {
} else if let triggerBy = settings?.triggerBy,
let values = self.notificationDefaultValue[triggerBy] {
switches = values
- self.createSubscriptionSubject.send((triggerBy: triggerBy, values: values))
} else {
// fallback a default value
let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
switches = self.notificationDefaultValue[anyone]
}
+
let notifications = [L10n.Scene.Settings.Section.Notifications.favorites,
L10n.Scene.Settings.Section.Notifications.follows,
L10n.Scene.Settings.Section.Notifications.boosts,
@@ -273,31 +272,61 @@ class SettingsViewModel: NSObject, NeedsDependency {
}
private func requestSubscription() {
- // request subscription of notifications
- typealias SubscriptionResponse = Mastodon.Response.Content
- viewDidLoad.flatMap { [weak self] (_) -> AnyPublisher in
- guard let self = self,
- let activeMastodonAuthenticationBox =
- self.context.authenticationService.activeMastodonAuthenticationBox.value else {
- return Empty().eraseToAnyPublisher()
+ setting.sink { [weak self] (settings) in
+ guard let self = self else { return }
+ guard settings != nil else { return }
+ guard self.triggerBy != settings?.triggerBy else { return }
+ self.triggerBy = settings?.triggerBy
+
+ var switches: [Bool?]?
+ var who: String?
+ if let alerts = settings?.subscription?.first(where: { (s) -> Bool in
+ return s.type == settings?.triggerBy
+ })?.alert {
+ var items = [Bool?]()
+ items.append(alerts.favourite?.boolValue)
+ items.append(alerts.follow?.boolValue)
+ items.append(alerts.reblog?.boolValue)
+ items.append(alerts.mention?.boolValue)
+ switches = items
+ who = settings?.triggerBy
+ } else if let triggerBy = settings?.triggerBy,
+ let values = self.notificationDefaultValue[triggerBy] {
+ switches = values
+ who = triggerBy
+ } else {
+ // fallback a default value
+ let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
+ switches = self.notificationDefaultValue[anyone]
+ who = anyone
}
- let domain = activeMastodonAuthenticationBox.domain
- return self.context.apiService.subscription(
- domain: domain,
- mastodonAuthenticationBox: activeMastodonAuthenticationBox)
- }
- .sink { [weak self] competion in
- if case .failure(_) = competion {
- // create a subscription when doesn't has one
- let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
- if let values = self?.notificationDefaultValue[anyone] {
- self?.createSubscriptionSubject.send((triggerBy: anyone, values: values))
- }
+ // should create a subscription whenever change trigger
+ if let values = switches, let triggerBy = who {
+ self.createSubscriptionSubject.send((triggerBy: triggerBy, values: values))
}
- } receiveValue: { (subscription) in
}
.store(in: &disposeBag)
+
+ guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else {
+ return
+ }
+ let domain = activeMastodonAuthenticationBox.domain
+ let userId = activeMastodonAuthenticationBox.userID
+
+ do {
+ try fetchResultsController.performFetch()
+ if nil == fetchResultsController.fetchedObjects?.first {
+ let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
+ setting.value = self.context.apiService.createSettingIfNeed(domain: domain,
+ userId: userId,
+ triggerBy: anyone)
+ } else {
+ setting.value = fetchResultsController.fetchedObjects?.first
+ }
+ } catch {
+ assertionFailure(error.localizedDescription)
+ }
}
deinit {
diff --git a/Mastodon/Service/APIService/APIService+Subscriptions.swift b/Mastodon/Service/APIService/APIService+Subscriptions.swift
index d67284c7..337ab26d 100644
--- a/Mastodon/Service/APIService/APIService+Subscriptions.swift
+++ b/Mastodon/Service/APIService/APIService+Subscriptions.swift
@@ -5,44 +5,71 @@
// Created by ihugo on 2021/4/9.
//
+import Combine
+import CoreData
+import CoreDataStack
import Foundation
import MastodonSDK
-import Combine
extension APIService {
func subscription(
domain: String,
+ userID: String,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization
-
+ let findSettings: Setting? = {
+ let request = Setting.sortedFetchRequest
+ request.predicate = Setting.predicate(domain: domain, userID: userID)
+ request.fetchLimit = 1
+ request.returnsObjectsAsFaults = false
+ do {
+ return try self.backgroundManagedObjectContext.fetch(request).first
+ } catch {
+ assertionFailure(error.localizedDescription)
+ return nil
+ }
+ }()
+ let triggerBy = findSettings?.triggerBy ?? "anyone"
+ let setting = self.createSettingIfNeed(
+ domain: domain,
+ userId: userID,
+ triggerBy: triggerBy
+ )
return Mastodon.API.Subscriptions.subscription(
session: session,
domain: domain,
- authorization: authorization)
- .flatMap { response -> AnyPublisher, Error> in
- return self.backgroundManagedObjectContext.performChanges {
- _ = APIService.CoreData.createOrMergeSubscription(
- into: self.backgroundManagedObjectContext,
- entity: response.value,
- domain: domain)
- }
- .setFailureType(to: Error.self)
- .map { _ in return response }
- .eraseToAnyPublisher()
- }.eraseToAnyPublisher()
+ authorization: authorization
+ )
+ .flatMap { response -> AnyPublisher, Error> in
+ return self.backgroundManagedObjectContext.performChanges {
+ _ = APIService.CoreData.createOrMergeSubscription(
+ into: self.backgroundManagedObjectContext,
+ entity: response.value,
+ domain: domain,
+ triggerBy: triggerBy,
+ setting: setting)
+ }
+ .setFailureType(to: Error.self)
+ .map { _ in return response }
+ .eraseToAnyPublisher()
+ }.eraseToAnyPublisher()
}
func changeSubscription(
domain: String,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox,
query: Mastodon.API.Subscriptions.CreateSubscriptionQuery,
- triggerBy: String
+ triggerBy: String,
+ userID: String
) -> AnyPublisher, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization
+ let setting = self.createSettingIfNeed(domain: domain,
+ userId: userID,
+ triggerBy: triggerBy)
return Mastodon.API.Subscriptions.createSubscription(
session: session,
domain: domain,
@@ -55,7 +82,9 @@ extension APIService {
into: self.backgroundManagedObjectContext,
entity: response.value,
domain: domain,
- triggerBy: triggerBy)
+ triggerBy: triggerBy,
+ setting: setting
+ )
}
.setFailureType(to: Error.self)
.map { _ in return response }
@@ -67,10 +96,15 @@ extension APIService {
domain: String,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox,
query: Mastodon.API.Subscriptions.UpdateSubscriptionQuery,
- triggerBy: String
+ triggerBy: String,
+ userID: String
) -> AnyPublisher, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization
+ let setting = self.createSettingIfNeed(domain: domain,
+ userId: userID,
+ triggerBy: triggerBy)
+
return Mastodon.API.Subscriptions.updateSubscription(
session: session,
domain: domain,
@@ -83,12 +117,47 @@ extension APIService {
into: self.backgroundManagedObjectContext,
entity: response.value,
domain: domain,
- triggerBy: triggerBy)
+ triggerBy: triggerBy,
+ setting: setting
+ )
}
.setFailureType(to: Error.self)
.map { _ in return response }
.eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
+
+ func createSettingIfNeed(domain: String, userId: String, triggerBy: String) -> Setting {
+ // create setting entity if possible
+ let oldSetting: Setting? = {
+ let request = Setting.sortedFetchRequest
+ request.predicate = Setting.predicate(domain: domain, userID: userId)
+ request.fetchLimit = 1
+ request.returnsObjectsAsFaults = false
+ do {
+ return try backgroundManagedObjectContext.fetch(request).first
+ } catch {
+ assertionFailure(error.localizedDescription)
+ return nil
+ }
+ }()
+ var setting: Setting!
+ if let oldSetting = oldSetting {
+ setting = oldSetting
+ } else {
+ let property = Setting.Property(
+ appearance: "automatic",
+ triggerBy: triggerBy,
+ domain: domain,
+ userID: userId)
+ (setting, _) = APIService.CoreData.createOrMergeSetting(
+ into: backgroundManagedObjectContext,
+ domain: domain,
+ userID: userId,
+ property: property
+ )
+ }
+ return setting
+ }
}
diff --git a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift
index 8dc18973..f5a4022e 100644
--- a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift
+++ b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Subscriptions.swift
@@ -16,11 +16,12 @@ extension APIService.CoreData {
static func createOrMergeSetting(
into managedObjectContext: NSManagedObjectContext,
domain: String,
+ userID: String,
property: Setting.Property
) -> (Subscription: Setting, isCreated: Bool) {
let oldSetting: Setting? = {
let request = Setting.sortedFetchRequest
- request.predicate = Setting.predicate(domain: property.domain)
+ request.predicate = Setting.predicate(domain: property.domain, userID: userID)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
@@ -45,38 +46,12 @@ extension APIService.CoreData {
into managedObjectContext: NSManagedObjectContext,
entity: Mastodon.Entity.Subscription,
domain: String,
- triggerBy: String? = nil
+ triggerBy: String,
+ setting: Setting
) -> (Subscription: Subscription, isCreated: Bool) {
- // create setting entity if possible
- let oldSetting: Setting? = {
- let request = Setting.sortedFetchRequest
- request.predicate = Setting.predicate(domain: domain)
- request.fetchLimit = 1
- request.returnsObjectsAsFaults = false
- do {
- return try managedObjectContext.fetch(request).first
- } catch {
- assertionFailure(error.localizedDescription)
- return nil
- }
- }()
- var setting: Setting!
- if let oldSetting = oldSetting {
- setting = oldSetting
- } else {
- let property = Setting.Property(
- appearance: "automatic",
- triggerBy: "anyone",
- domain: domain)
- (setting, _) = createOrMergeSetting(
- into: managedObjectContext,
- domain: domain,
- property: property)
- }
-
let oldSubscription: Subscription? = {
let request = Subscription.sortedFetchRequest
- request.predicate = Subscription.predicate(id: entity.id)
+ request.predicate = Subscription.predicate(type: triggerBy)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
@@ -91,7 +66,8 @@ extension APIService.CoreData {
endpoint: entity.endpoint,
id: entity.id,
serverKey: entity.serverKey,
- type: triggerBy ?? setting.triggerBy ?? "")
+ type: triggerBy
+ )
let alertEntity = entity.alerts
let alert = SubscriptionAlerts.Property(
favourite: alertEntity.favouriteNumber,
@@ -105,7 +81,8 @@ extension APIService.CoreData {
if nil == oldSubscription.alert {
oldSubscription.alert = SubscriptionAlerts.insert(
into: managedObjectContext,
- property: alert)
+ property: alert
+ )
} else {
oldSubscription.alert?.updateIfNeed(property: alert)
}