- Add BookmarkState enum with unread, favorite, and archived states - Extend API layer with query parameter filtering for bookmark states - Update Bookmark domain model to match complete API response schema - Implement BookmarkListView with card-based UI and preview images - Add BookmarkListViewModel with state management and error handling - Enhance BookmarkDetailView with meta information and WebView rendering - Create comprehensive DTO mapping for all bookmark fields - Add TabView with state-based bookmark filtering - Implement date formatting utilities for ISO8601 timestamps - Add progress indicators and pull-to-refresh functionality
113 lines
3.8 KiB
Swift
113 lines
3.8 KiB
Swift
import SwiftUI
|
|
|
|
struct BookmarkCardView: View {
|
|
let bookmark: Bookmark
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
// Vorschaubild - verwende image oder thumbnail
|
|
AsyncImage(url: imageURL) { image in
|
|
image
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
} placeholder: {
|
|
Rectangle()
|
|
.fill(Color.gray.opacity(0.2))
|
|
.overlay {
|
|
Image(systemName: "photo")
|
|
.foregroundColor(.gray)
|
|
}
|
|
}
|
|
.frame(height: 120)
|
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
// Status-Badges
|
|
HStack {
|
|
if bookmark.isMarked {
|
|
Badge(text: "Markiert", color: .blue)
|
|
}
|
|
if bookmark.isArchived {
|
|
Badge(text: "Archiviert", color: .gray)
|
|
}
|
|
if bookmark.hasArticle {
|
|
Badge(text: "Artikel", color: .green)
|
|
}
|
|
Spacer()
|
|
}
|
|
|
|
// Titel
|
|
Text(bookmark.title)
|
|
.font(.headline)
|
|
.fontWeight(.semibold)
|
|
.lineLimit(2)
|
|
.multilineTextAlignment(.leading)
|
|
|
|
// Beschreibung
|
|
if !bookmark.description.isEmpty {
|
|
Text(bookmark.description)
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(3)
|
|
.multilineTextAlignment(.leading)
|
|
}
|
|
|
|
// Meta-Info
|
|
HStack {
|
|
if !bookmark.siteName.isEmpty {
|
|
Label(bookmark.siteName, systemImage: "globe")
|
|
}
|
|
|
|
Spacer()
|
|
|
|
if let readingTime = bookmark.readingTime, readingTime > 0 {
|
|
Label("\(readingTime) min", systemImage: "clock")
|
|
}
|
|
}
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
|
|
// Progress Bar für Lesefortschritt
|
|
if bookmark.readProgress > 0 {
|
|
ProgressView(value: Double(bookmark.readProgress), total: 100)
|
|
.progressViewStyle(LinearProgressViewStyle())
|
|
.frame(height: 4)
|
|
}
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.bottom, 12)
|
|
}
|
|
.background(Color(.systemBackground))
|
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
|
.shadow(color: .black.opacity(0.1), radius: 2, x: 0, y: 1)
|
|
}
|
|
|
|
private var imageURL: URL? {
|
|
// Bevorzuge image, dann thumbnail, dann icon
|
|
if let imageUrl = bookmark.resources.image?.src {
|
|
return URL(string: imageUrl)
|
|
} else if let thumbnailUrl = bookmark.resources.thumbnail?.src {
|
|
return URL(string: thumbnailUrl)
|
|
} else if let iconUrl = bookmark.resources.icon?.src {
|
|
return URL(string: iconUrl)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
struct Badge: View {
|
|
let text: String
|
|
let color: Color
|
|
|
|
var body: some View {
|
|
Text(text)
|
|
.font(.caption2)
|
|
.padding(.horizontal, 6)
|
|
.padding(.vertical, 2)
|
|
.background(color.opacity(0.2))
|
|
.foregroundColor(color)
|
|
.clipShape(Capsule())
|
|
}
|
|
}
|
|
|