ReadKeep/readeck/UI/Bookmarks/BookmarkCardView.swift
Ilyas Hallak c8368f0a70 feat: Implement bookmark filtering, enhanced UI, and API integration
- 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
2025-06-11 22:02:44 +02:00

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())
}
}