Skip to content

feat: Added asset selection for multiple installer variants (#293)#314

Merged
rainxchzed merged 5 commits intoOpenHub-Store:mainfrom
tuuhin:feature/assets-options
Mar 10, 2026
Merged

feat: Added asset selection for multiple installer variants (#293)#314
rainxchzed merged 5 commits intoOpenHub-Store:mainfrom
tuuhin:feature/assets-options

Conversation

@tuuhin
Copy link
Contributor

@tuuhin tuuhin commented Mar 9, 2026

This PR includes an Asset Picker logic. If a release contains multiple installer files, the application will now allow user to select the variant option (#293 )

Key Changes:

  • Selection UI: Included a assets selection picker
  • Resources: Added base string resources in strings.xml associated with the new ui

Screenshots

  • Windows
    image

  • Android
    image

Summary by CodeRabbit

  • New Features

    • Release asset picker: view and select specific release files from a picker.
    • Version type filtering: choose Stable vs Pre-release listings.
  • Refactor

    • Reworked version/asset controls into a side-by-side header layout for easier access.
    • Cleaned up Android string resources (ordering/formatting only; no text changes).

Included an assets picker to allow users to select specific download assets
Included strings.xml res for the new string resources used no-translations are added though for them
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f55cece7-7ae7-485c-b003-2deed6762ce0

📥 Commits

Reviewing files that changed from the base of the PR and between f3d5ee7 and d2ba980.

📒 Files selected for processing (1)
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt

Walkthrough

Adds an asset-selection UI and wiring to Details: new ReleaseAssetsPicker component, VersionTypePicker, ViewModel action handlers (SelectDownloadAsset, ToggleReleaseAssetsPicker), new state fields (userProfile, primaryAsset, isReleaseSelectorVisible), VersionPicker signature change, and string resource reordering.

Changes

Cohort / File(s) Summary
String Resources
core/presentation/src/commonMain/composeResources/values/strings.xml
Large reorder/reformat of Android string resources; keys and values preserved.
Action Types
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt
Added SelectDownloadAsset(val release: GithubAsset) and ToggleReleaseAssetsPicker action variants.
State Updates
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsState.kt
Added userProfile: GithubUserProfile?, primaryAsset: GithubAsset?, isReleaseSelectorVisible: Boolean; selectedRelease repositioned within the data class.
ViewModel Logic
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
Added handlers for the two new actions (set primaryAsset, toggle isReleaseSelectorVisible); replaced wildcard resource import with explicit imports and adjusted error-result construction.
New Asset Picker UI
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/ReleaseAssetsPicker.kt
New Compose component with bottom-sheet asset list, selection handling, about dialog, and format utility; emits SelectDownloadAsset / ToggleReleaseAssetsPicker.
AppHeader / Platform Derivation
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt
Memoized derived supportedPlatforms from release assets and switched rendering to use it; author rendering guarded for null.
Version Selection UI
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/VersionPicker.kt, .../VersionTypePicker.kt
Removed selectedCategory from VersionPicker signature; added VersionTypePicker (FilterChip row); UI adjustments (enabled state, clipping, latest-release derivation).
Header Layout
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/Header.kt
Replaced previous VersionPicker usage with VersionTypePicker; added a two-part row combining ReleaseAssetsPicker and VersionPicker.
SmartInstallButton Preview
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/SmartInstallButton.kt
Preview now provides liquid state via rememberLiquidState and CompositionLocalProvider; no public API changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant UI as ReleaseAssetsPicker
  participant VM as DetailsViewModel
  participant State as DetailsStateStore

  User->>UI: Tap to open assets picker
  UI->>VM: Dispatch ToggleReleaseAssetsPicker
  VM->>State: set isReleaseSelectorVisible = true
  State-->>UI: visible = true
  UI->>User: show bottom sheet with assets
  User->>UI: Select asset A
  UI->>VM: Dispatch SelectDownloadAsset(asset A)
  VM->>State: set primaryAsset = asset A
  State-->>UI: updated primaryAsset
  UI->>User: display selected asset in header
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code and found a nest,

assets lined up, versions dressed,
a picker here, a tiny tweak,
state now holds the thing I seek,
nibble, pick, and bounce — UX blessed!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Added asset selection for multiple installer variants' accurately summarizes the main change—adding UI for users to select between multiple installer files in a release.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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: 3

🧹 Nitpick comments (6)
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/SmartInstallButton.kt (1)

480-487: Consider extracting formatFileSize to a shared utility.

This function is duplicated in ReleaseAssetsPicker.kt (lines 292-299) with identical implementation. Consider extracting it to a shared utility in the utils package to follow DRY principles.

🤖 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/SmartInstallButton.kt`
around lines 480 - 487, Extract the duplicated top-level function formatFileSize
into a single shared utility in the utils package (e.g. create a
FileSizeUtils.kt or top-level FileSizeUtils function formatFileSize(bytes:
Long): String) and remove the duplicate implementations in SmartInstallButton.kt
and ReleaseAssetsPicker.kt; update both files to import and call the shared
formatFileSize function (preserving the exact behavior/formatting) so both
components reference the single utility instead of keeping local copies.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt (1)

55-56: Misleading parameter name in SelectDownloadAsset.

The parameter is named release but the type is GithubAsset. This is confusing since elsewhere in this file release refers to GithubRelease (see line 43: SelectRelease(val release: GithubRelease)). Consider renaming for clarity.

Suggested fix
-    data class SelectDownloadAsset(val release: GithubAsset) : DetailsAction
+    data class SelectDownloadAsset(val asset: GithubAsset) : DetailsAction
🤖 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/DetailsAction.kt`
around lines 55 - 56, Rename the misleading parameter in the data class
SelectDownloadAsset from "release" to "asset" to reflect its GithubAsset type;
update the data class declaration (SelectDownloadAsset) and all call
sites/usages that instantiate or destructure SelectDownloadAsset to use the new
parameter name, and ensure this change remains consistent with the nearby
SelectRelease(val release: GithubRelease) declaration to avoid confusion between
GithubAsset and GithubRelease.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/ReleaseAssetsPicker.kt (2)

79-81: Unnecessary use of derivedStateOf for a simple boolean.

derivedStateOf is designed to avoid recomposition when derived values don't change despite input changes. Here, assetsList.isNotEmpty() is trivially computed and changes only when assetsList changes, which already triggers recomposition via the remember key. A simple remember suffices.

♻️ Suggested simplification
-    val isPickerEnabled by remember(assetsList) {
-        derivedStateOf { assetsList.isNotEmpty() }
-    }
+    val isPickerEnabled = remember(assetsList) { assetsList.isNotEmpty() }
🤖 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/ReleaseAssetsPicker.kt`
around lines 79 - 81, The boolean isPickerEnabled uses derivedStateOf
unnecessarily; replace the derivedStateOf usage with a simple remember keyed on
assetsList that returns assetsList.isNotEmpty() (i.e., change the declaration of
isPickerEnabled in ReleaseAssetsPicker to use remember(assetsList) {
assetsList.isNotEmpty() }), removing derivedStateOf to simplify and avoid the
extra wrapper.

84-90: Bottom sheet rendered unconditionally outside the visible Column layout.

The ReleaseAssetsItemsPicker is invoked before the main Column layout. While it internally checks showPicker and returns early, this ordering could be confusing for readers. Consider placing it after the main UI or within the Column for better code organization and readability.

🤖 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/ReleaseAssetsPicker.kt`
around lines 84 - 90, The ReleaseAssetsItemsPicker is declared before the main
Column which makes the layout ordering confusing even though it early-returns
based on isPickerVisible; move the ReleaseAssetsItemsPicker invocation to after
the main UI Column (or place it inside the Column where appropriate) so the
picker is rendered in code order with the visible layout, keeping the same props
(showPicker = isPickerVisible, assetsList, selectedAsset, onDismiss = {
onAction(DetailsAction.ToggleReleaseAssetsPicker) }, onSelect = {
onAction(DetailsAction.SelectDownloadAsset(it)) }) and without changing its
internal visibility check.
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/VersionPicker.kt (2)

94-96: Consider using TextOverflow.Ellipsis instead of Clip for better UX.

Using TextOverflow.Clip will abruptly cut off text without visual indication that content is truncated. TextOverflow.Ellipsis (used elsewhere in this file, e.g., line 105) provides a better user experience by indicating truncation with "...".

♻️ Suggested fix
                         text = selectedRelease?.tagName
                             ?: stringResource(Res.string.no_version_selected),
                         style = MaterialTheme.typography.titleSmall,
                         fontWeight = FontWeight.SemiBold,
-                        overflow = TextOverflow.Clip,
+                        overflow = TextOverflow.Ellipsis,
                         maxLines = 1,
                     )
🤖 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/VersionPicker.kt`
around lines 94 - 96, In VersionPicker.kt update the Text composable styling
that currently sets overflow = TextOverflow.Clip to use TextOverflow.Ellipsis so
truncated version labels show "..." (match the other usage in this file around
the existing ellipsis at line 105); locate the Text where fontWeight =
FontWeight.SemiBold and maxLines = 1 and replace the overflow value accordingly
to improve UX.

62-64: Same derivedStateOf simplification opportunity.

Similar to the feedback for ReleaseAssetsPicker, this derivedStateOf usage is unnecessary for a simple boolean check.

♻️ Suggested simplification
-    val isPickerEnabled by remember(filteredReleases) {
-        derivedStateOf { filteredReleases.isNotEmpty() }
-    }
+    val isPickerEnabled = remember(filteredReleases) { filteredReleases.isNotEmpty() }
🤖 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/VersionPicker.kt`
around lines 62 - 64, Replace the unnecessary remember + derivedStateOf pattern
for isPickerEnabled in VersionPicker.kt: instead of "val isPickerEnabled by
remember(filteredReleases) { derivedStateOf { filteredReleases.isNotEmpty() } }"
just compute the boolean directly (e.g., "val isPickerEnabled =
filteredReleases.isNotEmpty()"), referencing the isPickerEnabled variable and
filteredReleases in the VersionPicker component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/presentation/src/commonMain/composeResources/values/strings.xml`:
- Line 387: The string resource "no_version_selected" currently contains the
placeholder "0" which is inappropriate for user-facing UI; update the value of
the string resource named no_version_selected to a descriptive fallback such as
"No version" (or localized equivalent) so that VersionPicker.kt's fallback when
selectedRelease?.tagName is null displays a proper label; ensure the string name
remains unchanged and adjust any translations if present.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt`:
- Around line 248-263: The derivePlatformsFromAssets function uses a single when
branch which stops after the first match, so multiple platforms (e.g., Android
and Windows) can be missed; update the buildList block in
derivePlatformsFromAssets to use separate conditional checks (multiple if
statements) against names.any { ... } for each platform (Android, Windows,
macOS, Linux) so each matching platform is added independently, ensuring all
detected platforms from release.assets are appended to the list.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/ReleaseAssetsPicker.kt`:
- Around line 171-173: Replace the hardcoded contentDescription in the Icon call
inside IconButton with a localized stringResource; specifically, add a new
string resource (e.g., info_button_content_description) and update the Icon
invocation in ReleaseAssetsPicker (the Icon inside the IconButton whose onClick
sets showInfoDialog = true) to use
stringResource(R.string.info_button_content_description) instead of the literal
"Info" so it matches the rest of the file's localization approach.

---

Nitpick comments:
In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/ReleaseAssetsPicker.kt`:
- Around line 79-81: The boolean isPickerEnabled uses derivedStateOf
unnecessarily; replace the derivedStateOf usage with a simple remember keyed on
assetsList that returns assetsList.isNotEmpty() (i.e., change the declaration of
isPickerEnabled in ReleaseAssetsPicker to use remember(assetsList) {
assetsList.isNotEmpty() }), removing derivedStateOf to simplify and avoid the
extra wrapper.
- Around line 84-90: The ReleaseAssetsItemsPicker is declared before the main
Column which makes the layout ordering confusing even though it early-returns
based on isPickerVisible; move the ReleaseAssetsItemsPicker invocation to after
the main UI Column (or place it inside the Column where appropriate) so the
picker is rendered in code order with the visible layout, keeping the same props
(showPicker = isPickerVisible, assetsList, selectedAsset, onDismiss = {
onAction(DetailsAction.ToggleReleaseAssetsPicker) }, onSelect = {
onAction(DetailsAction.SelectDownloadAsset(it)) }) and without changing its
internal visibility check.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/SmartInstallButton.kt`:
- Around line 480-487: Extract the duplicated top-level function formatFileSize
into a single shared utility in the utils package (e.g. create a
FileSizeUtils.kt or top-level FileSizeUtils function formatFileSize(bytes:
Long): String) and remove the duplicate implementations in SmartInstallButton.kt
and ReleaseAssetsPicker.kt; update both files to import and call the shared
formatFileSize function (preserving the exact behavior/formatting) so both
components reference the single utility instead of keeping local copies.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/VersionPicker.kt`:
- Around line 94-96: In VersionPicker.kt update the Text composable styling that
currently sets overflow = TextOverflow.Clip to use TextOverflow.Ellipsis so
truncated version labels show "..." (match the other usage in this file around
the existing ellipsis at line 105); locate the Text where fontWeight =
FontWeight.SemiBold and maxLines = 1 and replace the overflow value accordingly
to improve UX.
- Around line 62-64: Replace the unnecessary remember + derivedStateOf pattern
for isPickerEnabled in VersionPicker.kt: instead of "val isPickerEnabled by
remember(filteredReleases) { derivedStateOf { filteredReleases.isNotEmpty() } }"
just compute the boolean directly (e.g., "val isPickerEnabled =
filteredReleases.isNotEmpty()"), referencing the isPickerEnabled variable and
filteredReleases in the VersionPicker component.

In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.kt`:
- Around line 55-56: Rename the misleading parameter in the data class
SelectDownloadAsset from "release" to "asset" to reflect its GithubAsset type;
update the data class declaration (SelectDownloadAsset) and all call
sites/usages that instantiate or destructure SelectDownloadAsset to use the new
parameter name, and ensure this change remains consistent with the nearby
SelectRelease(val release: GithubRelease) declaration to avoid confusion between
GithubAsset and GithubRelease.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d5f6223d-6233-4c7a-a25a-3aa24cf5a993

📥 Commits

Reviewing files that changed from the base of the PR and between f6c83dd and f95cf0a.

📒 Files selected for processing (10)
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsAction.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/AppHeader.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/ReleaseAssetsPicker.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/SmartInstallButton.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/VersionPicker.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/VersionTypePicker.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/Header.kt

@rainxchzed
Copy link
Member

@tuuhin Please could you apply the changes that Coderabbit has suggested? I would be able to merge the PR then.

derivePlatformsFromAssets in AppHeader.kt use of if statements rather than when
Use of string resources for content description in ReleaseAssetsPicker.kt
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: 1

🧹 Nitpick comments (1)
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt (1)

263-316: Reuse the shared InstallStatusBadge instead of maintaining a second copy.

This implementation matches core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/RepositoryCard.kt closely. Keeping both versions will drift the next time badge styling or copy changes.

🤖 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/AppHeader.kt`
around lines 263 - 316, This file defines a duplicate InstallStatusBadge; remove
this local function and use the shared InstallStatusBadge from core/presentation
instead: delete the InstallStatusBadge(...) declaration in AppHeader.kt, add the
appropriate import for the shared InstallStatusBadge, and call
InstallStatusBadge(isUpdateAvailable = ..., modifier = ...) wherever the local
badge was used so parameter names match (isUpdateAvailable, modifier) and
styling/copy is centralized.
🤖 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/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt`:
- Around line 173-177: The current Text call uses author?.login.toString() which
renders "null" for missing profiles; update the AppHeader composable so the Text
content only shows when author?.login is non-null or substitute a real fallback
(e.g., empty string or "Unknown") before passing to
stringResource(Res.string.by_author, ...). Locate the Text usage in AppHeader
(the Text composable that calls stringResource(Res.string.by_author,
author?.login.toString())) and change it to supply a safe non-null value (guard
the call with author?.login != null or compute val displayLogin = author?.login
?: fallback) so the UI never displays "by null".

---

Nitpick comments:
In
`@feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt`:
- Around line 263-316: This file defines a duplicate InstallStatusBadge; remove
this local function and use the shared InstallStatusBadge from core/presentation
instead: delete the InstallStatusBadge(...) declaration in AppHeader.kt, add the
appropriate import for the shared InstallStatusBadge, and call
InstallStatusBadge(isUpdateAvailable = ..., modifier = ...) wherever the local
badge was used so parameter names match (isUpdateAvailable, modifier) and
styling/copy is centralized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6dd03c05-9088-4775-ac40-222c06c09dd3

📥 Commits

Reviewing files that changed from the base of the PR and between f95cf0a and f3d5ee7.

📒 Files selected for processing (3)
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/ReleaseAssetsPicker.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/ReleaseAssetsPicker.kt
  • core/presentation/src/commonMain/composeResources/values/strings.xml

tuuhin and others added 3 commits March 10, 2026 19:01
derivePlatformsFromAssets in AppHeader.kt use of if statements rather than when
Use of string resources for content description in ReleaseAssetsPicker.kt
@rainxchzed
Copy link
Member

Everything looks good, lets get it merged. @tuuhin Thanks a lot for your contribution ❤️

@rainxchzed rainxchzed merged commit c4f30f3 into OpenHub-Store:main Mar 10, 2026
1 check passed
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.

2 participants