Skip to content

Details improvement#271

Merged
rainxchzed merged 5 commits intomainfrom
details-improvement
Feb 28, 2026
Merged

Details improvement#271
rainxchzed merged 5 commits intomainfrom
details-improvement

Conversation

@rainxchzed
Copy link
Owner

@rainxchzed rainxchzed commented Feb 28, 2026

Summary by CodeRabbit

  • New Features
    • About and What's New sections now support expandable content with animated collapse/expand, a bottom fade overlay, and "Read More" / "Show Less" toggles.
    • Markdown/HTML rendering greatly improved to handle images, links, headings, formatting, reference-style Markdown, videos and many edge cases.
    • Added localized "Read More" / "Show Less" strings for multiple languages.

…eprocessing

This commit introduces expandable "About" and "What's New" sections in the repository details view to handle long READMEs and release notes. It also significantly enhances the Markdown preprocessing logic to better handle HTML-in-Markdown and GitHub-specific formatting.

- **feat(details)**: Introduced `ExpandableMarkdownContent` component to provide "Read More" and "Show Less" functionality for long text blocks.
- **feat(details)**: Added `isAboutExpanded` and `isWhatsNewExpanded` states to `DetailsState` along with corresponding toggle actions in `DetailsViewModel`.
- **refactor(data)**: Extensively updated `preprocessMarkdown.kt` to handle various HTML tags (e.g., `<picture>`, `<video>`, `<a>`, headings, inline formatting) and convert them to standard Markdown.
- **refactor(data)**: Improved URL resolution in Markdown to correctly handle relative paths and GitHub blob links.
- **feat(ui)**: Integrated content size animations and gradient overlays for collapsed states in the details sections.
- **i18n**: Added `read_more` and `show_less` string resources.
This commit adds localized string resources for the "Read More" and "Show Less" labels across several languages to support UI components with expandable text.

- **i18n**: Added `read_more` and `show_less` string resources to Turkish (tr), Spanish (es), Chinese (zh-rCN), French (fr), Hindi (hi), Italian (it), Japanese (ja), Korean (kr), Polish (pl), Bengali (bn), and Russian (ru) locales.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40a4e8a and 3fe5d76.

📒 Files selected for processing (3)
  • feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt

Walkthrough

Adds localized "read more"/"show less" strings, introduces expand/collapse state and actions, refactors Details UI to render expandable Markdown for About/WhatsNew, and substantially enhances Markdown/HTML preprocessing for richer URL/image handling.

Changes

Cohort / File(s) Summary
Localization Resources
core/presentation/src/commonMain/composeResources/values/strings.xml, core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml, .../values-es/strings-es.xml, .../values-fr/strings-fr.xml, .../values-hi/strings-hi.xml, .../values-it/strings-it.xml, .../values-ja/strings-ja.xml, .../values-kr/strings-kr.xml, .../values-pl/strings-pl.xml, .../values-ru/strings-ru.xml, .../values-tr/strings-tr.xml, .../values-zh-rCN/strings-zh-rCN.xml
Added read_more and show_less string resources for multiple locales.
Presentation — Actions / State / ViewModel
feature/details/presentation/src/commonMain/kotlin/.../DetailsAction.kt, .../DetailsState.kt, .../DetailsViewModel.kt
Added ToggleAboutExpanded and ToggleWhatsNewExpanded actions; added isAboutExpanded and isWhatsNewExpanded to DetailsState; ViewModel toggles these flags on the new actions.
Presentation — Root / Orchestration
feature/details/presentation/src/commonMain/kotlin/.../DetailsRoot.kt
Switched to BoxWithConstraints, computes collapsedSectionHeight, and passes isExpanded, onToggleExpanded, and collapsedHeight into About and WhatsNew sections.
Presentation — Expandable UI Components
feature/details/presentation/src/commonMain/kotlin/.../components/sections/About.kt, .../WhatsNew.kt
Introduced ExpandableMarkdownContent composable and updated about/whatsNew to accept expansion props; added measurement-based collapsed height, animated height, gradient fade overlay, and Read more / Show less toggle UI.
Data — Markdown Preprocessing
feature/details/data/src/commonMain/kotlin/.../preprocessMarkdown.kt
Large rewrite: URL helpers, reference-style Markdown parsing/resolution, image/video/picture/a tag handling, HTML→Markdown conversions, entity decoding, skip rules for badges/SVGs, and extensive cleanup of HTML edge cases.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as DetailsScreen (Composable)
    participant VM as DetailsViewModel
    participant Data as PreprocessMarkdown
    User->>UI: open Details screen
    UI->>VM: load details
    VM->>Data: preprocessMarkdown(readme, baseUrl)
    Data-->>VM: markdownContent
    VM-->>UI: emit state + markdownContent
    UI->>UI: render ExpandableMarkdownContent(collapsed)
    User->>UI: tap "Read more"
    UI->>VM: DetailsAction.ToggleAboutExpanded
    VM-->>VM: toggle isAboutExpanded
    VM-->>UI: updated state (isAboutExpanded = true)
    UI->>UI: animate expand, show "Show less"
    User->>UI: tap "Show less"
    UI->>VM: DetailsAction.ToggleAboutExpanded
    VM-->>VM: toggle isAboutExpanded
    VM-->>UI: updated state (isAboutExpanded = false)
    UI->>UI: animate collapse, show "Read more"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble long markdown stems by moonlight,
