update README with new iPhone and iPad screenshots
This commit is contained in:
parent
3abeb3f3e4
commit
c8c93b76da
23
README.md
23
README.md
@ -9,13 +9,24 @@ https://codeberg.org/readeck/readeck
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
### iPhone
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="screenshots/main.webp" height="400" alt="Main View">
|
<img src="screenshots/iphone_1.png" height="400" alt="iPhone Screenshot 1">
|
||||||
<img src="screenshots/detail.webp" height="400" alt="Detail View">
|
<img src="screenshots/iphone_2.png" height="400" alt="iPhone Screenshot 2">
|
||||||
<img src="screenshots/new.webp" height="400" alt="Add Bookmark">
|
<img src="screenshots/iphone_3.png" height="400" alt="iPhone Screenshot 3">
|
||||||
<img src="screenshots/more.webp" height="400" alt="More Options">
|
<img src="screenshots/iphone_4.png" height="400" alt="iPhone Screenshot 4">
|
||||||
<img src="screenshots/share.webp" height="400" alt="Share Extension">
|
<img src="screenshots/iphone_5.png" height="400" alt="iPhone Screenshot 5">
|
||||||
<img src="screenshots/ipad.webp" height="400" alt="iPad View">
|
</p>
|
||||||
|
|
||||||
|
### iPad
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="screenshots/ipad_1.jpg" height="400" alt="iPad Screenshot 1">
|
||||||
|
<img src="screenshots/ipad_2.jpg" height="400" alt="iPad Screenshot 2">
|
||||||
|
<img src="screenshots/ipad_3.jpg" height="400" alt="iPad Screenshot 3">
|
||||||
|
<img src="screenshots/ipad_4.jpg" height="400" alt="iPad Screenshot 4">
|
||||||
|
<img src="screenshots/ipad_5.jpg" height="400" alt="iPad Screenshot 5">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|||||||
@ -83,6 +83,7 @@
|
|||||||
Data/CoreData/CoreDataManager.swift,
|
Data/CoreData/CoreDataManager.swift,
|
||||||
"Data/Extensions/NSManagedObjectContext+SafeFetch.swift",
|
"Data/Extensions/NSManagedObjectContext+SafeFetch.swift",
|
||||||
Data/KeychainHelper.swift,
|
Data/KeychainHelper.swift,
|
||||||
|
Data/Utils/LabelUtils.swift,
|
||||||
Domain/Model/Bookmark.swift,
|
Domain/Model/Bookmark.swift,
|
||||||
Domain/Model/BookmarkLabel.swift,
|
Domain/Model/BookmarkLabel.swift,
|
||||||
Logger.swift,
|
Logger.swift,
|
||||||
|
|||||||
@ -65,7 +65,6 @@ struct BookmarkDetailView: View {
|
|||||||
.frame(height: webViewHeight)
|
.frame(height: webViewHeight)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
.animation(.easeInOut, value: webViewHeight)
|
|
||||||
} else if viewModel.isLoadingArticle {
|
} else if viewModel.isLoadingArticle {
|
||||||
ProgressView("Loading article...")
|
ProgressView("Loading article...")
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
|||||||
@ -228,9 +228,24 @@ struct WebView: UIViewRepresentable {
|
|||||||
<body>
|
<body>
|
||||||
\(htmlContent)
|
\(htmlContent)
|
||||||
<script>
|
<script>
|
||||||
|
let lastHeight = 0;
|
||||||
|
let heightUpdateTimeout = null;
|
||||||
|
let scrollTimeout = null;
|
||||||
|
let isScrolling = false;
|
||||||
|
|
||||||
function updateHeight() {
|
function updateHeight() {
|
||||||
const height = document.body.scrollHeight;
|
const height = document.body.scrollHeight;
|
||||||
window.webkit.messageHandlers.heightUpdate.postMessage(height);
|
|
||||||
|
// Only send height update if it changed significantly and we're not scrolling
|
||||||
|
if (Math.abs(height - lastHeight) > 5 && !isScrolling) {
|
||||||
|
lastHeight = height;
|
||||||
|
window.webkit.messageHandlers.heightUpdate.postMessage(height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function debouncedHeightUpdate() {
|
||||||
|
clearTimeout(heightUpdateTimeout);
|
||||||
|
heightUpdateTimeout = setTimeout(updateHeight, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', updateHeight);
|
window.addEventListener('load', updateHeight);
|
||||||
@ -238,14 +253,29 @@ struct WebView: UIViewRepresentable {
|
|||||||
|
|
||||||
// Höhe bei Bild-Ladevorgängen aktualisieren
|
// Höhe bei Bild-Ladevorgängen aktualisieren
|
||||||
document.querySelectorAll('img').forEach(img => {
|
document.querySelectorAll('img').forEach(img => {
|
||||||
img.addEventListener('load', updateHeight);
|
img.addEventListener('load', debouncedHeightUpdate);
|
||||||
});
|
});
|
||||||
// Scroll progress reporting
|
|
||||||
|
// Throttled scroll progress reporting
|
||||||
window.addEventListener('scroll', function() {
|
window.addEventListener('scroll', function() {
|
||||||
var scrollTop = window.scrollY || document.documentElement.scrollTop;
|
isScrolling = true;
|
||||||
var docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
|
||||||
var progress = docHeight > 0 ? scrollTop / docHeight : 0;
|
// Clear existing timeout
|
||||||
window.webkit.messageHandlers.scrollProgress.postMessage(progress);
|
clearTimeout(scrollTimeout);
|
||||||
|
|
||||||
|
// Throttle scroll events to reduce frequency
|
||||||
|
scrollTimeout = setTimeout(function() {
|
||||||
|
var scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||||||
|
var docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||||
|
var progress = docHeight > 0 ? scrollTop / docHeight : 0;
|
||||||
|
window.webkit.messageHandlers.scrollProgress.postMessage(progress);
|
||||||
|
|
||||||
|
// Reset scrolling state after a delay
|
||||||
|
setTimeout(function() {
|
||||||
|
isScrolling = false;
|
||||||
|
debouncedHeightUpdate(); // Check for height changes after scrolling stops
|
||||||
|
}, 200);
|
||||||
|
}, 16); // ~60fps throttling
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
@ -284,9 +314,13 @@ struct WebView: UIViewRepresentable {
|
|||||||
class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
|
class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
|
||||||
var onHeightChange: ((CGFloat) -> Void)?
|
var onHeightChange: ((CGFloat) -> Void)?
|
||||||
var onScroll: ((Double) -> Void)?
|
var onScroll: ((Double) -> Void)?
|
||||||
var hasHeightUpdate: Bool = false
|
|
||||||
var isScrolling: Bool = false
|
var isScrolling: Bool = false
|
||||||
var scrollEndTimer: Timer?
|
var scrollEndTimer: Timer?
|
||||||
|
var heightUpdateTimer: Timer?
|
||||||
|
var lastHeight: CGFloat = 0
|
||||||
|
var pendingHeight: CGFloat = 0
|
||||||
|
var scrollVelocity: Double = 0
|
||||||
|
var lastScrollTime: Date = Date()
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||||
if navigationAction.navigationType == .linkActivated {
|
if navigationAction.navigationType == .linkActivated {
|
||||||
@ -302,26 +336,75 @@ 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 {
|
||||||
// Block height updates during active scrolling to prevent flicker
|
self.handleHeightUpdate(height: height)
|
||||||
if !self.isScrolling && !self.hasHeightUpdate {
|
|
||||||
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 {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
// Track scrolling state
|
self.handleScrollProgress(progress: progress)
|
||||||
self.isScrolling = true
|
|
||||||
|
|
||||||
// Reset scrolling state after scroll ends
|
|
||||||
self.scrollEndTimer?.invalidate()
|
|
||||||
self.scrollEndTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in
|
|
||||||
self.isScrolling = false
|
|
||||||
}
|
|
||||||
|
|
||||||
self.onScroll?(progress)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleHeightUpdate(height: CGFloat) {
|
||||||
|
// Store the pending height
|
||||||
|
pendingHeight = height
|
||||||
|
|
||||||
|
// If we're actively scrolling, defer the height update
|
||||||
|
if isScrolling {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply height update immediately if not scrolling
|
||||||
|
applyHeightUpdate(height: height)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleScrollProgress(progress: Double) {
|
||||||
|
let now = Date()
|
||||||
|
let timeDelta = now.timeIntervalSince(lastScrollTime)
|
||||||
|
|
||||||
|
// Calculate scroll velocity to detect fast scrolling
|
||||||
|
if timeDelta > 0 {
|
||||||
|
scrollVelocity = abs(progress) / timeDelta
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScrollTime = now
|
||||||
|
isScrolling = true
|
||||||
|
|
||||||
|
// Longer delay for scroll end detection, especially during fast scrolling
|
||||||
|
let scrollEndDelay: TimeInterval = scrollVelocity > 2.0 ? 0.8 : 0.5
|
||||||
|
|
||||||
|
scrollEndTimer?.invalidate()
|
||||||
|
scrollEndTimer = Timer.scheduledTimer(withTimeInterval: scrollEndDelay, repeats: false) { [weak self] _ in
|
||||||
|
self?.handleScrollEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll?(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleScrollEnd() {
|
||||||
|
isScrolling = false
|
||||||
|
scrollVelocity = 0
|
||||||
|
|
||||||
|
// Apply any pending height update after scrolling ends
|
||||||
|
if pendingHeight != lastHeight && pendingHeight > 0 {
|
||||||
|
// Add small delay to ensure scroll has fully stopped
|
||||||
|
heightUpdateTimer?.invalidate()
|
||||||
|
heightUpdateTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.applyHeightUpdate(height: self.pendingHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applyHeightUpdate(height: CGFloat) {
|
||||||
|
// Only update if height actually changed significantly
|
||||||
|
let heightDifference = abs(height - lastHeight)
|
||||||
|
if heightDifference < 5 { // Ignore tiny height changes that cause flicker
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHeight = height
|
||||||
|
onHeightChange?(height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user