import SwiftUI import OrgSocialKit /// Posts a `:MIGRATION:` entry to the user's feed, telling followers the feed has moved. /// Per the Org Social spec, a migration post has body `" "` and is /// indistinguishable from a regular post except that clients highlight it and should /// prefer following the new URL going forward. struct MigrationSheet: View { @Environment(\.dismiss) private var dismiss @AppStorage("publicFeedURL") private var publicFeedURL = "" @State private var newURL = "" @State private var isPosting = false @State private var errorMessage: String? @State private var finished = false var body: some View { NavigationStack { Form { Section { HStack { Text("Current URL").foregroundStyle(.secondary) Spacer() Text(publicFeedURL) .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.middle) } } Section("New feed URL") { TextField("https://new.example.com/social.org", text: $newURL) .autocorrectionDisabled() .textInputAutocapitalization(.never) .keyboardType(.URL) } if let errorMessage { Section { Label(errorMessage, systemImage: "exclamationmark.triangle") .font(.footnote) .foregroundStyle(.red) } } if finished { Section { Label("Migration announced", systemImage: "checkmark.circle.fill") .foregroundStyle(.green) } } } .navigationTitle("Migrate account") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { if isPosting { ProgressView() } else { Button("Post") { Task { await post() } } .disabled(!canPost) } } } } } private var canPost: Bool { !publicFeedURL.isEmpty && URL(string: publicFeedURL) != nil && URL(string: newURL) != nil && !isPosting } private func post() async { isPosting = true errorMessage = nil defer { isPosting = false } guard let feedURL = URL(string: publicFeedURL), let uploader = UploaderFactory.makeUploader() else { errorMessage = "Configure your vfile (or other upload method) first." return } do { let content = try await FeedFetcher().fetch(from: feedURL, bypassCache: true) let options = NewPostOptions.migration(from: publicFeedURL, to: newURL) let (updated, _) = try PostWriter().appendPost(to: content, feedURL: feedURL, options: options) try await uploader.upload(content: updated) FollowCoordinator.shared.updateCachedContent(updated) finished = true try? await Task.sleep(for: .seconds(1)) dismiss() } catch { errorMessage = error.localizedDescription } } }