Only migrate changed preferences

This commit is contained in:
Shadowfacts 2024-04-13 22:36:42 -04:00
parent 132fcfa099
commit a4d13ad03b
4 changed files with 111 additions and 69 deletions

View File

@ -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

View File

@ -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),
self.pureBlackDarkMode = legacy.pureBlackDarkMode Migration(from: \.hideCustomEmojiInUsernames, to: \.$hideCustomEmojiInUsernames),
self.accentColor = legacy.accentColor Migration(from: \.showIsStatusReplyIcon, to: \.$showIsStatusReplyIcon),
self.avatarStyle = legacy.avatarStyle Migration(from: \.alwaysShowStatusVisibilityIcon, to: \.$alwaysShowStatusVisibilityIcon),
self.hideCustomEmojiInUsernames = legacy.hideCustomEmojiInUsernames Migration(from: \.hideActionsInTimeline, to: \.$hideActionsInTimeline),
self.showIsStatusReplyIcon = legacy.showIsStatusReplyIcon Migration(from: \.showLinkPreviews, to: \.$showLinkPreviews),
self.alwaysShowStatusVisibilityIcon = legacy.alwaysShowStatusVisibilityIcon Migration(from: \.leadingStatusSwipeActions, to: \.$leadingStatusSwipeActions),
self.hideActionsInTimeline = legacy.hideActionsInTimeline Migration(from: \.trailingStatusSwipeActions, to: \.$trailingStatusSwipeActions),
self.showLinkPreviews = legacy.showLinkPreviews Migration(from: \.widescreenNavigationMode, to: \.$widescreenNavigationMode),
self.leadingStatusSwipeActions = legacy.leadingStatusSwipeActions Migration(from: \.underlineTextLinks, to: \.$underlineTextLinks),
self.trailingStatusSwipeActions = legacy.trailingStatusSwipeActions Migration(from: \.showAttachmentsInTimeline, to: \.$showAttachmentsInTimeline),
if legacy.widescreenNavigationMode != .splitScreen {
self.widescreenNavigationMode = legacy.widescreenNavigationMode
}
self.underlineTextLinks = legacy.underlineTextLinks
self.showAttachmentsInTimeline = legacy.showAttachmentsInTimeline
self.defaultPostVisibility = legacy.defaultPostVisibility Migration(from: \.defaultPostVisibility, to: \.$defaultPostVisibility),
self.defaultReplyVisibility = legacy.defaultReplyVisibility Migration(from: \.defaultReplyVisibility, to: \.$defaultReplyVisibility),
self.requireAttachmentDescriptions = legacy.requireAttachmentDescriptions Migration(from: \.requireAttachmentDescriptions, to: \.$requireAttachmentDescriptions),
self.contentWarningCopyMode = legacy.contentWarningCopyMode Migration(from: \.contentWarningCopyMode, to: \.$contentWarningCopyMode),
self.mentionReblogger = legacy.mentionReblogger Migration(from: \.mentionReblogger, to: \.$mentionReblogger),
self.useTwitterKeyboard = legacy.useTwitterKeyboard Migration(from: \.useTwitterKeyboard, to: \.$useTwitterKeyboard),
self.attachmentBlurMode = legacy.attachmentBlurMode Migration(from: \.attachmentBlurMode, to: \.$attachmentBlurMode),
self.blurMediaBehindContentWarning = legacy.blurMediaBehindContentWarning Migration(from: \.blurMediaBehindContentWarning, to: \.$blurMediaBehindContentWarning),
self.automaticallyPlayGifs = legacy.automaticallyPlayGifs Migration(from: \.automaticallyPlayGifs, to: \.$automaticallyPlayGifs),
self.showUncroppedMediaInline = legacy.showUncroppedMediaInline Migration(from: \.showUncroppedMediaInline, to: \.$showUncroppedMediaInline),
self.showAttachmentBadges = legacy.showAttachmentBadges Migration(from: \.showAttachmentBadges, to: \.$showAttachmentBadges),
self.attachmentAltBadgeInverted = legacy.attachmentAltBadgeInverted Migration(from: \.attachmentAltBadgeInverted, to: \.$attachmentAltBadgeInverted),
self.openLinksInApps = legacy.openLinksInApps Migration(from: \.openLinksInApps, to: \.$openLinksInApps),
self.useInAppSafari = legacy.useInAppSafari Migration(from: \.useInAppSafari, to: \.$useInAppSafari),
self.inAppSafariAutomaticReaderMode = legacy.inAppSafariAutomaticReaderMode Migration(from: \.inAppSafariAutomaticReaderMode, to: \.$inAppSafariAutomaticReaderMode),
self.expandAllContentWarnings = legacy.expandAllContentWarnings Migration(from: \.expandAllContentWarnings, to: \.$expandAllContentWarnings),
self.collapseLongPosts = legacy.collapseLongPosts Migration(from: \.collapseLongPosts, to: \.$collapseLongPosts),
self.oppositeCollapseKeywords = legacy.oppositeCollapseKeywords Migration(from: \.oppositeCollapseKeywords, to: \.$oppositeCollapseKeywords),
self.confirmBeforeReblog = legacy.confirmBeforeReblog Migration(from: \.confirmBeforeReblog, to: \.$confirmBeforeReblog),
self.timelineStateRestoration = legacy.timelineStateRestoration Migration(from: \.timelineStateRestoration, to: \.$timelineStateRestoration),
self.timelineSyncMode = legacy.timelineSyncMode Migration(from: \.timelineSyncMode, to: \.$timelineSyncMode),
self.hideReblogsInTimelines = legacy.hideReblogsInTimelines Migration(from: \.hideReblogsInTimelines, to: \.$hideReblogsInTimelines),
self.hideRepliesInTimelines = legacy.hideRepliesInTimelines Migration(from: \.hideRepliesInTimelines, to: \.$hideRepliesInTimelines),
self.showFavoriteAndReblogCounts = legacy.showFavoriteAndReblogCounts Migration(from: \.showFavoriteAndReblogCounts, to: \.$showFavoriteAndReblogCounts),
self.defaultNotificationsMode = legacy.defaultNotificationsMode Migration(from: \.defaultNotificationsMode, to: \.$defaultNotificationsMode),
self.grayscaleImages = legacy.grayscaleImages Migration(from: \.grayscaleImages, to: \.$grayscaleImages),
self.disableInfiniteScrolling = legacy.disableInfiniteScrolling Migration(from: \.disableInfiniteScrolling, to: \.$disableInfiniteScrolling),
self.hideTrends = legacy.hideTrends Migration(from: \.hideTrends, to: \.$hideTrends),
self.statusContentType = legacy.statusContentType Migration(from: \.statusContentType, to: \.$statusContentType),
self.reportErrorsAutomatically = legacy.reportErrorsAutomatically Migration(from: \.reportErrorsAutomatically, to: \.$reportErrorsAutomatically),
self.enabledFeatureFlags = legacy.enabledFeatureFlags Migration(from: \.enabledFeatureFlags, to: \.$enabledFeatureFlags),
self.hasShownLocalTimelineDescription = legacy.hasShownLocalTimelineDescription Migration(from: \.hasShownLocalTimelineDescription, to: \.$hasShownLocalTimelineDescription),
self.hasShownFederatedTimelineDescription = legacy.hasShownFederatedTimelineDescription 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
}
} }
} }

View File

@ -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)
} }
} }

View File

@ -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)
} }
} }