I hide the tails till they gleam so bright,
I hop to "read more" with a joyful squeak,
then curl beneath "show less" when I seek sleep —
🥕✨ small hops, big docs, delight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Details improvement" is vague and generic; it lacks specificity about what aspects of the details feature were improved or what the main change entails. Use a more descriptive title that captures the primary change, such as "Add expandable sections to Details view with read more/show less functionality" or "Implement collapsible About and WhatsNew sections with localization."
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch details-improvement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt (1)

251-259: Consider expanding HTML entity decoding.

The current set covers common entities, but GitHub READMEs may contain others like &mdash;, &ndash;, &hellip;, or numeric entities (&#160;). This can be deferred if not causing issues currently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt`
around lines 251 - 259, The HTML entity decoding in preprocessMarkdown.kt only
handles a few entities via chained .replace on the processed string; extend
decoding to include common README entities (&mdash;, &ndash;, &hellip;, &trade;,
etc.) and numeric entities (decimal like &#160; and hex like &#x2014;) by adding
replacements for those named entities and a small routine to parse/replace
numeric entities (&#\d+; and &#x[0-9A-Fa-f]+;) into their Unicode code points;
update the code that operates on the processed variable (the replace chain in
the preprocessMarkdown function) to include these extra named replacements and a
numeric-entity decoder to ensure broader coverage on GitHub READMEs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt`:
- Around line 103-122: The video-handling replace blocks use raw src values so
relative video URLs aren't resolved; update both Regex replacement lambdas in
preprocessMarkdown (the two blocks that assign to processed using Regex for
<video> and for <video> with <source>) to call resolveUrl(src) and use the
resolved value when building the markdown link (e.g., val resolved =
resolveUrl(src) and then "[Video]($resolved)") instead of using
match.groupValues[2] directly; keep the same match and group extraction logic
but replace src with the resolved URL.
- Around line 23-33: The resolveUrl function currently uses
path.trim().trimStart('.', '/') which strips leading ".." and breaks
parent-directory references; replace that trimming with logic that only removes
leading "./" segments and any leading slashes but preserves "../" sequences.
Specifically, in resolveUrl (and where normalizedBaseUrl is used) trim
whitespace, then repeatedly remove only "./" prefixes (not ".."), then remove
leading '/' characters (e.g., with a loop or regex that matches ^(?:\./)+ and
then ^/+), and finally concatenate "$normalizedBaseUrl$cleaned" so
"../images/logo.png" remains intact while "./img.png" and "/img.png" are
normalized.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt`:
- Around line 115-120: The collapse-height logic uses maxHeight (unbounded in
lazy lists) and the clipped measurement so needsExpansion becomes unstable;
replace the dynamic collapsedHeightDp = maxHeight * 0.7f with a fixed dp
constant (e.g. val collapsedHeightDp = 200.dp) and convert it to px via
with(density) { collapsedHeightDp.toPx() }, and stabilize measurements by
measuring the full content height instead of the clipped height: move the
onGloballyPositioned that sets contentHeightPx to a wrapper that is not clipped
(or use SubcomposeLayout/MeasurePolicy to measure the content unconstrained) so
contentHeightPx captures the intrinsic full content height; update the symbols
collapsedHeightDp, collapsedHeightPx, contentHeightPx and needsExpansion
accordingly (also apply the same fix to the similar blocks at the other
occurrences referenced).

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt`:
- Around line 127-134: In WhatsNew.kt replace the image transformer passed to
the Markdown composable (currently Coil3ImageTransformerImpl) with the app's
custom MarkdownImageTransformer so the Details module renders images the same as
About; locate the Markdown call in the WhatsNew composable and swap the
imageTransformer parameter to MarkdownImageTransformer, ensuring any
imports/constructor usage match the existing MarkdownImageTransformer
implementation that handles GitHub URLs and filters SVGs.

---

Nitpick comments:
In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt`:
- Around line 251-259: The HTML entity decoding in preprocessMarkdown.kt only
handles a few entities via chained .replace on the processed string; extend
decoding to include common README entities (&mdash;, &ndash;, &hellip;, &trade;,
etc.) and numeric entities (decimal like &#160; and hex like &#x2014;) by adding
replacements for those named entities and a small routine to parse/replace
numeric entities (&#\d+; and &#x[0-9A-Fa-f]+;) into their Unicode code points;
update the code that operates on the processed variable (the replace chain in
the preprocessMarkdown function) to include these extra named replacements and a
numeric-entity decoder to ensure broader coverage on GitHub READMEs.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b252023 and 733bddb.

📒 Files selected for processing (19)
  • core/presentation/src/commonMain/composeResources/values-bn/strings-bn.xml
  • core/presentation/src/commonMain/composeResources/values-es/strings-es.xml
  • core/presentation/src/commonMain/composeResources/values-fr/strings-fr.xml
  • core/presentation/src/commonMain/composeResources/values-hi/strings-hi.xml
  • core/presentation/src/commonMain/composeResources/values-it/strings-it.xml
  • core/presentation/src/commonMain/composeResources/values-ja/strings-ja.xml
  • core/presentation/src/commonMain/composeResources/values-kr/strings-kr.xml
  • core/presentation/src/commonMain/composeResources/values-pl/strings-pl.xml
  • core/presentation/src/commonMain/composeResources/values-ru/strings-ru.xml
  • core/presentation/src/commonMain/composeResources/values-tr/strings-tr.xml
  • core/presentation/src/commonMain/composeResources/values-zh-rCN/strings-zh-rCN.xml
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsRoot.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsState.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt

This commit significantly improves the Markdown preprocessing for repository descriptions and READMEs. It introduces more robust URL resolution, better handling of HTML-to-Markdown conversions, and filters out SVGs/badges to keep the UI clean. Additionally, it refines the "Read More" expansion logic in the UI by centralizing height constraints.

- **feat(data)**: Overhauled `preprocessMarkdown` with support for reference-style links/images, better HTML tag stripping (e.g., `<picture>`, `<iframe>`, `<summary>`), and enhanced URL normalization.
- **feat(data)**: Added detection for common badge providers (Shields.io, Codecov, etc.) and SVG badges to exclude them from Markdown rendering.
- **refactor(ui)**: Moved `collapsedHeight` calculation to `DetailsRoot` and passed it down to `About` and `WhatsNew` sections for consistent expansion behavior.
- **refactor(ui)**: Replaced `BoxWithConstraints` with a more stable `onGloballyPositioned` approach in `About` and `WhatsNew` to prevent layout flickering during expansion.
- **fix(ui)**: Improved the "Read More" logic to ensure the content height is correctly measured and the fade-out gradient matches the container's background color.
- **chore**: Introduced `MarkdownImageTransformer` as a unified image transformer for Markdown content.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt (1)

409-416: Numeric entity decoding limited to ASCII printable range.

The check code in 32..126 excludes common entities like &#160; (non-breaking space), &#8212; (em dash), and &#8217; (curly quote). These will be left as-is in the output.

♻️ Optional: Expand to valid Unicode range
     processed = processed.replace(Regex("""&#(\d+);""")) { match ->
         val code = match.groupValues[1].toIntOrNull()
-        if (code != null && code in 32..126) {
+        if (code != null && code in 32..0x10FFFF && code.toChar().isDefined()) {
             code.toChar().toString()
         } else {
             match.value
         }
     }

Alternatively, handle hex entities too: &#x...;

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt`
around lines 409 - 416, The numeric entity decoder in preprocessMarkdown.kt
currently only decodes decimal entities in the ASCII printable range via the
processed.replace(Regex("""&#(\d+);""")) block; update it to decode both decimal
and hex entities (handle patterns like &#x...;) and accept the full Unicode
range (0..0x10FFFF). Parse the matched number with the appropriate radix (10 for
decimal, 16 for hex), validate it is within 0..0x10FFFF, and convert to a string
using Character.toChars(code) for codepoints > 0xFFFF (or toChar() for BMP)
before returning it; otherwise return the original match.value. Ensure the regex
and replacement logic reference the same match groups you update so both decimal
and hex forms are handled.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt (1)

115-177: Consider reusing ExpandableMarkdownContent to remove duplicated expand/collapse logic.

This block is nearly the same behavior already implemented in About.kt. Reusing that composable (with card-specific colors/modifier inputs) will reduce drift and future bug fixes in two places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt`
around lines 115 - 177, The current expand/collapse UI block duplicates logic
present in About.kt; replace the Column/Box/TextButton section with a call to
the shared ExpandableMarkdownContent composable to remove duplication. Pass the
release.description (falling back to
stringResource(Res.string.no_release_notes)), the same
colors/typography/flavour/imageTransformer (MarkdownImageTransformer), the
liquidState modifier, the collapsedHeight/content measurement hooks (or
collapsedHeight and a way for the shared composable to measure contentHeightPx),
and styling inputs such as cardColor and modifiers; wire the existing
isExpanded/needsExpansion state and onToggleExpanded callback into
ExpandableMarkdownContent so the gradient overlay and the “Read more” / “Show
less” button are handled by the shared component instead of duplicating the Box
+ TextButton logic in WhatsNew.kt.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt`:
- Around line 323-337: The anchor-link conversion currently inserts the raw
href, causing relative links to break; update the replacement lambda in the
Regex that converts <a ...>...</a> to call the existing resolveUrl(...) helper
on match.groupValues[2] (the url) and use the resolved URL in the output (e.g.,
val resolved = resolveUrl(url); then produce "[$text]($resolved)" or
"[$resolved]($resolved)" for empty text) so anchor links are resolved
consistently with images and videos.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt`:
- Around line 119-124: The contentHeightPx state should be reset when the
displayed content or collapsedHeight changes: update the remember call that
initializes contentHeightPx to include content and collapsedHeightPx as keys
(remember(content, collapsedHeightPx) { mutableStateOf(0f) }) so measurements
don't become stale after shrink/replace; also remove the remember wrapper around
needsExpansion and compute needsExpansion directly as val needsExpansion =
contentHeightPx > collapsedHeightPx && collapsedHeightPx > 0f to avoid
memoization using outdated values.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt`:
- Around line 109-113: The contentHeightPx state is being remembered globally
and not reset when the release content changes, causing needsExpansion to stay
true for shorter releases; update the state creation so contentHeightPx is
remembered per-release (e.g., remember(release.id) { mutableStateOf(0f) } or
otherwise keyed to the content) and ensure any other occurrences (the second
block around needsExpansion at lines ~136-140) use the same per-release keyed
remember; this guarantees contentHeightPx resets when the release changes and
prevents stale expansion toggles.

---

Nitpick comments:
In
`@feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt`:
- Around line 409-416: The numeric entity decoder in preprocessMarkdown.kt
currently only decodes decimal entities in the ASCII printable range via the
processed.replace(Regex("""&#(\d+);""")) block; update it to decode both decimal
and hex entities (handle patterns like &#x...;) and accept the full Unicode
range (0..0x10FFFF). Parse the matched number with the appropriate radix (10 for
decimal, 16 for hex), validate it is within 0..0x10FFFF, and convert to a string
using Character.toChars(code) for codepoints > 0xFFFF (or toChar() for BMP)
before returning it; otherwise return the original match.value. Ensure the regex
and replacement logic reference the same match groups you update so both decimal
and hex forms are handled.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt`:
- Around line 115-177: The current expand/collapse UI block duplicates logic
present in About.kt; replace the Column/Box/TextButton section with a call to
the shared ExpandableMarkdownContent composable to remove duplication. Pass the
release.description (falling back to
stringResource(Res.string.no_release_notes)), the same
colors/typography/flavour/imageTransformer (MarkdownImageTransformer), the
liquidState modifier, the collapsedHeight/content measurement hooks (or
collapsedHeight and a way for the shared composable to measure contentHeightPx),
and styling inputs such as cardColor and modifiers; wire the existing
isExpanded/needsExpansion state and onToggleExpanded callback into
ExpandableMarkdownContent so the gradient overlay and the “Read more” / “Show
less” button are handled by the shared component instead of duplicating the Box
+ TextButton logic in WhatsNew.kt.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 733bddb and 40a4e8a.

📒 Files selected for processing (4)
  • feature/details/data/src/commonMain/kotlin/zed/rainxch/details/data/utils/preprocessMarkdown.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsRoot.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt

…lity

This commit fixes issues where UI sections might not correctly recalculate their expandable heights when content changes and improves markdown link resolution.

- **fix(details)**: Updated `WhatsNew` and `About` sections to re-calculate `contentHeightPx` when the source content or collapsed height constraints change by adding appropriate keys to `remember`.
- **fix(details)**: Optimized `About` section by removing redundant `remember` for `needsExpansion` and switching to `mutableFloatStateOf` in `WhatsNew` for better performance.
- **fix(data)**: Enhanced markdown preprocessing to resolve relative or shorthand URLs within markdown link syntax.
@rainxchzed rainxchzed merged commit 60d9b1e into main Feb 28, 2026
1 of 2 checks passed
@rainxchzed rainxchzed deleted the details-improvement branch February 28, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant