Migrate drafts to SwiftData

This commit is contained in:
Thomas Ricouard 2023-09-22 09:31:35 +02:00
parent 7eec1b8439
commit 0c4bde40af
8 changed files with 123 additions and 61 deletions

View file

@ -47,7 +47,7 @@
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4629506F6600B3281A /* NotificationTab.swift */; }; 9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4629506F6600B3281A /* NotificationTab.swift */; };
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; }; 9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; };
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4B2952005C00B3281A /* MessagesTab.swift */; }; 9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4B2952005C00B3281A /* MessagesTab.swift */; };
9F398AA62935FE8A00A889F2 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AA52935FE8A00A889F2 /* AppRouter.swift */; }; 9F398AA62935FE8A00A889F2 /* AppRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AA52935FE8A00A889F2 /* AppRegistry.swift */; };
9F398AA92935FFDB00A889F2 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AA82935FFDB00A889F2 /* Account */; }; 9F398AA92935FFDB00A889F2 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AA82935FFDB00A889F2 /* Account */; };
9F398AAB2935FFDB00A889F2 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AAA2935FFDB00A889F2 /* Models */; }; 9F398AAB2935FFDB00A889F2 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AAA2935FFDB00A889F2 /* Models */; };
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AB229360A4C00A889F2 /* TimelineTab.swift */; }; 9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AB229360A4C00A889F2 /* TimelineTab.swift */; };
@ -207,7 +207,7 @@
9F35DB4B2952005C00B3281A /* MessagesTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesTab.swift; sourceTree = "<group>"; }; 9F35DB4B2952005C00B3281A /* MessagesTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesTab.swift; sourceTree = "<group>"; };
9F38C233297D03120018F11E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; }; 9F38C233297D03120018F11E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; }; 9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; };
9F398AA52935FE8A00A889F2 /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = "<group>"; }; 9F398AA52935FE8A00A889F2 /* AppRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRegistry.swift; sourceTree = "<group>"; };
9F398AAC2936005300A889F2 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Account; path = Packages/Account; sourceTree = "<group>"; }; 9F398AAC2936005300A889F2 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Account; path = Packages/Account; sourceTree = "<group>"; };
9F398AB229360A4C00A889F2 /* TimelineTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTab.swift; sourceTree = "<group>"; }; 9F398AB229360A4C00A889F2 /* TimelineTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTab.swift; sourceTree = "<group>"; };
9F4A48182976B21900A1A038 /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; }; 9F4A48182976B21900A1A038 /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; };
@ -377,7 +377,7 @@
9F654BF0299AC46200D27FA5 /* Report */, 9F654BF0299AC46200D27FA5 /* Report */,
9FAE4AC9293783A200772766 /* Tabs */, 9FAE4AC9293783A200772766 /* Tabs */,
9FBFE63C292A715500C250E9 /* IceCubesApp.swift */, 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */,
9F398AA52935FE8A00A889F2 /* AppRouter.swift */, 9F398AA52935FE8A00A889F2 /* AppRegistry.swift */,
639CDF9B296AC82F00C35E58 /* SafariRouter.swift */, 639CDF9B296AC82F00C35E58 /* SafariRouter.swift */,
9FAD85A7297582F100496AB1 /* QuickLookRepresentable.swift */, 9FAD85A7297582F100496AB1 /* QuickLookRepresentable.swift */,
9FAD85CE2975B68900496AB1 /* SideBarView.swift */, 9FAD85CE2975B68900496AB1 /* SideBarView.swift */,
@ -842,7 +842,7 @@
FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */, FA31A9AB2A66BF7C00D5F662 /* EditTagGroupView.swift in Sources */,
FAD203D02A66D8A80030A7FD /* Symbols.swift in Sources */, FAD203D02A66D8A80030A7FD /* Symbols.swift in Sources */,
9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */, 9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */,
9F398AA62935FE8A00A889F2 /* AppRouter.swift in Sources */, 9F398AA62935FE8A00A889F2 /* AppRegistry.swift in Sources */,
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */, 9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */,
9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */, 9F4A48192976B21900A1A038 /* ProfileTab.swift in Sources */,
9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */, 9F2B92FA295DA7D700DE16D0 /* AddAccountsView.swift in Sources */,

