longform: add focus mode with auto-hide chrome
When reading longform articles, scrolling down hides the navigation bar and tab bar for a distraction-free reading experience. Tap anywhere to restore the chrome, or it automatically restores when leaving the view. Closes: https://github.com/damus-io/damus/issues/3493 Changelog-Added: Added focus mode with auto-hide navigation for longform reading Signed-off-by: alltheseas
This commit is contained in:
committed by
Daniel D’Aquino
parent
ef262b3c22
commit
527b53a7c8
@@ -30,6 +30,12 @@ struct ChatroomThreadView: View {
|
|||||||
@State private var contentBottomY: CGFloat = 0
|
@State private var contentBottomY: CGFloat = 0
|
||||||
@State private var initialTopY: CGFloat? = nil
|
@State private var initialTopY: CGFloat? = nil
|
||||||
|
|
||||||
|
// Focus mode: auto-hide chrome (nav bar + tab bar) during longform reading
|
||||||
|
@State private var chromeHidden: Bool = false
|
||||||
|
@State private var lastScrollY: CGFloat = 0
|
||||||
|
/// Minimum scroll distance before triggering chrome hide/show
|
||||||
|
private let scrollThreshold: CGFloat = 15
|
||||||
|
|
||||||
private static let untrusted_network_section_id = "untrusted-network-section"
|
private static let untrusted_network_section_id = "untrusted-network-section"
|
||||||
private static let sticky_header_adjusted_anchor = UnitPoint(x: UnitPoint.top.x, y: 0.2)
|
private static let sticky_header_adjusted_anchor = UnitPoint(x: UnitPoint.top.x, y: 0.2)
|
||||||
|
|
||||||
@@ -62,6 +68,38 @@ struct ChatroomThreadView: View {
|
|||||||
readingProgress = min(max(progress, 0), 1)
|
readingProgress = min(max(progress, 0), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates chrome visibility based on scroll direction (longform only).
|
||||||
|
/// Scrolling down hides chrome, scrolling up shows it.
|
||||||
|
private func updateChromeVisibility(newY: CGFloat) {
|
||||||
|
guard isLongformEvent else { return }
|
||||||
|
|
||||||
|
let delta = newY - lastScrollY
|
||||||
|
|
||||||
|
// Only toggle chrome state if scroll exceeds threshold
|
||||||
|
if abs(delta) > scrollThreshold {
|
||||||
|
let shouldHide = delta < 0 // Scrolling down (content moving up)
|
||||||
|
|
||||||
|
if shouldHide != chromeHidden {
|
||||||
|
withAnimation(.easeInOut(duration: 0.25)) {
|
||||||
|
chromeHidden = shouldHide
|
||||||
|
}
|
||||||
|
notify(.display_tabbar(!shouldHide))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always update lastScrollY to prevent stale delta accumulation
|
||||||
|
lastScrollY = newY
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows chrome (nav bar + tab bar) - called on tap or when leaving view.
|
||||||
|
private func showChrome() {
|
||||||
|
guard chromeHidden else { return }
|
||||||
|
withAnimation(.easeInOut(duration: 0.25)) {
|
||||||
|
chromeHidden = false
|
||||||
|
}
|
||||||
|
notify(.display_tabbar(true))
|
||||||
|
}
|
||||||
|
|
||||||
func go_to_event(scroller: ScrollViewProxy, note_id: NoteId) {
|
func go_to_event(scroller: ScrollViewProxy, note_id: NoteId) {
|
||||||
let adjustedAnchor: UnitPoint = showStickyHeader ? ChatroomThreadView.sticky_header_adjusted_anchor : .top
|
let adjustedAnchor: UnitPoint = showStickyHeader ? ChatroomThreadView.sticky_header_adjusted_anchor : .top
|
||||||
|
|
||||||
@@ -158,9 +196,11 @@ struct ChatroomThreadView: View {
|
|||||||
.onChange(of: geo.frame(in: .global).minY) { newY in
|
.onChange(of: geo.frame(in: .global).minY) { newY in
|
||||||
contentTopY = newY
|
contentTopY = newY
|
||||||
updateReadingProgress()
|
updateReadingProgress()
|
||||||
|
updateChromeVisibility(newY: newY)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
contentTopY = geo.frame(in: .global).minY
|
contentTopY = geo.frame(in: .global).minY
|
||||||
|
lastScrollY = geo.frame(in: .global).minY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
@@ -348,6 +388,11 @@ struct ChatroomThreadView: View {
|
|||||||
.onAppear() {
|
.onAppear() {
|
||||||
thread.subscribe()
|
thread.subscribe()
|
||||||
scroll_to_event(scroller: scroller, id: thread.selected_event.id, delay: 0.1, animate: false)
|
scroll_to_event(scroller: scroller, id: thread.selected_event.id, delay: 0.1, animate: false)
|
||||||
|
// Ensure chrome is visible when view appears (handles interrupted transitions)
|
||||||
|
if isLongformEvent {
|
||||||
|
chromeHidden = false
|
||||||
|
notify(.display_tabbar(true))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: thread.selected_event.id) { _ in
|
.onChange(of: thread.selected_event.id) { _ in
|
||||||
// Reset reading progress when switching to a different event
|
// Reset reading progress when switching to a different event
|
||||||
@@ -356,7 +401,18 @@ struct ChatroomThreadView: View {
|
|||||||
}
|
}
|
||||||
.onDisappear() {
|
.onDisappear() {
|
||||||
thread.unsubscribe()
|
thread.unsubscribe()
|
||||||
|
showChrome() // Restore chrome when leaving view
|
||||||
}
|
}
|
||||||
|
.navigationBarHidden(chromeHidden && isLongformEvent)
|
||||||
|
// Tap anywhere to show chrome when hidden (doesn't block other gestures)
|
||||||
|
.simultaneousGesture(
|
||||||
|
TapGesture()
|
||||||
|
.onEnded { _ in
|
||||||
|
if isLongformEvent && chromeHidden {
|
||||||
|
showChrome()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user