mirror of
https://github.com/wallabag/ios-app.git
synced 2026-05-19 15:10:34 +00:00
Layout changes to support liquid glass and increase reading area (#465)
* UI improvements * Revert accidental change * Tidying up
This commit is contained in:
@@ -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 "<h1>\(escapedTitle)</h1>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user