Skip to content

Set as Ringtone Dialog Compose#254

Open
TheTerminatorOfProgramming wants to merge 1 commit intomardous:masterfrom
TheTerminatorOfProgramming:ringtone_dialog_compose
Open

Set as Ringtone Dialog Compose#254
TheTerminatorOfProgramming wants to merge 1 commit intomardous:masterfrom
TheTerminatorOfProgramming:ringtone_dialog_compose

Conversation

@TheTerminatorOfProgramming
Copy link
Contributor

@TheTerminatorOfProgramming TheTerminatorOfProgramming commented Jan 10, 2026

Convert the Set as Ringtone Dialog to Compose

Summary by Sourcery

Convert the legacy "set as ringtone" dialog to a Compose-based bottom sheet flow with navigation support.

New Features:

  • Add a ringtone bottom sheet dialog implemented with Jetpack Compose for selecting ringtone options.
  • Introduce a permission bottom sheet that guides users to grant write settings permission when required before setting a ringtone.
  • Add a new navigation dialog destination for the ringtone flow that accepts a Song argument.

Enhancements:

  • Replace direct SetRingtoneDialog usage with navigation to the new ringtone bottom sheet fragment.
  • Configure the ringtone bottom sheet behavior and sizing via a dedicated BottomSheetDialogFragment and new dimension resource.

Convert the Set as Ringtone Dialog to Compose
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 10, 2026

Reviewer's Guide

Converts the legacy XML-based "Set as Ringtone" dialog into a Compose-driven bottom sheet presented via a navigation dialog destination, including a separate permission bottom sheet and wiring it into the existing song menu flow.

Sequence diagram for the new set-as-ringtone Compose bottom sheet flow

sequenceDiagram
    actor User
    participant SongMenu
    participant HostingFragment
    participant NavController
    participant RingtoneFragment
    participant SystemSettings
    participant RingtoneBottomSheet
    participant RingtonePermissionBottomSheet

    User->>SongMenu: select action_set_as_ringtone
    SongMenu->>HostingFragment: onSongMenu(this)
    HostingFragment->>NavController: navigate(nav_ringtone, songDetailArgs(song))
    NavController->>RingtoneFragment: create with extraSong
    RingtoneFragment->>RingtoneFragment: onCreateView(inflater, container, savedInstanceState)
    RingtoneFragment->>SystemSettings: canWrite(context)
    alt write settings granted
        RingtoneFragment->>RingtoneBottomSheet: show for song
        User->>RingtoneBottomSheet: toggle use_also_as_alarm_alert
        User->>RingtoneBottomSheet: tap action_set_as_ringtone
        RingtoneBottomSheet->>song: configureRingtone(context, useAlsoAsAlarm)
    else write settings denied
        RingtoneFragment->>RingtonePermissionBottomSheet: show for song
        User->>RingtonePermissionBottomSheet: tap action_grant
        RingtonePermissionBottomSheet->>SystemSettings: startActivity(ACTION_MANAGE_WRITE_SETTINGS)
    end
Loading

Updated class diagram for the new RingtoneFragment and related types

classDiagram
    class BottomSheetDialogFragment
    class RingtoneFragmentArgs
    class Song

    class RingtoneFragment {
        - navArgs: RingtoneFragmentArgs
        - song: Song
        + onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View
    }

    BottomSheetDialogFragment <|-- RingtoneFragment
    RingtoneFragmentArgs --> Song
    RingtoneFragment --> Song
Loading

Flow diagram for navigation to the new ringtone Compose bottom sheet

flowchart TD
    A["Song.onSongMenu handles action_set_as_ringtone"] --> B["HostingFragment.findActivityNavController(R.id.fragment_container)"]
    B --> C["NavController.navigate(R.id.nav_ringtone, songDetailArgs(song))"]
    C --> D["Nav_graph dialog destination nav_ringtone"]
    D --> E["RingtoneFragment receives extraSong argument"]
    E --> F{"Settings.System.canWrite(context)?"}
    F -->|Yes| G["Show RingtoneBottomSheet(song)"]
    F -->|No| H["Show RingtonePermissionBottomSheet(song)"]
Loading

File-Level Changes

Change Details Files
Replace the old SetRingtoneDialog with a Compose-based bottom-sheet flow driven by a new BottomSheetDialogFragment and navigation dialog destination.
  • Add a new nav dialog destination nav_ringtone that takes a Song argument and points to RingtoneFragment.
  • Update the song menu handler for the set-as-ringtone action to navigate to nav_ringtone instead of showing the old SetRingtoneDialog.
  • Remove the legacy SetRingtoneDialog class and its XML layout dialog_ringtone.xml.
