Only migrate changed preferences
This commit is contained in:
parent
132fcfa099
commit
a4d13ad03b
|
@ -13,6 +13,9 @@ private protocol PreferenceProtocol {
|
|||
init()
|
||||
}
|
||||
|
||||
extension Preference: PreferenceProtocol {
|
||||
}
|
||||
|
||||
struct PreferenceCoding<Wrapped: Codable>: Codable {
|
||||
let wrapped: Wrapped
|
||||
|
||||
|
|
|
@ -6,67 +6,97 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension PreferenceStore {
|
||||
func migrate(from legacy: LegacyPreferences) {
|
||||
self.theme = switch legacy.theme {
|
||||
case .light: .light
|
||||
case .dark: .dark
|
||||
default: .unspecified
|
||||
let migrations: [any MigrationProtocol] = [
|
||||
Migration(from: \.theme.theme, to: \.$theme),
|
||||
Migration(from: \.pureBlackDarkMode, to: \.$pureBlackDarkMode),
|
||||
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>
|
||||
) -> Key.Value {
|
||||
get {
|
||||
get(enclosingInstance: instance, wrapped: wrappedKeyPath, storage: storageKeyPath)
|
||||
get(enclosingInstance: instance, storage: storageKeyPath)
|
||||
}
|
||||
set {
|
||||
set(enclosingInstance: instance, wrapped: wrappedKeyPath, storage: storageKeyPath, newValue: newValue)
|
||||
set(enclosingInstance: instance, storage: storageKeyPath, newValue: newValue)
|
||||
Key.didSet(in: instance, newValue: newValue)
|
||||
}
|
||||
}
|
||||
|
@ -57,8 +57,7 @@ final class Preference<Key: PreferenceKey>: Codable {
|
|||
@inline(__always)
|
||||
static func get<Enclosing>(
|
||||
enclosingInstance: Enclosing,
|
||||
wrapped: ReferenceWritableKeyPath<Enclosing, Key.Value>,
|
||||
storage: ReferenceWritableKeyPath<Enclosing, Preference>
|
||||
storage: KeyPath<Enclosing, Preference>
|
||||
) -> Key.Value where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||
let pref = enclosingInstance[keyPath: storage]
|
||||
return pref.storedValue ?? Key.defaultValue
|
||||
|
@ -68,16 +67,26 @@ final class Preference<Key: PreferenceKey>: Codable {
|
|||
@inline(__always)
|
||||
static func set<Enclosing>(
|
||||
enclosingInstance: Enclosing,
|
||||
wrapped: ReferenceWritableKeyPath<Enclosing, Key.Value>,
|
||||
storage: ReferenceWritableKeyPath<Enclosing, Preference>,
|
||||
storage: KeyPath<Enclosing, Preference>,
|
||||
newValue: Key.Value
|
||||
) where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||
enclosingInstance.objectWillChange.send()
|
||||
let pref = enclosingInstance[keyPath: storage]
|
||||
pref.storedValue = newValue
|
||||
}
|
||||
|
||||
var projectedValue: some Publisher<Key.Value, Never> {
|
||||
$storedValue.map { $0 ?? Key.defaultValue }
|
||||
|
||||
var projectedValue: PreferencePublisher<Key> {
|
||||
.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
|
||||
var test: Bool {
|
||||
get {
|
||||
Preference.get(enclosingInstance: self, wrapped: \.test, storage: \._test)
|
||||
Preference.get(enclosingInstance: self, storage: \._test)
|
||||
}
|
||||
set {
|
||||
Preference.set(enclosingInstance: self, wrapped: \.test, storage: \._test, newValue: newValue)
|
||||
Preference.set(enclosingInstance: self, storage: \._test, newValue: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue