longform: remove card styling from preview in full article view
When viewing the full article (not truncated), remove the card border and background styling for a cleaner reading experience. Card styling is now only shown in truncated preview mode (timeline, search results). Changelog-Changed: Removed card styling from longform preview in full article view Signed-off-by: alltheseas
This commit is contained in:
committed by
Daniel D’Aquino
parent
28a2c23a76
commit
ef262b3c22
@@ -212,7 +212,8 @@ struct ChatroomThreadView: View {
|
|||||||
ThreadedSwipeViewGroup(scroller: scroller, events: trusted_events)
|
ThreadedSwipeViewGroup(scroller: scroller, events: trusted_events)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top)
|
// Remove top padding for longform articles with sepia to eliminate gap
|
||||||
|
.padding(.top, isLongformEvent && damus.settings.longform_sepia_mode ? 0 : nil)
|
||||||
|
|
||||||
// MARK: - Children view - outside trusted network
|
// MARK: - Children view - outside trusted network
|
||||||
if !untrusted_events.isEmpty {
|
if !untrusted_events.isEmpty {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ struct EventBody: View {
|
|||||||
let should_blur_img: Bool
|
let should_blur_img: Bool
|
||||||
let options: EventViewOptions
|
let options: EventViewOptions
|
||||||
|
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_blur_img: Bool? = nil, options: EventViewOptions) {
|
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_blur_img: Bool? = nil, options: EventViewOptions) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.event = event
|
self.event = event
|
||||||
@@ -29,12 +31,36 @@ struct EventBody: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if event.known_kind == .longform {
|
if event.known_kind == .longform {
|
||||||
LongformPreviewBody(state: damus_state, ev: event, options: options, header: true)
|
let isFullArticle = !options.contains(.truncate_content)
|
||||||
|
let sepiaEnabled = damus_state.settings.longform_sepia_mode
|
||||||
|
|
||||||
|
if isFullArticle && sepiaEnabled {
|
||||||
|
// Wrap in single sepia container to eliminate gaps
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
LongformPreviewBody(
|
||||||
|
state: damus_state,
|
||||||
|
ev: event,
|
||||||
|
options: options,
|
||||||
|
header: true,
|
||||||
|
sepiaEnabled: true
|
||||||
|
)
|
||||||
|
note_content
|
||||||
|
}
|
||||||
|
.background(DamusColors.sepiaBackground(for: colorScheme))
|
||||||
|
} else {
|
||||||
|
LongformPreviewBody(
|
||||||
|
state: damus_state,
|
||||||
|
ev: event,
|
||||||
|
options: options,
|
||||||
|
header: true,
|
||||||
|
sepiaEnabled: false
|
||||||
|
)
|
||||||
|
|
||||||
// truncated longform bodies are just the preview
|
// truncated longform bodies are just the preview
|
||||||
if !options.contains(.truncate_content) {
|
if isFullArticle {
|
||||||
note_content
|
note_content
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if event.known_kind == .highlight {
|
} else if event.known_kind == .highlight {
|
||||||
HighlightBodyView(state: damus_state, ev: event, options: options)
|
HighlightBodyView(state: damus_state, ev: event, options: options)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
|||||||
@@ -12,12 +12,19 @@ struct SelectedEventView: View {
|
|||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let size: EventViewKind
|
let size: EventViewKind
|
||||||
|
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
var pubkey: Pubkey {
|
var pubkey: Pubkey {
|
||||||
event.pubkey
|
event.pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
@StateObject var bar: ActionBarModel
|
@StateObject var bar: ActionBarModel
|
||||||
|
|
||||||
|
/// Whether to apply sepia styling to the entire view (for longform articles)
|
||||||
|
var useSepia: Bool {
|
||||||
|
event.known_kind == .longform && damus.settings.longform_sepia_mode
|
||||||
|
}
|
||||||
|
|
||||||
init(damus: DamusState, event: NostrEvent, size: EventViewKind) {
|
init(damus: DamusState, event: NostrEvent, size: EventViewKind) {
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.event = event
|
self.event = event
|
||||||
@@ -78,6 +85,10 @@ struct SelectedEventView: View {
|
|||||||
}
|
}
|
||||||
.compositingGroup()
|
.compositingGroup()
|
||||||
}
|
}
|
||||||
|
// Apply sepia background to outer HStack for full width coverage
|
||||||
|
// Note: foregroundStyle intentionally NOT applied here to preserve UI element contrast
|
||||||
|
// (buttons, icons, timestamps). Article text gets sepia styling via EventBody.
|
||||||
|
.background(useSepia ? DamusColors.sepiaBackground(for: colorScheme) : Color.clear)
|
||||||
}
|
}
|
||||||
|
|
||||||
var Mention: some View {
|
var Mention: some View {
|
||||||
|
|||||||
@@ -19,11 +19,15 @@ struct LongformMarkdownView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
/// Relative line spacing in em units (1.5x multiplier = 0.5em extra spacing)
|
/// Relative line spacing in em units (1.5x multiplier = 0.5em extra spacing)
|
||||||
|
/// Guarded against negative values for safety
|
||||||
private var relativeLineSpacing: CGFloat {
|
private var relativeLineSpacing: CGFloat {
|
||||||
lineHeightMultiplier - 1.0
|
max(0, lineHeightMultiplier - 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
// Full-width background container
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Spacer(minLength: 0)
|
||||||
Markdown(markdown)
|
Markdown(markdown)
|
||||||
// Override only paragraph style, preserving all other default formatting (headings, lists, etc.)
|
// Override only paragraph style, preserving all other default formatting (headings, lists, etc.)
|
||||||
.markdownBlockStyle(\.paragraph) { configuration in
|
.markdownBlockStyle(\.paragraph) { configuration in
|
||||||
@@ -34,8 +38,10 @@ struct LongformMarkdownView: View {
|
|||||||
.markdownImageProvider(.kingfisher(disable_animation: disableAnimation))
|
.markdownImageProvider(.kingfisher(disable_animation: disableAnimation))
|
||||||
.markdownInlineImageProvider(.kingfisher)
|
.markdownInlineImageProvider(.kingfisher)
|
||||||
.frame(maxWidth: 600, alignment: .leading)
|
.frame(maxWidth: 600, alignment: .leading)
|
||||||
.frame(maxWidth: .infinity)
|
.padding([.leading, .trailing])
|
||||||
.padding([.leading, .trailing, .top])
|
Spacer(minLength: 0)
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
.background(sepiaEnabled ? DamusColors.sepiaBackground(for: colorScheme) : Color.clear)
|
.background(sepiaEnabled ? DamusColors.sepiaBackground(for: colorScheme) : Color.clear)
|
||||||
.foregroundStyle(sepiaEnabled ? DamusColors.sepiaText(for: colorScheme) : Color.primary)
|
.foregroundStyle(sepiaEnabled ? DamusColors.sepiaText(for: colorScheme) : Color.primary)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,28 @@ struct LongformPreviewBody: View {
|
|||||||
let event: LongformEvent
|
let event: LongformEvent
|
||||||
let options: EventViewOptions
|
let options: EventViewOptions
|
||||||
let header: Bool
|
let header: Bool
|
||||||
|
let sepiaEnabled: Bool
|
||||||
@State var blur_images: Bool = true
|
@State var blur_images: Bool = true
|
||||||
|
|
||||||
@ObservedObject var artifacts: NoteArtifactsModel
|
@ObservedObject var artifacts: NoteArtifactsModel
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
init(state: DamusState, ev: LongformEvent, options: EventViewOptions, header: Bool) {
|
init(state: DamusState, ev: LongformEvent, options: EventViewOptions, header: Bool, sepiaEnabled: Bool = false) {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.event = ev
|
self.event = ev
|
||||||
self.options = options
|
self.options = options
|
||||||
self.header = header
|
self.header = header
|
||||||
|
self.sepiaEnabled = sepiaEnabled
|
||||||
|
|
||||||
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(state: DamusState, ev: NostrEvent, options: EventViewOptions, header: Bool) {
|
init(state: DamusState, ev: NostrEvent, options: EventViewOptions, header: Bool, sepiaEnabled: Bool = false) {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.event = LongformEvent.parse(from: ev)
|
self.event = LongformEvent.parse(from: ev)
|
||||||
self.options = options
|
self.options = options
|
||||||
self.header = header
|
self.header = header
|
||||||
|
self.sepiaEnabled = sepiaEnabled
|
||||||
|
|
||||||
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
||||||
}
|
}
|
||||||
@@ -110,6 +114,17 @@ struct LongformPreviewBody: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
// In full article view with sepia, wrap in full-width container for seamless background
|
||||||
|
let fullWidthSepia = !truncate && sepiaEnabled
|
||||||
|
|
||||||
|
if fullWidthSepia {
|
||||||
|
// Full-width sepia background container
|
||||||
|
MainContent
|
||||||
|
.padding(.horizontal)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(DamusColors.sepiaBackground(for: colorScheme))
|
||||||
|
.foregroundStyle(DamusColors.sepiaText(for: colorScheme))
|
||||||
|
} else {
|
||||||
Group {
|
Group {
|
||||||
if options.contains(.wide) {
|
if options.contains(.wide) {
|
||||||
Main.padding(.horizontal)
|
Main.padding(.horizontal)
|
||||||
@@ -118,6 +133,69 @@ struct LongformPreviewBody: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Content without card styling - for use in full-width sepia container
|
||||||
|
var MainContent: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
if let url = event.image {
|
||||||
|
if (self.options.contains(.no_media)) {
|
||||||
|
EmptyView()
|
||||||
|
} else if !blur_images || (!blur_images && !state.settings.media_previews) {
|
||||||
|
titleImage(url: url)
|
||||||
|
} else if blur_images || (blur_images && !state.settings.media_previews) {
|
||||||
|
ZStack {
|
||||||
|
titleImage(url: url)
|
||||||
|
BlurOverlayView(blur_images: $blur_images, artifacts: nil, size: nil, damus_state: nil, parentView: .longFormView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(event.title ?? NSLocalizedString("Untitled", comment: "Title of longform event if it is untitled."))
|
||||||
|
.font(header ? .title : .headline)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
if let summary = event.summary {
|
||||||
|
truncatedText(content: CompatibleText(stringLiteral: summary))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let labels = event.labels {
|
||||||
|
ScrollView(.horizontal) {
|
||||||
|
HStack {
|
||||||
|
ForEach(labels, id: \.self) { label in
|
||||||
|
Text(label)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(DamusColors.sepiaText(for: colorScheme).opacity(0.7))
|
||||||
|
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
|
||||||
|
.background(DamusColors.sepiaText(for: colorScheme).opacity(0.1))
|
||||||
|
.cornerRadius(20)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 20)
|
||||||
|
.stroke(DamusColors.sepiaText(for: colorScheme).opacity(0.3), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scrollIndicators(.hidden)
|
||||||
|
.padding(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .loaded(let arts) = artifacts.state,
|
||||||
|
case .longform(let longform) = arts
|
||||||
|
{
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
ReadTime(longform.estimatedReadTimeMinutes)
|
||||||
|
Text("·")
|
||||||
|
Words(longform.words)
|
||||||
|
}
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding([.horizontal, .bottom], 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
var Main: some View {
|
var Main: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
@@ -147,15 +225,17 @@ struct LongformPreviewBody: View {
|
|||||||
ScrollView(.horizontal) {
|
ScrollView(.horizontal) {
|
||||||
HStack {
|
HStack {
|
||||||
ForEach(labels, id: \.self) { label in
|
ForEach(labels, id: \.self) { label in
|
||||||
|
// In full article view with sepia, use subtle sepia-tinted tag styling
|
||||||
|
let useSepiaTags = !truncate && sepiaEnabled
|
||||||
Text(label)
|
Text(label)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(useSepiaTags ? DamusColors.sepiaText(for: colorScheme).opacity(0.7) : .gray)
|
||||||
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
|
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
|
||||||
.background(DamusColors.neutral1)
|
.background(useSepiaTags ? DamusColors.sepiaText(for: colorScheme).opacity(0.1) : DamusColors.neutral1)
|
||||||
.cornerRadius(20)
|
.cornerRadius(20)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 20)
|
RoundedRectangle(cornerRadius: 20)
|
||||||
.stroke(DamusColors.neutral3, lineWidth: 1)
|
.stroke(useSepiaTags ? DamusColors.sepiaText(for: colorScheme).opacity(0.3) : DamusColors.neutral3, lineWidth: 1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,13 +259,18 @@ struct LongformPreviewBody: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.background(DamusColors.neutral3)
|
// Only show card styling when content is truncated (preview mode)
|
||||||
.cornerRadius(10)
|
// In full article view, use sepia background if enabled, otherwise clear
|
||||||
|
.background(truncate ? DamusColors.neutral3 : (sepiaEnabled ? DamusColors.sepiaBackground(for: colorScheme) : Color.clear))
|
||||||
|
.foregroundStyle(truncate ? Color.primary : (sepiaEnabled ? DamusColors.sepiaText(for: colorScheme) : Color.primary))
|
||||||
|
.cornerRadius(truncate ? 10 : 0)
|
||||||
.overlay(
|
.overlay(
|
||||||
|
truncate ?
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.stroke(DamusColors.neutral1, lineWidth: 1)
|
.stroke(DamusColors.neutral1, lineWidth: 1)
|
||||||
|
: nil
|
||||||
)
|
)
|
||||||
.padding(.top, 10)
|
.padding(.top, truncate ? 10 : 0)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event.event, our_pubkey: state.pubkey)
|
blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event.event, our_pubkey: state.pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class DamusColors {
|
|||||||
static let lighterPink = Color(red: 248/255.0, green: 105/255.0, blue: 182/255.0)
|
static let lighterPink = Color(red: 248/255.0, green: 105/255.0, blue: 182/255.0)
|
||||||
static let lightBackgroundPink = Color(red: 0xF8/255.0, green: 0xE7/255.0, blue: 0xF8/255.0)
|
static let lightBackgroundPink = Color(red: 0xF8/255.0, green: 0xE7/255.0, blue: 0xF8/255.0)
|
||||||
|
|
||||||
// Sepia mode colors for comfortable longform reading (based on research: 25% lower luminescence reduces eye strain)
|
// Sepia mode colors for comfortable longform reading
|
||||||
// Light mode sepia
|
// Light mode sepia
|
||||||
static let sepiaBackgroundLight = Color(red: 0.98, green: 0.95, blue: 0.90) // #FAF3E6 - warm off-white
|
static let sepiaBackgroundLight = Color(red: 0.98, green: 0.95, blue: 0.90) // #FAF3E6 - warm off-white
|
||||||
static let sepiaTextLight = Color(red: 0.35, green: 0.27, blue: 0.20) // #5A4632 - warm brown
|
static let sepiaTextLight = Color(red: 0.35, green: 0.27, blue: 0.20) // #5A4632 - warm brown
|
||||||
|
|||||||
Reference in New Issue
Block a user