diff --git a/App/Entity/Entry.swift b/App/Entity/Entry.swift index ff7756e..116b09a 100644 --- a/App/Entity/Entry.swift +++ b/App/Entity/Entry.swift @@ -1,6 +1,7 @@ import CoreData import CoreSpotlight import Foundation +import HTMLEntities import SharedLib // import MobileCoreServices @@ -80,4 +81,9 @@ extension Entry { } return Double(screenPosition) } + + var titleHtml: String { + let escapedTitle = (title ?? "").htmlEscape() + return "

\(escapedTitle)

" + } } diff --git a/App/Features/Entry/EntriesView.swift b/App/Features/Entry/EntriesView.swift index 4893d56..25794fd 100644 --- a/App/Features/Entry/EntriesView.swift +++ b/App/Features/Entry/EntriesView.swift @@ -14,17 +14,23 @@ struct EntriesView: View { @State private var showPaywallWallabagPlus = false var body: some View { - VStack { - #if os(iOS) - PasteBoardView() - #endif - SearchView(searchViewModel: searchViewModel) - EntriesListView( - predicate: searchViewModel.predicate, - entriesSortedById: entriesSortedById, - entriesSortedByReadingTime: entriesSortedByReadingTime, - entriesSortedByAscending: entriesSortedByAscending - ) + EntriesListView( + predicate: searchViewModel.predicate, + entriesSortedById: entriesSortedById, + entriesSortedByReadingTime: entriesSortedByReadingTime, + entriesSortedByAscending: entriesSortedByAscending + ) + .scrollContentBackground(.hidden) + .safeAreaInset(edge: .top) { + VStack(spacing: 0) { + #if os(iOS) + PasteBoardView() + #endif + SearchView(searchViewModel: searchViewModel) + .padding(.bottom, 8) + Divider() + } + .background(.ultraThinMaterial) } .onChange(of: entriesSortedById) { _, newValue in if newValue { @@ -94,6 +100,8 @@ struct EntriesView: View { PaywallView(displayCloseButton: true) } .navigationTitle("Entries") + .navigationBarTitleDisplayMode(.inline) + .ignoresSafeArea(.all, edges: .bottom) } } diff --git a/App/Features/Entry/EntryView.swift b/App/Features/Entry/EntryView.swift index b6eeb41..9562223 100644 --- a/App/Features/Entry/EntryView.swift +++ b/App/Features/Entry/EntryView.swift @@ -23,18 +23,14 @@ struct EntryView: View { #endif var body: some View { - VStack(alignment: .leading) { - Text(entry.title?.htmlUnescape() ?? "Entry") - .font(.title) - .fontWeight(.black) - .lineLimit(2) - .padding(.horizontal) - ProgressView(value: min(progress, 1), total: 1) - WebView(entry: entry, progress: $progress) - } - .addSwipeToBack { - dismiss() - } + WebView(entry: entry, progress: $progress) + .ignoresSafeArea() + .safeAreaInset(edge: .top) { + ProgressView(value: max(0, min(progress, 1)), total: 1) + } + .addSwipeToBack { + dismiss() + } .toolbar { ToolbarItem(placement: toolbarPlacement) { Menu(content: { @@ -79,6 +75,9 @@ struct EntryView: View { TagListFor(entry: entry) .presentationDetents([.medium, .large]) } + .toolbarBackground(.ultraThinMaterial, for: .bottomBar) + .toolbarBackground(.visible, for: .bottomBar) + .ignoresSafeArea(.all, edges: .bottom) #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif diff --git a/App/Features/Entry/WebView.swift b/App/Features/Entry/WebView.swift index 72ac739..6c776d3 100644 --- a/App/Features/Entry/WebView.swift +++ b/App/Features/Entry/WebView.swift @@ -6,7 +6,6 @@ import WebKit #if os(iOS) struct WebView: UIViewRepresentable { var entry: Entry - private(set) var wkWebView = WKWebView(frame: .zero) @EnvironmentObject var appSetting: AppSetting @Binding var progress: Double @@ -26,19 +25,27 @@ import WebKit super.init() } - func webViewToLastPosition() { - webView.wkWebView.scrollView.setContentOffset( - CGPoint( - x: 0.0, - y: webView.entry.screenPositionForWebView - ), - animated: true - ) + func webViewToLastPosition(in webView: WKWebView) { + let position = self.webView.entry.screenPositionForWebView + if position > 0 { + // Check if content is actually loaded by verifying contentSize + if webView.scrollView.contentSize.height > webView.bounds.height { + webView.scrollView.setContentOffset( + CGPoint(x: 0.0, y: position), + animated: true + ) + } else { + // Content not ready yet, try again after a minimal delay + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + self.webViewToLastPosition(in: webView) + } + } + } } func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { - webViewToLastPosition() webView.fontSizePercent(appSetting.webFontSizePercent) + self.webViewToLastPosition(in: webView) } func webView(_: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { @@ -67,7 +74,12 @@ import WebKit } func scrollViewDidScroll(_ scrollView: UIScrollView) { - webView.progress = scrollView.contentOffset.y / (scrollView.contentSize.height - scrollView.bounds.height) + let scrollableHeight = scrollView.contentSize.height - scrollView.bounds.height + if scrollableHeight > 0 { + webView.progress = scrollView.contentOffset.y / scrollableHeight + } else { + webView.progress = 0 + } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { @@ -79,11 +91,17 @@ import WebKit } func makeUIView(context: Context) -> WKWebView { - wkWebView.navigationDelegate = context.coordinator - wkWebView.scrollView.delegate = context.coordinator - wkWebView.load(content: entry.content, justify: UserDefaults.standard.bool(forKey: "justifyArticle")) + let webView = WKWebView(frame: .zero) + webView.navigationDelegate = context.coordinator + webView.scrollView.delegate = context.coordinator + webView.isOpaque = false + webView.backgroundColor = .clear + webView.scrollView.backgroundColor = .clear + webView.scrollView.contentInsetAdjustmentBehavior = .always + + webView.load(content: entry.titleHtml + (entry.content ?? ""), justify: UserDefaults.standard.bool(forKey: "justifyArticle")) - return wkWebView + return webView } func updateUIView(_ webView: WKWebView, context _: Context) { @@ -95,17 +113,18 @@ import WebKit #if os(macOS) struct WebView: NSViewRepresentable { var entry: Entry - private(set) var wkWebView = WKWebView(frame: .zero) @EnvironmentObject var appSetting: AppSetting + @Binding var progress: Double - func makeNSView(context _: Context) -> WKWebView { - wkWebView.load(content: entry.content, justify: false) + func makeNSView(context: Context) -> WKWebView { + let webView = WKWebView(frame: .zero) + webView.navigationDelegate = context.coordinator + webView.load(content: entry.titleHtml + (entry.content ?? ""), justify: false) - return wkWebView + return webView } func updateNSView(_ nsView: WKWebView, context _: Context) { - nsView.load(content: entry.content, justify: false) nsView.fontSizePercent(appSetting.webFontSizePercent) } @@ -186,10 +205,12 @@ struct WebView_Previews: PreviewProvider { Group { WebView( entry: entry, progress: .constant(0.5) - ).colorScheme(.light) + ).environmentObject(AppSetting()) + .colorScheme(.light) WebView( entry: entry, progress: .constant(0.5) - ).colorScheme(.dark) + ).environmentObject(AppSetting()) + .colorScheme(.dark) } } } diff --git a/App/Features/Sync/RefreshButton.swift b/App/Features/Sync/RefreshButton.swift index 036bc2c..0a34284 100644 --- a/App/Features/Sync/RefreshButton.swift +++ b/App/Features/Sync/RefreshButton.swift @@ -4,20 +4,15 @@ struct RefreshButton: View { @Environment(AppSync.self) var appSync: AppSync var body: some View { - HStack { + ZStack { if appSync.inProgress { - ProgressView(value: appSync.progress, total: 100) - #if os(iOS) - .progressViewStyle(.linear) - #else + ProgressView() .progressViewStyle(.circular) - #endif } else { Button( action: appSync.requestSync, label: { Image(systemName: "arrow.counterclockwise") - .frame(width: 34, height: 34, alignment: .center) } ) .buttonStyle(.plain) @@ -26,6 +21,7 @@ struct RefreshButton: View { .keyboardShortcut("r", modifiers: .command) } } + .frame(width: 34, height: 34, alignment: .center) } } diff --git a/App/Resources/html-ressources/main.css b/App/Resources/html-ressources/main.css index 1b2ad1a..1d825e2 100755 --- a/App/Resources/html-ressources/main.css +++ b/App/Resources/html-ressources/main.css @@ -14,7 +14,8 @@ html { min-height: 100%; } -html, body { +html, +body { overscroll-behavior: none; } @@ -31,16 +32,20 @@ a { font-weight: bold; } -h2, h3, h4 { +h2, +h3, +h4 { font-family: 'PT Sans', sans-serif; text-transform: uppercase; } -p, li { +p, +li { color: #666; } -a:hover, a:focus { +a:hover, +a:focus { text-decoration: none; } @@ -58,11 +63,11 @@ h2:after { ========================================================================== */ #main { - margin-left: 13em; - position: relative; - z-index: 10; - padding-right: 5%; - padding-bottom: 1em; + margin-left: 13em; + position: relative; + z-index: 10; + padding-right: 5%; + padding-bottom: 1em; } #content { @@ -86,7 +91,9 @@ blockquote { margin: 0; } -#article h2, #article h3, #article h4 { +#article h2, +#article h3, +#article h4 { text-transform: none; } @@ -97,7 +104,7 @@ blockquote { @media screen { - body > header { + body>header { background: #333; position: fixed; top: 0; @@ -117,35 +124,45 @@ blockquote { } #article h1 { - font-size: 1.2em; + font-size: 2.2em; + font-weight: 900; + line-height: 1.1; + margin-top: 1em; + margin-bottom: 0.8em; + display: -webkit-box; + -webkit-line-clamp: 3; + line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; } video { - width: 100% + width: 100% } } @media (prefers-color-scheme: dark) { body { - color: #FFF; - background: #000; + color: #FFF; + background: #000; } ::selection { - color: #777; - background: #000; + color: #777; + background: #000; } - p, li { - color: #FFF; + p, + li { + color: #FFF; } a { - color: #6CA0DC; + color: #6CA0DC; } blockquote { - background: #111; + background: #111; } } @@ -157,4 +174,4 @@ blockquote { a { color: #000; } -} +} \ No newline at end of file