ReadKeep/readeck/Data/Repository/BookmarksRepository.swift
Ilyas Hallak 28cecf585c feat: Implement native List view with swipe actions and Safari integration
- Replace ScrollView + LazyVStack with native List for better performance
- Add swipe actions for bookmark management (archive left, favorite/delete right)
- Implement programmatic navigation with NavigationStack and navigationDestination
- Create reusable SafariUtil for opening URLs in SFSafariViewController
- Add clipboard URL detection and paste functionality in AddBookmarkView
- Improve BookmarkCardView layout with better image handling and meta info
- Add comprehensive date formatting with relative time display
- Implement proper progress bar visualization for reading progress

Navigation improvements:
- Use Button + navigationDestination instead of NavigationLink
- Add selectedBookmarkId state management for programmatic navigation
- Support for share extension URL handling with notification system

UI/UX enhancements:
- Native iOS swipe gestures with haptic feedback
- Consistent Safari integration across all views
- Better accessibility with proper button targets
- Improved visual hierarchy with refined spacing and typography
- Added image fallback chain (image → thumbnail → icon → placeholder)

Technical changes:
- Remove ScrollView scroll tracking complexity
- Simplify FAB button logic (always visible on unread tab)
- Add String+Identifiable extension for navigation
- Refactor duplicate Safari opening code into utility class
2025-06-14 22:25:19 +02:00

124 lines
4.2 KiB
Swift

import Foundation
protocol PBookmarksRepository {
func fetchBookmarks(state: BookmarkState?) async throws -> [Bookmark]
func fetchBookmark(id: String) async throws -> BookmarkDetail
func fetchBookmarkArticle(id: String) async throws -> String
func createBookmark(createRequest: CreateBookmarkRequest) async throws -> String
func updateBookmark(id: String, updateRequest: BookmarkUpdateRequest) async throws
func deleteBookmark(id: String) async throws
}
class BookmarksRepository: PBookmarksRepository {
private var api: PAPI
init(api: PAPI) {
self.api = api
}
func fetchBookmarks(state: BookmarkState? = nil) async throws -> [Bookmark] {
let bookmarkDtos = try await api.getBookmarks(state: state)
return bookmarkDtos.map { $0.toDomain() }
}
func fetchBookmark(id: String) async throws -> BookmarkDetail {
let bookmarkDetailDto = try await api.getBookmark(id: id)
return BookmarkDetail(
id: bookmarkDetailDto.id,
title: bookmarkDetailDto.title,
url: bookmarkDetailDto.url,
description: bookmarkDetailDto.description,
siteName: bookmarkDetailDto.siteName,
authors: bookmarkDetailDto.authors,
created: bookmarkDetailDto.created,
updated: bookmarkDetailDto.updated,
wordCount: bookmarkDetailDto.wordCount,
readingTime: bookmarkDetailDto.readingTime,
hasArticle: bookmarkDetailDto.hasArticle,
isMarked: bookmarkDetailDto.isMarked,
isArchived: bookmarkDetailDto.isArchived,
thumbnailUrl: bookmarkDetailDto.resources.thumbnail?.src ?? "",
imageUrl: bookmarkDetailDto.resources.image?.src ?? ""
)
}
func fetchBookmarkArticle(id: String) async throws -> String {
return try await api.getBookmarkArticle(id: id)
}
func createBookmark(createRequest: CreateBookmarkRequest) async throws -> String {
let dto = CreateBookmarkRequestDto(
url: createRequest.url,
title: createRequest.title,
labels: createRequest.labels
)
let response = try await api.createBookmark(createRequest: dto)
// Prüfe ob die Erstellung erfolgreich war
guard response.status == 0 || response.status == 202 else {
throw CreateBookmarkError.serverError(response.message)
}
return response.message
}
func deleteBookmark(id: String) async throws {
try await api.deleteBookmark(id: id)
}
func updateBookmark(id: String, updateRequest: BookmarkUpdateRequest) async throws {
let dto = UpdateBookmarkRequestDto(
addLabels: updateRequest.addLabels,
isArchived: updateRequest.isArchived,
isDeleted: updateRequest.isDeleted,
isMarked: updateRequest.isMarked,
labels: updateRequest.labels,
readAnchor: updateRequest.readAnchor,
readProgress: updateRequest.readProgress,
removeLabels: updateRequest.removeLabels,
title: updateRequest.title
)
try await api.updateBookmark(id: id, updateRequest: dto)
}
}
struct BookmarkDetail {
let id: String
let title: String
let url: String
let description: String
let siteName: String
let authors: [String]
let created: String
let updated: String
let wordCount: Int?
let readingTime: Int?
let hasArticle: Bool
let isMarked: Bool
let isArchived: Bool
let thumbnailUrl: String
let imageUrl: String
}
enum CreateBookmarkError: Error, LocalizedError {
case invalidURL
case duplicateBookmark
case networkError
case serverError(String)
var errorDescription: String? {
switch self {
case .invalidURL:
return "Die eingegebene URL ist ungültig"
case .duplicateBookmark:
return "Dieser Bookmark existiert bereits"
case .networkError:
return "Netzwerkfehler beim Erstellen des Bookmarks"
case .serverError(let message):
return message // Verwende die Server-Nachricht direkt
}
}
}