View file

@ -120,6 +120,12 @@ extension View {
.environment(PushNotificationsService.shared) .environment(PushNotificationsService.shared)
.environment(AppAccountsManager.shared.currentClient) .environment(AppAccountsManager.shared.currentClient)
} }
func withModelContainer() -> some View {
modelContainer(for: [
Draft.self,
])
}
} }
struct ActivityView: UIViewControllerRepresentable { struct ActivityView: UIViewControllerRepresentable {

View file

@ -8,6 +8,7 @@ import Network
import RevenueCat import RevenueCat
import SwiftUI import SwiftUI
import Timeline import Timeline
import Status
@main @main
struct IceCubesApp: App { struct IceCubesApp: App {
@ -75,6 +76,7 @@ struct IceCubesApp: App {
} }
} }
} }
.withModelContainer()
} }
.commands { .commands {
appMenu appMenu

View file

@ -10,7 +10,6 @@ import SwiftUI
@AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = [] @AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = []
@AppStorage("tag_groups") public var tagGroups: [TagGroup] = [] @AppStorage("tag_groups") public var tagGroups: [TagGroup] = []
@AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari @AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari
@AppStorage("draft_posts") public var draftsPosts: [String] = []
@AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true @AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true
@AppStorage("is_open_ai_enabled") public var isOpenAIEnabled: Bool = true @AppStorage("is_open_ai_enabled") public var isOpenAIEnabled: Bool = true
@ -79,11 +78,7 @@ import SwiftUI
storage.preferredBrowser = preferredBrowser storage.preferredBrowser = preferredBrowser
} }
} }
public var draftsPosts: [String] {
didSet {
storage.draftsPosts = draftsPosts
}
}
public var showTranslateButton: Bool { public var showTranslateButton: Bool {
didSet { didSet {
storage.showTranslateButton = showTranslateButton storage.showTranslateButton = showTranslateButton
@ -379,7 +374,6 @@ import SwiftUI
remoteLocalTimelines = storage.remoteLocalTimelines remoteLocalTimelines = storage.remoteLocalTimelines
tagGroups = storage.tagGroups tagGroups = storage.tagGroups
preferredBrowser = storage.preferredBrowser preferredBrowser = storage.preferredBrowser
draftsPosts = storage.draftsPosts
showTranslateButton = storage.showTranslateButton showTranslateButton = storage.showTranslateButton
isOpenAIEnabled = storage.isOpenAIEnabled isOpenAIEnabled = storage.isOpenAIEnabled
recentlyUsedLanguages = storage.recentlyUsedLanguages recentlyUsedLanguages = storage.recentlyUsedLanguages

View file

@ -111,9 +111,10 @@ struct StatusEditorAccessoryView: View {
.accessibilityLabel("accessibility.editor.button.drafts") .accessibilityLabel("accessibility.editor.button.drafts")
.popover(isPresented: $isDraftsSheetDisplayed) { .popover(isPresented: $isDraftsSheetDisplayed) {
if UIDevice.current.userInterfaceIdiom == .phone { if UIDevice.current.userInterfaceIdiom == .phone {
draftsSheetView draftsListView
.presentationDetents([.medium])
} else { } else {
draftsSheetView draftsListView
.frame(width: 400, height: 500) .frame(width: 400, height: 500)
} }
} }
@ -176,6 +177,16 @@ struct StatusEditorAccessoryView: View {
viewModel.setInitialLanguageSelection(preference: preferences.recentlyUsedLanguages.first ?? preferences.serverPreferences?.postLanguage) viewModel.setInitialLanguageSelection(preference: preferences.recentlyUsedLanguages.first ?? preferences.serverPreferences?.postLanguage)
} }
} }
private var draftsListView: some View {
DraftsListView(selectedDraft: .init(get: {
nil
}, set: { draft in
if let draft {
viewModel.insertStatusText(text: draft.content)
}
}))
}
@ViewBuilder @ViewBuilder
private func languageTextView(isoCode: String, nativeName: String?, name: String?) -> some View { private func languageTextView(isoCode: String, nativeName: String?, name: String?) -> some View {
@ -269,38 +280,6 @@ struct StatusEditorAccessoryView: View {
} }
} }
private var draftsSheetView: some View {
NavigationStack {
List {
ForEach(preferences.draftsPosts, id: \.self) { draft in
Button {
viewModel.insertStatusText(text: draft)
isDraftsSheetDisplayed = false
} label: {
Text(draft)
.lineLimit(3)
.foregroundStyle(theme.labelColor)
}.listRowBackground(theme.primaryBackgroundColor)
}
.onDelete { indexes in
if let index = indexes.first {
preferences.draftsPosts.remove(at: index)
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("action.cancel", action: { isDraftsSheetDisplayed = false })
}
}
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.navigationTitle("status.editor.drafts.navigation-title")
.navigationBarTitleDisplayMode(.inline)
}
.presentationDetents([.medium])
}
private var customEmojisSheet: some View { private var customEmojisSheet: some View {
NavigationStack { NavigationStack {
ScrollView { ScrollView {

View file

@ -0,0 +1,15 @@
import SwiftData
import SwiftUI
import Foundation
@Model public class Draft: Identifiable {
@Attribute(.unique) public var id: UUID
public var content: String
public var creationDate: Date
public init(content: String) {
self.id = UUID()
self.content = content
self.creationDate = Date()
}
}

View file

@ -0,0 +1,64 @@
import SwiftUI
import SwiftData
import DesignSystem
struct DraftsListView: View {
@AppStorage("draft_posts") public var legacyDraftPosts: [String] = []
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var context
@Environment(Theme.self) private var theme
@Query(sort: \Draft.creationDate, order: .reverse) var drafts: [Draft]
@Binding var selectedDraft: Draft?
var body: some View {
NavigationStack {
List {
ForEach(drafts) { draft in
Button {
selectedDraft = draft
dismiss()
} label: {
VStack(alignment: .leading, spacing: 8) {
Text(draft.content)
.font(.body)
.lineLimit(3)
.foregroundStyle(theme.labelColor)
Text(draft.creationDate, style: .relative)
.font(.footnote)
.foregroundStyle(.gray)
}
}.listRowBackground(theme.primaryBackgroundColor)
}
.onDelete { indexes in
if let index = indexes.first {
context.delete(drafts[index])
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("action.cancel", action: { dismiss() })
}
}
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.navigationTitle("status.editor.drafts.navigation-title")
.navigationBarTitleDisplayMode(.inline)
.onAppear {
migrateUserPreferencesDraft()
}
}
}
func migrateUserPreferencesDraft() {
for draft in legacyDraftPosts {
let newDraft = Draft(content: draft)
context.insert(newDraft)
}
legacyDraftPosts = []
}
}

View file

@ -19,6 +19,7 @@ public struct StatusEditorView: View {
@Environment(Client.self) private var client @Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var context
@State private var viewModel: StatusEditorViewModel @State private var viewModel: StatusEditorViewModel
@FocusState private var isSpoilerTextFocused: Bool @FocusState private var isSpoilerTextFocused: Bool
@ -144,22 +145,23 @@ public struct StatusEditorView: View {
Text("action.cancel") Text("action.cancel")
} }
.keyboardShortcut(.cancelAction) .keyboardShortcut(.cancelAction)
.confirmationDialog("", .confirmationDialog(
isPresented: $isDismissAlertPresented, "",
actions: { isPresented: $isDismissAlertPresented,
Button("status.draft.delete", role: .destructive) { actions: {
dismiss() Button("status.draft.delete", role: .destructive) {
NotificationCenter.default.post(name: NotificationsName.shareSheetClose, dismiss()
object: nil) NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
} object: nil)
Button("status.draft.save") { }
preferences.draftsPosts.insert(viewModel.statusText.string, at: 0) Button("status.draft.save") {
dismiss() context.insert(Draft(content: viewModel.statusText.string))
NotificationCenter.default.post(name: NotificationsName.shareSheetClose, dismiss()
object: nil) NotificationCenter.default.post(name: NotificationsName.shareSheetClose,
} object: nil)
Button("action.cancel", role: .cancel) {} }
}) Button("action.cancel", role: .cancel) {}
})
} }
} }
} }