app/src/main/res/navigation/graph_main.xml
app/src/main/java/com/mardous/booming/ui/component/menu/MenuItemClickExt.kt
app/src/main/java/com/mardous/booming/ui/dialogs/songs/SetRingtoneDialog.kt
app/src/main/res/layout/dialog_ringtone.xml
Introduce a Compose UI for the ringtone configuration bottom sheet that mirrors the old dialog behavior and calls configureRingtone.
  • Create RingtoneBottomSheet composable with title text using the song title, a checkbox for also using the tone as alarm alert, and an action button that invokes song.configureRingtone(context, checkedState).
  • Use BottomSheetDialogSurface and Material3 components (Text, Checkbox, Button) with scroll support and basic spacing/styling.
app/src/main/java/com/mardous/booming/ui/screen/ringtone/RingtoneScreen.kt
Add a Compose UI for handling missing WRITE_SETTINGS permission and directing the user to system settings.
  • Create RingtonePermissionBottomSheet composable that explains the missing permission using the song title and a grant button.
  • Implement grant button to launch Settings.ACTION_MANAGE_WRITE_SETTINGS for the app package, catching ActivityNotFoundException.
app/src/main/java/com/mardous/booming/ui/screen/ringtone/RingtonePermissionScreen.kt
Wrap the ringtone/permission composables in a BottomSheetDialogFragment that decides which sheet to show based on WRITE_SETTINGS capability and configures bottom sheet behavior.
  • Introduce RingtoneFragment as a BottomSheetDialogFragment using navArgs to receive the Song argument.
  • Configure BottomSheetDialog behavior (fitToContents, skipCollapsed, peekHeight, maxHeight) using a new shuffle_height dimension.
  • Render either RingtonePermissionBottomSheet or RingtoneBottomSheet in a ComposeView under BoomingMusicTheme depending on Settings.System.canWrite().
app/src/main/java/com/mardous/booming/ui/screen/ringtone/RingtoneFragment.kt
app/src/main/res/values/dimens.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • Both RingtoneBottomSheet and RingtonePermissionBottomSheet accept song: Song? but immediately use song!! in string resources; consider making song non-nullable in the composable API and enforcing that at the call sites to avoid potential NPEs.
  • The Settings.System.canWrite(requireContext()) check is done directly in the composable setContent block; consider hoisting this into a value (e.g., via remember or computed before composition) to avoid calling the platform API during recomposition and to make the logic easier to test.
  • Using R.dimen.shuffle_height as the bottom sheet peekHeight/maxHeight for the ringtone dialog couples it to an unrelated dimension; consider introducing a dedicated ringtone bottom sheet height dimension to make the layout intent clearer and easier to maintain.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Both `RingtoneBottomSheet` and `RingtonePermissionBottomSheet` accept `song: Song?` but immediately use `song!!` in string resources; consider making `song` non-nullable in the composable API and enforcing that at the call sites to avoid potential NPEs.
- The `Settings.System.canWrite(requireContext())` check is done directly in the composable `setContent` block; consider hoisting this into a value (e.g., via `remember` or computed before composition) to avoid calling the platform API during recomposition and to make the logic easier to test.
- Using `R.dimen.shuffle_height` as the bottom sheet `peekHeight`/`maxHeight` for the ringtone dialog couples it to an unrelated dimension; consider introducing a dedicated ringtone bottom sheet height dimension to make the layout intent clearer and easier to maintain.

## Individual Comments

