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:
parent
dd1b2628b6
commit
176885442e
@ -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" : {
|
||||||
|
|
||||||
|
|||||||
@ -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)%)")
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user