ReadKeep/readeck/UI/Bookmarks/BookmarksView.swift
Ilyas Hallak 82f9d8a5a9 feat: Add URL Share Extension with API integration
- Add ShareViewController with complete URL extraction logic
- Support URL sharing from Safari, Chrome and other apps
- Extract URLs from different content types (URL, plain text, property list)
- Implement direct API call to create bookmarks from share extension
- Add Core Data integration to fetch authentication token
- Include proper error handling and user feedback with alerts
- Support title extraction and user text input for bookmark creation

Technical implementation:
- Handle UTType.url, UTType.plainText, and UTType.propertyList
- Async/await pattern for API requests
- NSDataDetector for URL extraction from text
- Property list parsing for Safari-style sharing
- Loading and success/error alerts for better UX
2025-06-12 23:21:55 +02:00

119 lines
4.8 KiB
Swift

import SwiftUI
struct BookmarksView: View {
@State private var viewModel = BookmarksViewModel()
@State private var showingAddBookmark = false
@State private var scrollOffset: CGFloat = 0
@State private var isScrolling = false
let state: BookmarkState
var body: some View {
NavigationView {
ZStack {
if viewModel.isLoading && viewModel.bookmarks.isEmpty {
ProgressView("Lade \(state.displayName)...")
} else {
ScrollView {
LazyVStack(spacing: 12) {
ForEach(viewModel.bookmarks, id: \.id) { bookmark in
NavigationLink(destination: BookmarkDetailView(bookmarkId: bookmark.id)) {
BookmarkCardView(
bookmark: bookmark,
currentState: state,
onArchive: { bookmark in
Task {
await viewModel.toggleArchive(bookmark: bookmark)
}
},
onDelete: { bookmark in
Task {
await viewModel.deleteBookmark(bookmark: bookmark)
}
},
onToggleFavorite: { bookmark in
Task {
await viewModel.toggleFavorite(bookmark: bookmark)
}
}
)
.padding(.bottom, 20)
}
.buttonStyle(PlainButtonStyle())
}
}
.padding()
}
.refreshable {
await viewModel.refreshBookmarks()
}
.overlay {
if viewModel.bookmarks.isEmpty && !viewModel.isLoading {
ContentUnavailableView(
"Keine Bookmarks",
systemImage: "bookmark",
description: Text("Es wurden noch keine Bookmarks in \(state.displayName.lowercased()) gefunden.")
)
}
}
}
}
.navigationTitle(state.displayName)
.sheet(isPresented: $showingAddBookmark) {
AddBookmarkView()
}
.alert("Fehler", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("OK", role: .cancel) {
viewModel.errorMessage = nil
}
} message: {
Text(viewModel.errorMessage ?? "")
}
.task {
await viewModel.loadBookmarks(state: state)
}
.onChange(of: showingAddBookmark) { oldValue, newValue in
// Refresh bookmarks when sheet is dismissed
if oldValue && !newValue {
Task {
await viewModel.refreshBookmarks()
}
}
}
}
.overlay {
// Animated FAB Button
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
showingAddBookmark = true
}) {
Image(systemName: "plus")
.font(.title2)
.fontWeight(.semibold)
.foregroundColor(.white)
.frame(width: 56, height: 56)
.background(Color.accentColor)
.clipShape(Circle())
.shadow(color: .black.opacity(0.25), radius: 6, x: 0, y: 3)
.scaleEffect(isScrolling ? 0.8 : 1.0)
.opacity(isScrolling ? 0.7 : 1.0)
}
.padding(.trailing, 20)
.padding(.bottom, 20)
}
}
}
}
}
// Helper für Scroll-Tracking
struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}