### Comment 1
<location> `app/src/main/java/com/mardous/booming/ui/screen/ringtone/RingtoneScreen.kt:40-49` </location>
<code_context>
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RingtonePermissionBottomSheet(
+    song: Song?
+) {
+    val context = LocalContext.current
</code_context>

<issue_to_address>
**issue (bug_risk):** Avoid nullable `Song` with `song!!` and make the parameter non-nullable if it's required.

Here `song` is declared as `Song?` but immediately used as `song!!`. Since navigation always provides a non-null `Song`, change the parameter to `song: Song` and remove the `!!` to avoid potential crashes if this composable is ever called with null in the future.
</issue_to_address>

### Comment 2
<location> `app/src/main/java/com/mardous/booming/ui/screen/ringtone/RingtonePermissionScreen.kt:38-47` </location>
<code_context>
+fun RingtonePermissionBottomSheet(
</code_context>

<issue_to_address>
**issue (bug_risk):** Align `RingtonePermissionBottomSheet` parameter nullability with actual usage instead of using `song!!`.

This composable always needs a `Song` (for `song.title`), so making the parameter non-nullable avoids potential NPEs and keeps the API consistent with how the fragment passes the argument.
</issue_to_address>

### Comment 3
<location> `app/src/main/java/com/mardous/booming/ui/screen/ringtone/RingtoneFragment.kt:31-36` </location>
<code_context>
+    ): View {
+        dialog?.setOnShowListener { dialogInterface ->
+            val bottomSheetDialog = dialogInterface as BottomSheetDialog
+            bottomSheetDialog.behavior.apply {
+                isFitToContents = true
+                skipCollapsed = true
+                peekHeight = dip(R.dimen.shuffle_height)
+                //isDraggable = false
+                maxHeight = peekHeight
+            }
+        }
</code_context>

<issue_to_address>
**suggestion:** Using a large fixed `peekHeight`/`maxHeight` may misbehave on small or rotated screens.

With `isFitToContents = true` and `skipCollapsed = true`, hard-coding both `peekHeight` and `maxHeight` to `dip(R.dimen.shuffle_height)` (800dp) can exceed the available height on some devices, causing layout problems. Consider using `PEEK_HEIGHT_AUTO`, deriving the height from the window size, or clamping this dimension to the screen height at runtime.

Suggested implementation:

```
        dialog?.setOnShowListener { dialogInterface ->
            val bottomSheetDialog = dialogInterface as BottomSheetDialog
            bottomSheetDialog.behavior.apply {
                isFitToContents = true
                skipCollapsed = true

                val desiredPeekHeight = dip(R.dimen.shuffle_height)
                val windowHeight = resources.displayMetrics.heightPixels
                val clampedPeekHeight = kotlin.math.min(desiredPeekHeight, windowHeight)

                peekHeight = clampedPeekHeight
                //isDraggable = false
                maxHeight = clampedPeekHeight
            }
        }

```

If your project prefers imports over fully-qualified calls, add:
`import kotlin.math.min`
at the top of `RingtoneFragment.kt` and replace `kotlin.math.min` with `min` in the body.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +40 to +49
song: Song?
) {
var checkedState by remember { mutableStateOf(false) }
val context = LocalContext.current

BottomSheetDialogSurface {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Avoid nullable Song with song!! and make the parameter non-nullable if it's required.

Here song is declared as Song? but immediately used as song!!. Since navigation always provides a non-null Song, change the parameter to song: Song and remove the !! to avoid potential crashes if this composable is ever called with null in the future.

Comment on lines +38 to +47
fun RingtonePermissionBottomSheet(
song: Song?
) {
val context = LocalContext.current

BottomSheetDialogSurface {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Align RingtonePermissionBottomSheet parameter nullability with actual usage instead of using song!!.

This composable always needs a Song (for song.title), so making the parameter non-nullable avoids potential NPEs and keeps the API consistent with how the fragment passes the argument.

Comment on lines +31 to +36
bottomSheetDialog.behavior.apply {
isFitToContents = true
skipCollapsed = true
peekHeight = dip(R.dimen.shuffle_height)
//isDraggable = false
maxHeight = peekHeight
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Using a large fixed peekHeight/maxHeight may misbehave on small or rotated screens.

With isFitToContents = true and skipCollapsed = true, hard-coding both peekHeight and maxHeight to dip(R.dimen.shuffle_height) (800dp) can exceed the available height on some devices, causing layout problems. Consider using PEEK_HEIGHT_AUTO, deriving the height from the window size, or clamping this dimension to the screen height at runtime.

Suggested implementation:

        dialog?.setOnShowListener { dialogInterface ->
            val bottomSheetDialog = dialogInterface as BottomSheetDialog
            bottomSheetDialog.behavior.apply {
                isFitToContents = true
                skipCollapsed = true

                val desiredPeekHeight = dip(R.dimen.shuffle_height)
                val windowHeight = resources.displayMetrics.heightPixels
                val clampedPeekHeight = kotlin.math.min(desiredPeekHeight, windowHeight)

                peekHeight = clampedPeekHeight
                //isDraggable = false
                maxHeight = clampedPeekHeight
            }
        }

If your project prefers imports over fully-qualified calls, add:
import kotlin.math.min
at the top of RingtoneFragment.kt and replace kotlin.math.min with min in the body.

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