feat: Add label management to bookmarks and UI improvements
- BookmarkDetail: Add labels property, display and manage labels in detail view - Add AddLabelsToBookmarkUseCase and RemoveLabelsFromBookmarkUseCase - Update UpdateBookmarkUseCase and BookmarkUpdateRequest for label operations - UI: Show labels in BookmarkDetailView, add label management sheet - DefaultUseCaseFactory: Provide use cases for label management - Localizable: Add/adjust label-related strings, minor cleanup - SettingsServerView: Update debug endpoint - SidebarTab: Change 'Alle' to 'All' - Project: Remove unused region from Xcode project
This commit is contained in:
parent
2c5b51ca3a
commit
d2e8228903
@ -14,20 +14,27 @@
|
||||
|
||||
},
|
||||
"Abbrechen" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "needs_review",
|
||||
"value" : "Abbrechen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
"Abmelden" : {
|
||||
|
||||
},
|
||||
"Add Item" : {
|
||||
|
||||
},
|
||||
"Aktuelle Labels" : {
|
||||
|
||||
},
|
||||
"all" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ale"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Anmelden & speichern" : {
|
||||
|
||||
@ -61,17 +68,6 @@
|
||||
},
|
||||
"Debug-Anmeldung" : {
|
||||
|
||||
},
|
||||
"done" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fertig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Einfügen" : {
|
||||
|
||||
@ -117,17 +113,6 @@
|
||||
},
|
||||
"Fertig mit Lesen?" : {
|
||||
|
||||
},
|
||||
"font_settings_title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Schrift-Einstellungen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Füge einen neuen Link zu deiner Sammlung hinzu" : {
|
||||
|
||||
@ -161,9 +146,21 @@
|
||||
},
|
||||
"Keine Ergebnisse" : {
|
||||
|
||||
},
|
||||
"Keine Labels vorhanden" : {
|
||||
|
||||
},
|
||||
"Key" : {
|
||||
"extractionState" : "manual"
|
||||
},
|
||||
"Label eingeben..." : {
|
||||
|
||||
},
|
||||
"Labels" : {
|
||||
|
||||
},
|
||||
"Labels verwalten" : {
|
||||
|
||||
},
|
||||
"Lade %@..." : {
|
||||
|
||||
@ -185,6 +182,9 @@
|
||||
},
|
||||
"Neues Bookmark" : {
|
||||
|
||||
},
|
||||
"Neues Label hinzufügen" : {
|
||||
|
||||
},
|
||||
"OK" : {
|
||||
|
||||
|
||||
@ -319,7 +319,6 @@
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
"fr-CA",
|
||||
de,
|
||||
);
|
||||
mainGroup = 5D45F9BF2DF858680048D5B8;
|
||||
|
||||
@ -38,6 +38,7 @@ class BookmarksRepository: PBookmarksRepository {
|
||||
hasArticle: bookmarkDetailDto.hasArticle,
|
||||
isMarked: bookmarkDetailDto.isMarked,
|
||||
isArchived: bookmarkDetailDto.isArchived,
|
||||
labels: bookmarkDetailDto.labels,
|
||||
thumbnailUrl: bookmarkDetailDto.resources.thumbnail?.src ?? "",
|
||||
imageUrl: bookmarkDetailDto.resources.image?.src ?? ""
|
||||
)
|
||||
|
||||
@ -14,6 +14,7 @@ struct BookmarkDetail {
|
||||
let hasArticle: Bool
|
||||
let isMarked: Bool
|
||||
var isArchived: Bool
|
||||
let labels: [String]
|
||||
let thumbnailUrl: String
|
||||
let imageUrl: String
|
||||
}
|
||||
@ -33,6 +34,7 @@ extension BookmarkDetail {
|
||||
hasArticle: false,
|
||||
isMarked: false,
|
||||
isArchived: false,
|
||||
labels: [],
|
||||
thumbnailUrl: "",
|
||||
imageUrl: ""
|
||||
)
|
||||
|
||||
@ -59,4 +59,12 @@ extension BookmarkUpdateRequest {
|
||||
static func updateLabels(_ labels: [String]) -> BookmarkUpdateRequest {
|
||||
return BookmarkUpdateRequest(labels: labels)
|
||||
}
|
||||
|
||||
static func addLabels(_ labels: [String]) -> BookmarkUpdateRequest {
|
||||
return BookmarkUpdateRequest(addLabels: labels)
|
||||
}
|
||||
|
||||
static func removeLabels(_ labels: [String]) -> BookmarkUpdateRequest {
|
||||
return BookmarkUpdateRequest(removeLabels: labels)
|
||||
}
|
||||
}
|
||||
43
readeck/Domain/UseCase/AddLabelsToBookmarkUseCase.swift
Normal file
43
readeck/Domain/UseCase/AddLabelsToBookmarkUseCase.swift
Normal file
@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
|
||||
class AddLabelsToBookmarkUseCase {
|
||||
private let repository: PBookmarksRepository
|
||||
|
||||
init(repository: PBookmarksRepository) {
|
||||
self.repository = repository
|
||||
}
|
||||
|
||||
func execute(bookmarkId: String, labels: [String]) async throws {
|
||||
// Validierung der Labels
|
||||
guard !labels.isEmpty else {
|
||||
throw BookmarkUpdateError.emptyLabels
|
||||
}
|
||||
|
||||
// Entferne leere Labels und Duplikate
|
||||
let cleanLabels = Array(Set(labels.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }))
|
||||
|
||||
guard !cleanLabels.isEmpty else {
|
||||
throw BookmarkUpdateError.emptyLabels
|
||||
}
|
||||
|
||||
let request = BookmarkUpdateRequest.addLabels(cleanLabels)
|
||||
try await repository.updateBookmark(id: bookmarkId, updateRequest: request)
|
||||
}
|
||||
|
||||
// Convenience method für einzelne Labels
|
||||
func execute(bookmarkId: String, label: String) async throws {
|
||||
try await execute(bookmarkId: bookmarkId, labels: [label])
|
||||
}
|
||||
}
|
||||
|
||||
// Custom error für Label-Operationen
|
||||
enum BookmarkUpdateError: LocalizedError {
|
||||
case emptyLabels
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .emptyLabels:
|
||||
return "Labels können nicht leer sein"
|
||||
}
|
||||
}
|
||||
}
|
||||
31
readeck/Domain/UseCase/RemoveLabelsFromBookmarkUseCase.swift
Normal file
31
readeck/Domain/UseCase/RemoveLabelsFromBookmarkUseCase.swift
Normal file
@ -0,0 +1,31 @@
|
||||
import Foundation
|
||||
|
||||
class RemoveLabelsFromBookmarkUseCase {
|
||||
private let repository: PBookmarksRepository
|
||||
|
||||
init(repository: PBookmarksRepository) {
|
||||
self.repository = repository
|
||||
}
|
||||
|
||||
func execute(bookmarkId: String, labels: [String]) async throws {
|
||||
// Validierung der Labels
|
||||
guard !labels.isEmpty else {
|
||||
throw BookmarkUpdateError.emptyLabels
|
||||
}
|
||||
|
||||
// Entferne leere Labels und Duplikate
|
||||
let cleanLabels = Array(Set(labels.filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }))
|
||||
|
||||
guard !cleanLabels.isEmpty else {
|
||||
throw BookmarkUpdateError.emptyLabels
|
||||
}
|
||||
|
||||
let request = BookmarkUpdateRequest.removeLabels(cleanLabels)
|
||||
try await repository.updateBookmark(id: bookmarkId, updateRequest: request)
|
||||
}
|
||||
|
||||
// Convenience method für einzelne Labels
|
||||
func execute(bookmarkId: String, label: String) async throws {
|
||||
try await execute(bookmarkId: bookmarkId, labels: [label])
|
||||
}
|
||||
}
|
||||
@ -41,4 +41,14 @@ class UpdateBookmarkUseCase {
|
||||
let request = BookmarkUpdateRequest.updateLabels(labels)
|
||||
try await execute(bookmarkId: bookmarkId, updateRequest: request)
|
||||
}
|
||||
|
||||
func addLabels(bookmarkId: String, labels: [String]) async throws {
|
||||
let request = BookmarkUpdateRequest.addLabels(labels)
|
||||
try await execute(bookmarkId: bookmarkId, updateRequest: request)
|
||||
}
|
||||
|
||||
func removeLabels(bookmarkId: String, labels: [String]) async throws {
|
||||
let request = BookmarkUpdateRequest.removeLabels(labels)
|
||||
try await execute(bookmarkId: bookmarkId, updateRequest: request)
|
||||
}
|
||||
}
|
||||
@ -6,8 +6,9 @@ struct BookmarkDetailView: View {
|
||||
@State private var viewModel = BookmarkDetailViewModel()
|
||||
@State private var webViewHeight: CGFloat = 300
|
||||
@State private var showingFontSettings = false
|
||||
@State private var showingLabelsSheet = false
|
||||
|
||||
private let headerHeight: CGFloat = 260
|
||||
private let headerHeight: CGFloat = 320
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
@ -29,10 +30,18 @@ struct BookmarkDetailView: View {
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: {
|
||||
showingFontSettings = true
|
||||
}) {
|
||||
Image(systemName: "textformat")
|
||||
HStack(spacing: 12) {
|
||||
Button(action: {
|
||||
showingLabelsSheet = true
|
||||
}) {
|
||||
Image(systemName: "tag")
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
showingFontSettings = true
|
||||
}) {
|
||||
Image(systemName: "textformat")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,6 +66,9 @@ struct BookmarkDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingLabelsSheet) {
|
||||
BookmarkLabelsView(bookmarkId: bookmarkId, initialLabels: viewModel.bookmarkDetail.labels)
|
||||
}
|
||||
.onChange(of: showingFontSettings) { _, isShowing in
|
||||
if !isShowing {
|
||||
// Reload settings when sheet is dismissed
|
||||
@ -65,6 +77,14 @@ struct BookmarkDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: showingLabelsSheet) { _, isShowing in
|
||||
if !isShowing {
|
||||
// Reload bookmark detail when labels sheet is dismissed
|
||||
Task {
|
||||
await viewModel.refreshBookmarkDetail(id: bookmarkId)
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await viewModel.loadBookmarkDetail(id: bookmarkId)
|
||||
await viewModel.loadArticleContent(id: bookmarkId)
|
||||
@ -91,13 +111,22 @@ struct BookmarkDetailView: View {
|
||||
.fill(Color.gray.opacity(0.4))
|
||||
.frame(width: geometry.size.width, height: headerHeight)
|
||||
}
|
||||
// Gradient overlay für bessere Button-Sichtbarkeit
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [Color.black.opacity(0.6), Color.clear]),
|
||||
gradient: Gradient(colors: [
|
||||
Color.black.opacity(1.0),
|
||||
Color.black.opacity(0.9),
|
||||
Color.black.opacity(0.7),
|
||||
Color.black.opacity(0.4),
|
||||
Color.black.opacity(0.2),
|
||||
Color.clear
|
||||
]),
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
.frame(height: 120)
|
||||
.frame(height: 240)
|
||||
.frame(maxWidth: .infinity)
|
||||
.offset(y: (offset > 0 ? -offset : 0))
|
||||
}
|
||||
}
|
||||
.frame(height: headerHeight)
|
||||
@ -155,6 +184,38 @@ struct BookmarkDetailView: View {
|
||||
}
|
||||
metaRow(icon: "calendar", text: formatDate(viewModel.bookmarkDetail.created))
|
||||
metaRow(icon: "textformat", text: "\(viewModel.bookmarkDetail.wordCount ?? 0) Wörter • \(viewModel.bookmarkDetail.readingTime ?? 0) min Lesezeit")
|
||||
|
||||
// Labels section
|
||||
if !viewModel.bookmarkDetail.labels.isEmpty {
|
||||
HStack(alignment: .top, spacing: 8) {
|
||||
Image(systemName: "tag")
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top, 2)
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 6) {
|
||||
ForEach(viewModel.bookmarkDetail.labels, id: \.self) { label in
|
||||
Text(label)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color.accentColor.opacity(0.1))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(Color.accentColor.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metaRow(icon: "safari") {
|
||||
Button(action: {
|
||||
SafariUtil.openInSafari(url: viewModel.bookmarkDetail.url)
|
||||
|
||||
@ -73,4 +73,9 @@ class BookmarkDetailViewModel {
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func refreshBookmarkDetail(id: String) async {
|
||||
await loadBookmarkDetail(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
175
readeck/UI/BookmarkDetail/BookmarkLabelsView.swift
Normal file
175
readeck/UI/BookmarkDetail/BookmarkLabelsView.swift
Normal file
@ -0,0 +1,175 @@
|
||||
import SwiftUI
|
||||
|
||||
struct BookmarkLabelsView: View {
|
||||
let bookmarkId: String
|
||||
@State private var viewModel: BookmarkLabelsViewModel
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
init(bookmarkId: String, initialLabels: [String]) {
|
||||
self.bookmarkId = bookmarkId
|
||||
self._viewModel = State(initialValue: BookmarkLabelsViewModel(initialLabels: initialLabels))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 16) {
|
||||
// Add new label section
|
||||
addLabelSection
|
||||
|
||||
Divider()
|
||||
.padding(.horizontal, -16)
|
||||
|
||||
// Current labels section
|
||||
currentLabelsSection
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.navigationTitle("Labels verwalten")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Abbrechen") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Fertig") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert("Fehler", isPresented: $viewModel.showErrorAlert) {
|
||||
Button("OK") { }
|
||||
} message: {
|
||||
Text(viewModel.errorMessage ?? "Unbekannter Fehler")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var addLabelSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Neues Label hinzufügen")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
TextField("Label eingeben...", text: $viewModel.newLabelText)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.onSubmit {
|
||||
Task {
|
||||
await viewModel.addLabel(to: bookmarkId, label: viewModel.newLabelText)
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
Task {
|
||||
await viewModel.addLabel(to: bookmarkId, label: viewModel.newLabelText)
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 32, height: 32)
|
||||
.background(
|
||||
Circle()
|
||||
.fill(Color.accentColor)
|
||||
)
|
||||
}
|
||||
.disabled(viewModel.newLabelText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.isLoading)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(.secondarySystemGroupedBackground))
|
||||
)
|
||||
}
|
||||
|
||||
private var currentLabelsSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("Aktuelle Labels")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
.scaleEffect(0.8)
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.currentLabels.isEmpty {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "tag")
|
||||
.font(.title2)
|
||||
.foregroundColor(.secondary)
|
||||
Text("Keine Labels vorhanden")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 32)
|
||||
} else {
|
||||
LazyVGrid(columns: [
|
||||
GridItem(.adaptive(minimum: 80, maximum: 150))
|
||||
], spacing: 4) {
|
||||
ForEach(viewModel.currentLabels, id: \.self) { label in
|
||||
LabelChip(
|
||||
label: label,
|
||||
onRemove: {
|
||||
Task {
|
||||
await viewModel.removeLabel(from: bookmarkId, label: label)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(.secondarySystemGroupedBackground))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct LabelChip: View {
|
||||
let label: String
|
||||
let onRemove: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 6) {
|
||||
Text(label)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Button(action: onRemove) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color.accentColor.opacity(0.15))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.stroke(Color.accentColor.opacity(0.4), lineWidth: 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
BookmarkLabelsView(bookmarkId: "test-id", initialLabels: ["wichtig", "arbeit", "persönlich"])
|
||||
}
|
||||
86
readeck/UI/BookmarkDetail/BookmarkLabelsViewModel.swift
Normal file
86
readeck/UI/BookmarkDetail/BookmarkLabelsViewModel.swift
Normal file
@ -0,0 +1,86 @@
|
||||
import Foundation
|
||||
|
||||
@Observable
|
||||
class BookmarkLabelsViewModel {
|
||||
private let addLabelsUseCase = DefaultUseCaseFactory.shared.makeAddLabelsToBookmarkUseCase()
|
||||
private let removeLabelsUseCase = DefaultUseCaseFactory.shared.makeRemoveLabelsFromBookmarkUseCase()
|
||||
|
||||
var isLoading = false
|
||||
var errorMessage: String?
|
||||
var showErrorAlert = false
|
||||
var currentLabels: [String] = []
|
||||
var newLabelText = ""
|
||||
|
||||
init(initialLabels: [String] = []) {
|
||||
self.currentLabels = initialLabels
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func addLabels(to bookmarkId: String, labels: [String]) async {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
do {
|
||||
try await addLabelsUseCase.execute(bookmarkId: bookmarkId, labels: labels)
|
||||
// Update local labels
|
||||
currentLabels.append(contentsOf: labels)
|
||||
currentLabels = Array(Set(currentLabels)) // Remove duplicates
|
||||
} catch let error as BookmarkUpdateError {
|
||||
errorMessage = error.localizedDescription
|
||||
showErrorAlert = true
|
||||
} catch {
|
||||
errorMessage = "Fehler beim Hinzufügen der Labels"
|
||||
showErrorAlert = true
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func addLabel(to bookmarkId: String, label: String) async {
|
||||
let trimmedLabel = label.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmedLabel.isEmpty else { return }
|
||||
|
||||
await addLabels(to: bookmarkId, labels: [trimmedLabel])
|
||||
newLabelText = ""
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func removeLabels(from bookmarkId: String, labels: [String]) async {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
do {
|
||||
try await removeLabelsUseCase.execute(bookmarkId: bookmarkId, labels: labels)
|
||||
// Update local labels
|
||||
currentLabels.removeAll { labels.contains($0) }
|
||||
} catch let error as BookmarkUpdateError {
|
||||
errorMessage = error.localizedDescription
|
||||
showErrorAlert = true
|
||||
} catch {
|
||||
errorMessage = "Fehler beim Entfernen der Labels"
|
||||
showErrorAlert = true
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func removeLabel(from bookmarkId: String, label: String) async {
|
||||
await removeLabels(from: bookmarkId, labels: [label])
|
||||
}
|
||||
|
||||
// Convenience method für das Umschalten eines Labels (hinzufügen wenn nicht vorhanden, entfernen wenn vorhanden)
|
||||
@MainActor
|
||||
func toggleLabel(for bookmarkId: String, label: String) async {
|
||||
if currentLabels.contains(label) {
|
||||
await removeLabel(from: bookmarkId, label: label)
|
||||
} else {
|
||||
await addLabel(to: bookmarkId, label: label)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLabels(_ labels: [String]) {
|
||||
currentLabels = labels
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,8 @@ protocol UseCaseFactory {
|
||||
func makeLogoutUseCase() -> LogoutUseCase
|
||||
func makeSearchBookmarksUseCase() -> SearchBookmarksUseCase
|
||||
func makeSaveServerSettingsUseCase() -> SaveServerSettingsUseCase
|
||||
func makeAddLabelsToBookmarkUseCase() -> AddLabelsToBookmarkUseCase
|
||||
func makeRemoveLabelsFromBookmarkUseCase() -> RemoveLabelsFromBookmarkUseCase
|
||||
}
|
||||
|
||||
class DefaultUseCaseFactory: UseCaseFactory {
|
||||
@ -78,4 +80,12 @@ class DefaultUseCaseFactory: UseCaseFactory {
|
||||
func makeSaveServerSettingsUseCase() -> SaveServerSettingsUseCase {
|
||||
return SaveServerSettingsUseCase(repository: SettingsRepository())
|
||||
}
|
||||
|
||||
func makeAddLabelsToBookmarkUseCase() -> AddLabelsToBookmarkUseCase {
|
||||
return AddLabelsToBookmarkUseCase(repository: bookmarksRepository)
|
||||
}
|
||||
|
||||
func makeRemoveLabelsFromBookmarkUseCase() -> RemoveLabelsFromBookmarkUseCase {
|
||||
return RemoveLabelsFromBookmarkUseCase(repository: bookmarksRepository)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ enum SidebarTab: Hashable, CaseIterable, Identifiable {
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .all: return "Alle"
|
||||
case .all: return "All"
|
||||
case .unread: return "Ungelesen"
|
||||
case .favorite: return "Favoriten"
|
||||
case .archived: return "Archiv"
|
||||
|
||||
@ -126,7 +126,7 @@ struct SettingsServerView: View {
|
||||
Button("Debug-Anmeldung") {
|
||||
viewModel.username = "admin"
|
||||
viewModel.password = "Diggah123"
|
||||
viewModel.endpoint = "https://readeck.mnk.any64.de"
|
||||
viewModel.endpoint = "https://keep.mnk.any64.de"
|
||||
}
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user