feat: add delete confirmation for bookmarks and UI improvements

- Add confirmation alert before deleting a bookmark to prevent accidental deletions (BookmarksView)
- Add localized strings for delete confirmation dialog
- Improve layout and logic in BookmarkDetailView (alignment, locale, progress jump)
- Show read progress only for non-archived/non-marked bookmarks (BookmarkCardView)
- Refine WebView: remove debug code, improve scroll/height update logic, disable scroll
This commit is contained in:
Ilyas Hallak 2025-07-23 23:58:47 +02:00
parent dd1b2628b6
commit 176885442e
5 changed files with 45 additions and 26 deletions

View File

@ -70,6 +70,9 @@
}, },
"Archive bookmark" : { "Archive bookmark" : {
},
"Are you sure you want to delete this bookmark? This action cannot be undone." : {
}, },
"Are you sure you want to log out? This will delete all your login credentials and return you to setup." : { "Are you sure you want to log out? This will delete all your login credentials and return you to setup." : {
@ -100,6 +103,9 @@
}, },
"Delete" : { "Delete" : {
},
"Delete Bookmark" : {
}, },
"Developer: Ilyas Hallak" : { "Developer: Ilyas Hallak" : {

View File

@ -48,7 +48,7 @@ struct BookmarkDetailView: View {
.frame(height: 0) .frame(height: 0)
ZStack(alignment: .top) { ZStack(alignment: .top) {
headerView(geometry: outerGeo) headerView(geometry: outerGeo)
VStack(alignment: .center, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
Color.clear.frame(height: viewModel.bookmarkDetail.imageUrl.isEmpty ? 84 : headerHeight) Color.clear.frame(height: viewModel.bookmarkDetail.imageUrl.isEmpty ? 84 : headerHeight)
titleSection titleSection
Divider().padding(.horizontal) Divider().padding(.horizontal)
@ -57,7 +57,9 @@ struct BookmarkDetailView: View {
} }
if let settings = viewModel.settings, !viewModel.articleContent.isEmpty { if let settings = viewModel.settings, !viewModel.articleContent.isEmpty {
WebView(htmlContent: viewModel.articleContent, settings: settings, onHeightChange: { height in WebView(htmlContent: viewModel.articleContent, settings: settings, onHeightChange: { height in
webViewHeight = height if webViewHeight != height {
webViewHeight = height
}
}) })
.frame(height: webViewHeight) .frame(height: webViewHeight)
.cornerRadius(14) .cornerRadius(14)
@ -82,11 +84,14 @@ struct BookmarkDetailView: View {
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 0) .padding(.top, 0)
} }
Spacer(minLength: 40)
if viewModel.isLoadingArticle == false && viewModel.isLoading == false { if viewModel.isLoadingArticle == false && viewModel.isLoading == false {
archiveSection VStack(alignment: .center) {
.transition(.opacity.combined(with: .move(edge: .bottom))) archiveSection
.animation(.easeInOut, value: viewModel.articleContent) .transition(.opacity.combined(with: .move(edge: .bottom)))
.animation(.easeInOut, value: viewModel.articleContent)
}
.frame(maxWidth: .infinity)
} }
} }
} }
@ -358,7 +363,7 @@ struct BookmarkDetailView: View {
let displayFormatter = DateFormatter() let displayFormatter = DateFormatter()
displayFormatter.dateStyle = .medium displayFormatter.dateStyle = .medium
displayFormatter.timeStyle = .short displayFormatter.timeStyle = .short
displayFormatter.locale = Locale(identifier: "de_DE") displayFormatter.locale = .autoupdatingCurrent
return displayFormatter.string(from: date) return displayFormatter.string(from: date)
} }
return dateString return dateString
@ -417,13 +422,11 @@ struct BookmarkDetailView: View {
@ViewBuilder @ViewBuilder
func JumpButton() -> some View { func JumpButton() -> some View {
Button(action: { Button(action: {
if #available(iOS 17.0, *) { let maxOffset = webViewHeight - scrollViewHeight
let maxOffset = webViewHeight - scrollViewHeight let offset = maxOffset * (Double(viewModel.readProgress) / 100.0)
let offset = maxOffset * (Double(viewModel.readProgress) / 100.0) DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { scrollPosition = ScrollPosition(y: offset)
scrollPosition = ScrollPosition(y: offset) showJumpToProgressButton = false
showJumpToProgressButton = false
}
} }
}) { }) {
Text("Jump to last read position (\(viewModel.readProgress)%)") Text("Jump to last read position (\(viewModel.readProgress)%)")

View File

@ -28,7 +28,7 @@ struct BookmarkCardView: View {
} }
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
if bookmark.readProgress > 0 { if bookmark.readProgress > 0 && bookmark.isArchived == false && bookmark.isMarked == false {
ZStack { ZStack {
Circle() Circle()
.fill(Color(.systemBackground)) .fill(Color(.systemBackground))

View File

@ -12,6 +12,7 @@ struct BookmarksView: View {
@State private var showingAddBookmarkFromShare = false @State private var showingAddBookmarkFromShare = false
@State private var shareURL = "" @State private var shareURL = ""
@State private var shareTitle = "" @State private var shareTitle = ""
@State private var bookmarkToDelete: Bookmark? = nil
let state: BookmarkState let state: BookmarkState
let type: [BookmarkType] let type: [BookmarkType]
@ -19,7 +20,6 @@ struct BookmarksView: View {
@EnvironmentObject var playerUIState: PlayerUIState @EnvironmentObject var playerUIState: PlayerUIState
let tag: String? let tag: String?
// MARK: Environments // MARK: Environments
@Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.horizontalSizeClass) var horizontalSizeClass
@ -65,9 +65,7 @@ struct BookmarksView: View {
} }
}, },
onDelete: { bookmark in onDelete: { bookmark in
Task { bookmarkToDelete = bookmark
await viewModel.deleteBookmark(bookmark: bookmark)
}
}, },
onToggleFavorite: { bookmark in onToggleFavorite: { bookmark in
Task { Task {
@ -150,6 +148,18 @@ struct BookmarksView: View {
AddBookmarkView(prefilledURL: shareURL, prefilledTitle: shareTitle) AddBookmarkView(prefilledURL: shareURL, prefilledTitle: shareTitle)
} }
) )
.alert(item: $bookmarkToDelete) { bookmark in
Alert(
title: Text("Delete Bookmark"),
message: Text("Are you sure you want to delete this bookmark? This action cannot be undone."),
primaryButton: .destructive(Text("Delete")) {
Task {
await viewModel.deleteBookmark(bookmark: bookmark)
}
},
secondaryButton: .cancel()
)
}
.onAppear { .onAppear {
Task { Task {
await viewModel.loadBookmarks(state: state, type: type, tag: tag) await viewModel.loadBookmarks(state: state, type: type, tag: tag)

View File

@ -11,7 +11,7 @@ struct WebView: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView { func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView() let webView = WKWebView()
webView.navigationDelegate = context.coordinator webView.navigationDelegate = context.coordinator
webView.scrollView.isScrollEnabled = true webView.scrollView.isScrollEnabled = false
webView.isOpaque = false webView.isOpaque = false
webView.backgroundColor = UIColor.clear webView.backgroundColor = UIColor.clear
@ -214,17 +214,13 @@ struct WebView: UIViewRepresentable {
<body> <body>
\(htmlContent) \(htmlContent)
<script> <script>
console.log('Script loaded!');
alert('Script loaded!');
function updateHeight() { function updateHeight() {
const height = document.body.scrollHeight; const height = document.body.scrollHeight;
window.webkit.messageHandlers.heightUpdate.postMessage(height); window.webkit.messageHandlers.heightUpdate.postMessage(height);
} }
window.addEventListener('load', updateHeight); window.addEventListener('load', updateHeight);
setTimeout(updateHeight, 100);
setTimeout(updateHeight, 500); setTimeout(updateHeight, 500);
setTimeout(updateHeight, 1000);
// Höhe bei Bild-Ladevorgängen aktualisieren // Höhe bei Bild-Ladevorgängen aktualisieren
document.querySelectorAll('img').forEach(img => { document.querySelectorAll('img').forEach(img => {
@ -236,7 +232,6 @@ struct WebView: UIViewRepresentable {
var docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; var docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
var progress = docHeight > 0 ? scrollTop / docHeight : 0; var progress = docHeight > 0 ? scrollTop / docHeight : 0;
window.webkit.messageHandlers.scrollProgress.postMessage(progress); window.webkit.messageHandlers.scrollProgress.postMessage(progress);
console.log('Scroll event fired, progress:', progress);
}); });
</script> </script>
</body> </body>
@ -290,7 +285,10 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "heightUpdate", let height = message.body as? CGFloat { if message.name == "heightUpdate", let height = message.body as? CGFloat {
DispatchQueue.main.async { DispatchQueue.main.async {
self.onHeightChange?(height) if self.hasHeightUpdate == false {
self.onHeightChange?(height)
self.hasHeightUpdate = true
}
} }
} }
if message.name == "scrollProgress", let progress = message.body as? Double { if message.name == "scrollProgress", let progress = message.body as? Double {
@ -299,4 +297,6 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler
} }
} }
} }
var hasHeightUpdate: Bool = false
} }