forked from shadowfacts/Tusker
Only migrate changed preferences
This commit is contained in:
parent
132fcfa099
commit
a4d13ad03b
|
@ -13,6 +13,9 @@ private protocol PreferenceProtocol {
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Preference: PreferenceProtocol {
|
||||||
|
}
|
||||||
|
|
||||||
struct PreferenceCoding<Wrapped: Codable>: Codable {
|
struct PreferenceCoding<Wrapped: Codable>: Codable {
|
||||||
let wrapped: Wrapped
|
let wrapped: Wrapped
|
||||||
|
|
||||||
|
|
|
@ -6,67 +6,97 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
extension PreferenceStore {
|
extension PreferenceStore {
|
||||||
func migrate(from legacy: LegacyPreferences) {
|
func migrate(from legacy: LegacyPreferences) {
|
||||||
self.theme = switch legacy.theme {
|
let migrations: [any MigrationProtocol] = [
|
||||||
case .light: .light
|
Migration(from: \.theme.theme, to: \.$theme),
|
||||||
case .dark: .dark
|
Migration(from: \.pureBlackDarkMode, to: \.$pureBlackDarkMode),
|
||||||
default: .unspecified
|
Migration(from: \.accentColor, to: \.$accentColor),
|
||||||
|
Migration(from: \.avatarStyle, to: \.$avatarStyle),
|
||||||
|
Migration(from: \.hideCustomEmojiInUsernames, to: \.$hideCustomEmojiInUsernames),
|
||||||
|
Migration(from: \.showIsStatusReplyIcon, to: \.$showIsStatusReplyIcon),
|
||||||
|
Migration(from: \.alwaysShowStatusVisibilityIcon, to: \.$alwaysShowStatusVisibilityIcon),
|
||||||
|
Migration(from: \.hideActionsInTimeline, to: \.$hideActionsInTimeline),
|
||||||
|
Migration(from: \.showLinkPreviews, to: \.$showLinkPreviews),
|
||||||
|
Migration(from: \.leadingStatusSwipeActions, to: \.$leadingStatusSwipeActions),
|
||||||
|
Migration(from: \.trailingStatusSwipeActions, to: \.$trailingStatusSwipeActions),
|
||||||
|
Migration(from: \.widescreenNavigationMode, to: \.$widescreenNavigationMode),
|
||||||
|
Migration(from: \.underlineTextLinks, to: \.$underlineTextLinks),
|
||||||
|
Migration(from: \.showAttachmentsInTimeline, to: \.$showAttachmentsInTimeline),
|
||||||
|
|
||||||
|
Migration(from: \.defaultPostVisibility, to: \.$defaultPostVisibility),
|
||||||
|
Migration(from: \.defaultReplyVisibility, to: \.$defaultReplyVisibility),
|
||||||
|
Migration(from: \.requireAttachmentDescriptions, to: \.$requireAttachmentDescriptions),
|
||||||
|
Migration(from: \.contentWarningCopyMode, to: \.$contentWarningCopyMode),
|
||||||
|
Migration(from: \.mentionReblogger, to: \.$mentionReblogger),
|
||||||
|
Migration(from: \.useTwitterKeyboard, to: \.$useTwitterKeyboard),
|
||||||
|
|
||||||
|
Migration(from: \.attachmentBlurMode, to: \.$attachmentBlurMode),
|
||||||
|
Migration(from: \.blurMediaBehindContentWarning, to: \.$blurMediaBehindContentWarning),
|
||||||
|
Migration(from: \.automaticallyPlayGifs, to: \.$automaticallyPlayGifs),
|
||||||
|
Migration(from: \.showUncroppedMediaInline, to: \.$showUncroppedMediaInline),
|
||||||
|
Migration(from: \.showAttachmentBadges, to: \.$showAttachmentBadges),
|
||||||
|
Migration(from: \.attachmentAltBadgeInverted, to: \.$attachmentAltBadgeInverted),
|
||||||
|
|
||||||
|
Migration(from: \.openLinksInApps, to: \.$openLinksInApps),
|
||||||
|
Migration(from: \.useInAppSafari, to: \.$useInAppSafari),
|
||||||
|
Migration(from: \.inAppSafariAutomaticReaderMode, to: \.$inAppSafariAutomaticReaderMode),
|
||||||
|
Migration(from: \.expandAllContentWarnings, to: \.$expandAllContentWarnings),
|
||||||
|
Migration(from: \.collapseLongPosts, to: \.$collapseLongPosts),
|
||||||
|
Migration(from: \.oppositeCollapseKeywords, to: \.$oppositeCollapseKeywords),
|
||||||
|
Migration(from: \.confirmBeforeReblog, to: \.$confirmBeforeReblog),
|
||||||
|
Migration(from: \.timelineStateRestoration, to: \.$timelineStateRestoration),
|
||||||
|
Migration(from: \.timelineSyncMode, to: \.$timelineSyncMode),
|
||||||
|
Migration(from: \.hideReblogsInTimelines, to: \.$hideReblogsInTimelines),
|
||||||
|
Migration(from: \.hideRepliesInTimelines, to: \.$hideRepliesInTimelines),
|
||||||
|
|
||||||
|
Migration(from: \.showFavoriteAndReblogCounts, to: \.$showFavoriteAndReblogCounts),
|
||||||
|
Migration(from: \.defaultNotificationsMode, to: \.$defaultNotificationsMode),
|
||||||
|
Migration(from: \.grayscaleImages, to: \.$grayscaleImages),
|
||||||
|
Migration(from: \.disableInfiniteScrolling, to: \.$disableInfiniteScrolling),
|
||||||
|
Migration(from: \.hideTrends, to: \.$hideTrends),
|
||||||
|
|
||||||
|
Migration(from: \.statusContentType, to: \.$statusContentType),
|
||||||
|
Migration(from: \.reportErrorsAutomatically, to: \.$reportErrorsAutomatically),
|
||||||
|
Migration(from: \.enabledFeatureFlags, to: \.$enabledFeatureFlags),
|
||||||
|
|
||||||
|
Migration(from: \.hasShownLocalTimelineDescription, to: \.$hasShownLocalTimelineDescription),
|
||||||
|
Migration(from: \.hasShownFederatedTimelineDescription, to: \.$hasShownFederatedTimelineDescription),
|
||||||
|
]
|
||||||
|
|
||||||
|
for migration in migrations {
|
||||||
|
migration.migrate(from: legacy, to: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private protocol MigrationProtocol {
|
||||||
|
func migrate(from legacy: LegacyPreferences, to store: PreferenceStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Migration<Key: PreferenceKey>: MigrationProtocol where Key.Value: Equatable {
|
||||||
|
let from: KeyPath<LegacyPreferences, Key.Value>
|
||||||
|
let to: KeyPath<PreferenceStore, PreferencePublisher<Key>>
|
||||||
|
|
||||||
|
func migrate(from legacy: LegacyPreferences, to store: PreferenceStore) {
|
||||||
|
let value = legacy[keyPath: from]
|
||||||
|
if value != Key.defaultValue {
|
||||||
|
Preference.set(enclosingInstance: store, storage: to.appending(path: \.preference), newValue: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UIUserInterfaceStyle {
|
||||||
|
var theme: Theme {
|
||||||
|
switch self {
|
||||||
|
case .light:
|
||||||
|
.light
|
||||||
|
case .dark:
|
||||||
|
.dark
|
||||||
|
default:
|
||||||
|
.unspecified
|
||||||
}
|
}
|
||||||
self.pureBlackDarkMode = legacy.pureBlackDarkMode
|
|
||||||
self.accentColor = legacy.accentColor
|
|
||||||
self.avatarStyle = legacy.avatarStyle
|
|
||||||
self.hideCustomEmojiInUsernames = legacy.hideCustomEmojiInUsernames
|
|
||||||
self.showIsStatusReplyIcon = legacy.showIsStatusReplyIcon
|
|
||||||
self.alwaysShowStatusVisibilityIcon = legacy.alwaysShowStatusVisibilityIcon
|
|
||||||
self.hideActionsInTimeline = legacy.hideActionsInTimeline
|
|
||||||
self.showLinkPreviews = legacy.showLinkPreviews
|
|
||||||
self.leadingStatusSwipeActions = legacy.leadingStatusSwipeActions
|
|
||||||
self.trailingStatusSwipeActions = legacy.trailingStatusSwipeActions
|
|
||||||
if legacy.widescreenNavigationMode != .splitScreen {
|
|
||||||
self.widescreenNavigationMode = legacy.widescreenNavigationMode
|
|
||||||
}
|
|
||||||
self.underlineTextLinks = legacy.underlineTextLinks
|
|
||||||
self.showAttachmentsInTimeline = legacy.showAttachmentsInTimeline
|
|
||||||
|
|
||||||
self.defaultPostVisibility = legacy.defaultPostVisibility
|
|
||||||
self.defaultReplyVisibility = legacy.defaultReplyVisibility
|
|
||||||
self.requireAttachmentDescriptions = legacy.requireAttachmentDescriptions
|
|
||||||
self.contentWarningCopyMode = legacy.contentWarningCopyMode
|
|
||||||
self.mentionReblogger = legacy.mentionReblogger
|
|
||||||
self.useTwitterKeyboard = legacy.useTwitterKeyboard
|
|
||||||
|
|
||||||
self.attachmentBlurMode = legacy.attachmentBlurMode
|
|
||||||
self.blurMediaBehindContentWarning = legacy.blurMediaBehindContentWarning
|
|
||||||
self.automaticallyPlayGifs = legacy.automaticallyPlayGifs
|
|
||||||
self.showUncroppedMediaInline = legacy.showUncroppedMediaInline
|
|
||||||
self.showAttachmentBadges = legacy.showAttachmentBadges
|
|
||||||
self.attachmentAltBadgeInverted = legacy.attachmentAltBadgeInverted
|
|
||||||
|
|
||||||
self.openLinksInApps = legacy.openLinksInApps
|
|
||||||
self.useInAppSafari = legacy.useInAppSafari
|
|
||||||
self.inAppSafariAutomaticReaderMode = legacy.inAppSafariAutomaticReaderMode
|
|
||||||
self.expandAllContentWarnings = legacy.expandAllContentWarnings
|
|
||||||
self.collapseLongPosts = legacy.collapseLongPosts
|
|
||||||
self.oppositeCollapseKeywords = legacy.oppositeCollapseKeywords
|
|
||||||
self.confirmBeforeReblog = legacy.confirmBeforeReblog
|
|
||||||
self.timelineStateRestoration = legacy.timelineStateRestoration
|
|
||||||
self.timelineSyncMode = legacy.timelineSyncMode
|
|
||||||
self.hideReblogsInTimelines = legacy.hideReblogsInTimelines
|
|
||||||
self.hideRepliesInTimelines = legacy.hideRepliesInTimelines
|
|
||||||
|
|
||||||
self.showFavoriteAndReblogCounts = legacy.showFavoriteAndReblogCounts
|
|
||||||
self.defaultNotificationsMode = legacy.defaultNotificationsMode
|
|
||||||
self.grayscaleImages = legacy.grayscaleImages
|
|
||||||
self.disableInfiniteScrolling = legacy.disableInfiniteScrolling
|
|
||||||
self.hideTrends = legacy.hideTrends
|
|
||||||
|
|
||||||
self.statusContentType = legacy.statusContentType
|
|
||||||
self.reportErrorsAutomatically = legacy.reportErrorsAutomatically
|
|
||||||
self.enabledFeatureFlags = legacy.enabledFeatureFlags
|
|
||||||
|
|
||||||
self.hasShownLocalTimelineDescription = legacy.hasShownLocalTimelineDescription
|
|
||||||
self.hasShownFederatedTimelineDescription = legacy.hasShownFederatedTimelineDescription
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,10 +45,10 @@ final class Preference<Key: PreferenceKey>: Codable {
|
||||||
storage storageKeyPath: ReferenceWritableKeyPath<PreferenceStore, Preference>
|
storage storageKeyPath: ReferenceWritableKeyPath<PreferenceStore, Preference>
|
||||||
) -> Key.Value {
|
) -> Key.Value {
|
||||||
get {
|
get {
|
||||||
get(enclosingInstance: instance, wrapped: wrappedKeyPath, storage: storageKeyPath)
|
get(enclosingInstance: instance, storage: storageKeyPath)
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
set(enclosingInstance: instance, wrapped: wrappedKeyPath, storage: storageKeyPath, newValue: newValue)
|
set(enclosingInstance: instance, storage: storageKeyPath, newValue: newValue)
|
||||||
Key.didSet(in: instance, newValue: newValue)
|
Key.didSet(in: instance, newValue: newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,8 +57,7 @@ final class Preference<Key: PreferenceKey>: Codable {
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
static func get<Enclosing>(
|
static func get<Enclosing>(
|
||||||
enclosingInstance: Enclosing,
|
enclosingInstance: Enclosing,
|
||||||
wrapped: ReferenceWritableKeyPath<Enclosing, Key.Value>,
|
storage: KeyPath<Enclosing, Preference>
|
||||||
storage: ReferenceWritableKeyPath<Enclosing, Preference>
|
|
||||||
) -> Key.Value where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher {
|
) -> Key.Value where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||||
let pref = enclosingInstance[keyPath: storage]
|
let pref = enclosingInstance[keyPath: storage]
|
||||||
return pref.storedValue ?? Key.defaultValue
|
return pref.storedValue ?? Key.defaultValue
|
||||||
|
@ -68,8 +67,7 @@ final class Preference<Key: PreferenceKey>: Codable {
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
static func set<Enclosing>(
|
static func set<Enclosing>(
|
||||||
enclosingInstance: Enclosing,
|
enclosingInstance: Enclosing,
|
||||||
wrapped: ReferenceWritableKeyPath<Enclosing, Key.Value>,
|
storage: KeyPath<Enclosing, Preference>,
|
||||||
storage: ReferenceWritableKeyPath<Enclosing, Preference>,
|
|
||||||
newValue: Key.Value
|
newValue: Key.Value
|
||||||
) where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher {
|
) where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||||
enclosingInstance.objectWillChange.send()
|
enclosingInstance.objectWillChange.send()
|
||||||
|
@ -77,7 +75,18 @@ final class Preference<Key: PreferenceKey>: Codable {
|
||||||
pref.storedValue = newValue
|
pref.storedValue = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectedValue: some Publisher<Key.Value, Never> {
|
var projectedValue: PreferencePublisher<Key> {
|
||||||
$storedValue.map { $0 ?? Key.defaultValue }
|
.init(preference: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PreferencePublisher<Key: PreferenceKey>: Publisher {
|
||||||
|
typealias Output = Key.Value
|
||||||
|
typealias Failure = Never
|
||||||
|
|
||||||
|
let preference: Preference<Key>
|
||||||
|
|
||||||
|
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Key.Value == S.Input {
|
||||||
|
preference.$storedValue.map { $0 ?? Key.defaultValue }.receive(subscriber: subscriber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,10 @@ final class PreferenceStoreTests: XCTestCase {
|
||||||
// the acutal subscript expects the enclosingInstance to be a PreferenceStore, so do it manually
|
// the acutal subscript expects the enclosingInstance to be a PreferenceStore, so do it manually
|
||||||
var test: Bool {
|
var test: Bool {
|
||||||
get {
|
get {
|
||||||
Preference.get(enclosingInstance: self, wrapped: \.test, storage: \._test)
|
Preference.get(enclosingInstance: self, storage: \._test)
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
Preference.set(enclosingInstance: self, wrapped: \.test, storage: \._test, newValue: newValue)
|
Preference.set(enclosingInstance: self, storage: \._test, newValue: newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue