initial commit: Proximity iOS app proposal, full Swift code scaffold, reference backend, testing docs, generated Xcode project
This commit is contained in:
126
Proximity/Views/ProfileImportSheet.swift
Normal file
126
Proximity/Views/ProfileImportSheet.swift
Normal file
@@ -0,0 +1,126 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user