ReadKeep/readeck/UI/AddBookmark/AddBookmarkView.swift
Ilyas Hallak 8882a402ef refactor: Simplify Share Extension to open main app directly
- Refactor ShareViewController to extract URL and open main app instead of direct API calls
- Add robust URL extraction from multiple content types (URL, text, property lists)
- Implement comprehensive debugging for Share Extension content processing
- Add URL scheme handling in main app (readeck://add-bookmark)
- Add notification-based communication between Share Extension and main app
- Extend BookmarksViewModel with share notification handling
- Support automatic AddBookmarkView opening with prefilled URL and title from shares

Technical changes:
- Remove Core Data dependency from Share Extension
- Add extensionContext.open() for launching main app with custom URL scheme
- Process all registered type identifiers for robust content extraction
- Add NSDataDetector for URL extraction from plain text
- Handle Safari property list sharing format
- Add share state management in BookmarksViewModel (@Observable pattern)
- Implement NotificationCenter publisher pattern with Combine

URL scheme format: readeck://add-bookmark?url=...&title=...
Notification: 'AddBookmarkFromShare' with url and title in userInfo
2025-06-13 15:02:46 +02:00

145 lines
5.5 KiB
Swift

import SwiftUI
struct AddBookmarkView: View {
@State private var viewModel = AddBookmarkViewModel()
@Environment(\.dismiss) private var dismiss
init(prefilledURL: String? = nil, prefilledTitle: String? = nil) {
viewModel.title = prefilledTitle ?? ""
viewModel.url = prefilledURL ?? ""
}
var body: some View {
NavigationView {
Form {
Section(header: Text("Bookmark Details")) {
VStack(alignment: .leading, spacing: 8) {
Text("URL *")
.font(.caption)
.foregroundColor(.secondary)
TextField("https://example.com", text: $viewModel.url)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.URL)
.autocapitalization(.none)
.autocorrectionDisabled()
}
VStack(alignment: .leading, spacing: 8) {
Text("Titel (optional)")
.font(.caption)
.foregroundColor(.secondary)
TextField("Bookmark Titel", text: $viewModel.title)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
Section(header: Text("Labels")) {
VStack(alignment: .leading, spacing: 8) {
Text("Labels (durch Komma getrennt)")
.font(.caption)
.foregroundColor(.secondary)
TextField("work, important, later", text: $viewModel.labelsText)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
if !viewModel.parsedLabels.isEmpty {
LazyVGrid(columns: [
GridItem(.adaptive(minimum: 80))
], spacing: 8) {
ForEach(viewModel.parsedLabels, id: \.self) { label in
Text(label)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.blue.opacity(0.1))
.foregroundColor(.blue)
.clipShape(Capsule())
}
}
}
}
Section {
Button("Aus Zwischenablage einfügen") {
viewModel.pasteFromClipboard()
}
.disabled(viewModel.clipboardURL == nil)
if let clipboardURL = viewModel.clipboardURL {
Text("Zwischenablage: \(clipboardURL)")
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
.truncationMode(.middle)
}
}
}
.navigationTitle("Bookmark hinzufügen")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Abbrechen") {
dismiss()
viewModel.clearForm()
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Speichern") {
Task {
await viewModel.createBookmark()
dismiss()
}
}
.disabled(!viewModel.isValid || viewModel.isLoading)
}
}
.overlay {
if viewModel.isLoading {
ZStack {
Color.black.opacity(0.3)
VStack(spacing: 16) {
ProgressView()
.scaleEffect(1.2)
Text("Bookmark wird erstellt...")
.font(.subheadline)
}
.padding(24)
.background(Color(.systemBackground))
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 10)
}
.ignoresSafeArea()
}
}
.alert("Erfolgreich", isPresented: $viewModel.showSuccessAlert) {
Button("OK") {
dismiss()
}
} message: {
Text("Bookmark wurde erfolgreich hinzugefügt!")
}
.alert("Fehler", isPresented: $viewModel.showErrorAlert) {
Button("OK", role: .cancel) { }
} message: {
Text(viewModel.errorMessage ?? "Unbekannter Fehler")
}
}
.onAppear {
viewModel.checkClipboard()
}
.onDisappear {
viewModel.clearForm()
}
}
}
#Preview {
AddBookmarkView()
}