Files
Nearfield_Friends/Proximity/Views/ProfileImportSheet.swift

127 lines
4.4 KiB
Swift
Raw Normal View History

import SwiftUI
/// Shown at **reveal time** when a connection becomes mutual.
///
/// This is the only moment a social identity can be attached. The user can
/// choose to enrich their revealed profile with an Instagram or X handle
/// or decline and stay minimal. Importing is always optional and always
/// scoped to this specific connection.
struct ProfileImportSheet: View {
@EnvironmentObject private var profileService: ProfileImportService
@Environment(\.dismiss) private var dismiss
/// The connection that just became mutual.
let connection: Connection
@State private var imported: [ImportedProfile] = []
@State private var isImporting = false
@State private var errorMessage: String?
var body: some View {
NavigationStack {
VStack(spacing: 24) {
// Header
VStack(spacing: 8) {
Image(systemName: "person.crop.circle.badge.checkmark")
.font(.system(size: 48))
.foregroundColor(.green)
Text("You're connected with \(connection.displayName)")
.font(.headline)
.multilineTextAlignment(.center)
Text("Want to share a bit more about who you are?")
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.top, 24)
// Import buttons
VStack(spacing: 12) {
ForEach(ProfileProviderID.allCases) { provider in
importButton(for: provider)
}
}
.padding(.horizontal)
// Already imported
if !imported.isEmpty {
importedList
}
Spacer()
// Done
Button("Done") { dismiss() }
.buttonStyle(.borderedProminent)
.padding(.bottom, 24)
}
.navigationTitle("Share your profile")
.navigationBarTitleDisplayMode(.inline)
}
}
private func importButton(for provider: ProfileProviderID) -> some View {
Button {
Task { await importProfile(provider) }
} label: {
HStack {
Image(systemName: provider == .instagram ? "camera" : "xmark.square")
Text("Import from \(provider.displayName)")
Spacer()
if isImporting {
ProgressView()
} else {
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding()
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.buttonStyle(.plain)
.disabled(isImporting)
}
private var importedList: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Imported")
.font(.subheadline)
.foregroundColor(.secondary)
ForEach(imported) { profile in
HStack {
Text("@\(profile.handle)")
.font(.headline)
Spacer()
Text(profile.providerID.displayName)
.font(.caption)
.foregroundColor(.secondary)
Button {
profileService.remove(profile)
imported.removeAll { $0.id == profile.id }
} label: {
Image(systemName: "xmark.circle")
.foregroundColor(.red)
}
}
.padding()
.background(Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
.padding(.horizontal)
}
private func importProfile(_ provider: ProfileProviderID) async {
isImporting = true
defer { isImporting = false }
do {
let profile = try await profileService.importProfile(from: provider)
profileService.attach(profile, to: connection.id)
imported.append(profile)
} catch {
errorMessage = error.localizedDescription
}
}
}