Replace the abandoned RN iOS path with a SwiftUI app (iOS 17+) that reuses the shared TipTap editor bundle, GRDB local mirror/outbox, and Android-aligned shell chrome for list, create, detail, and Me. Package the editor HTML in the app resources so Markdown renders, present edit from the workspace root with a UIKit FAB, and keep caret stable while typing.
53 lines
1.8 KiB
Swift
53 lines
1.8 KiB
Swift
import SwiftUI
|
|
|
|
/// Minimal conflict UI: discard local queue item and rehydrate from server.
|
|
struct ConflictResolutionView: View {
|
|
@Environment(AppEnvironment.self) private var env
|
|
let item: OutboxItem
|
|
var onResolved: () -> Void
|
|
|
|
@State private var busy = false
|
|
@State private var error: String?
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("同步冲突").font(.headline)
|
|
Text("本地草稿与服务器版本不一致。你可以丢弃本地更改并采用云端内容,或稍后再处理。")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
if let err = item.lastError {
|
|
Text(err).font(.caption).foregroundStyle(.red)
|
|
}
|
|
if let error {
|
|
Text(error).font(.caption).foregroundStyle(.red)
|
|
}
|
|
HStack {
|
|
Button("丢弃本地") {
|
|
Task { await discardLocal() }
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.tint(.red)
|
|
.disabled(busy)
|
|
Button("稍后") { onResolved() }
|
|
.disabled(busy)
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
|
|
private func discardLocal() async {
|
|
guard let scope = env.session.dataScope else { return }
|
|
busy = true
|
|
defer { busy = false }
|
|
do {
|
|
let remote = try await env.session.client.getMemo(id: item.memoId, includeDeleted: true)
|
|
_ = try env.outbox.remove(scope: scope, id: item.id, expectedVersion: nil)
|
|
try env.mirror.upsertMemo(scope: scope, memo: remote)
|
|
try env.drafts.clear(scope: scope, key: DraftRepository.memoKey(item.memoId))
|
|
onResolved()
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
}
|