finish SwiftData migration with @Query views
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

- Drive chat list, timeline and forward picker from SwiftData @Query
  instead of AppModel's in-memory arrays; inject the per-account
  ModelContext from RootView with an in-memory fallback.
- Physically delete locally deleted messages (context.delete) and
  purge rows marked deleted by older builds on store open, so @Query
  views never resurrect them.
- Delete the legacy JSON snapshot only after a successful import save
  and restore the completeUntilFirstUserAuthentication data protection
  attribute on the store file.
- Mirror the old ChatArchive tolerant decoding in LegacyArchiveImporter
  so snapshots from older schema versions (missing reactions,
  isGroupMessage, roster fields) still import.
- Cover the new behaviour in ArchiveStoreTests and guard it with new
  verify.sh invariants; update AGENTS.md, ARCHITECTURE.md and
  SECURITY.md.
This commit is contained in:
wt
2026-08-29 00:57:08 +07:00
parent 277d8654c3
commit 2884810ec4
16 changed files with 653 additions and 65 deletions
+19 -2
View File
@@ -1,3 +1,4 @@
import SwiftData
import SwiftUI
@MainActor
@@ -10,6 +11,9 @@ struct ForwardMessageView: View {
@State private var destinationJID = ""
@State private var isForwarding = false
/// SwiftData-backed destination list, ordered like the main chat list.
@Query private var conversations: [Conversation]
init(
model: AppModel,
messages: [ChatMessage],
@@ -18,6 +22,11 @@ struct ForwardMessageView: View {
_model = ObservedObject(wrappedValue: model)
self.messages = messages.filter(\.canBeForwarded)
self.onComplete = onComplete
_conversations = Query(
sort: [
SortDescriptor(\Conversation.lastActivity, order: .reverse),
]
)
}
var body: some View {
@@ -39,9 +48,9 @@ struct ForwardMessageView: View {
}
}
if !model.conversations.isEmpty {
if !orderedConversations.isEmpty {
Section("Чаты") {
ForEach(model.conversations) { conversation in
ForEach(orderedConversations) { conversation in
Button {
forward(to: conversation.jid)
} label: {
@@ -105,6 +114,14 @@ struct ForwardMessageView: View {
#endif
}
/// Pinned-first ordering, matching the main chat list.
private var orderedConversations: [Conversation] {
conversations.sorted { lhs, rhs in
if lhs.isPinned != rhs.isPinned { return lhs.isPinned }
return lhs.lastActivity > rhs.lastActivity
}
}
private func forward(to jid: String) {
guard !isForwarding, !messages.isEmpty else { return }
isForwarding = true