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()
}
extension Preference: PreferenceProtocol {
}
struct PreferenceCoding<Wrapped: Codable>: Codable {
let wrapped: Wrapped

View File

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

View File

@ -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,8 +67,7 @@ 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()
@ -77,7 +75,18 @@ final class Preference<Key: PreferenceKey>: Codable {
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)
}
}

View File

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