package com.suno.android.ui.bottom_sheets.song_actions import android.content.Intent import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.SheetState import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.suno.android.common_core_utils.constants.ReactionType import com.suno.android.common_data.mappers.clips.ClipStatus import com.suno.android.common_data.mappers.clips.SongListData import com.suno.android.common_data.mappers.clips.xAsLocalClipData import com.suno.android.common_data.mappers.hooks.LocalHookData import com.suno.android.common_networking.remote.entities.GenParamsSpec import com.suno.android.common_ui.R import com.suno.android.common_ui.components.bottom_sheet.ShareLinkBottomSheet import com.suno.android.common_ui.components.bottom_sheet.SingleSelectionBottomSheet import com.suno.android.common_ui.components.bottom_sheet.handleShareClicked import com.suno.android.common_ui.components.buttons.SunoMediumButton import com.suno.android.common_ui.components.dialogs.CommercialRightsDialog import com.suno.android.common_ui.components.indicators.Pill import com.suno.android.common_ui.components.loading.SunoSpinner import com.suno.android.common_ui.theme.ExtendedTheme import com.suno.android.ui.bottom_sheets.create.RemixTypeSelectionBottomSheet import com.suno.android.ui.bottom_sheets.playlists.AddToPlaylistBottomSheet import com.suno.android.ui.screens.create.CreatePromptForm import com.suno.android.ui.screens.songdetails.SongDetailsBottomSheet import kotlinx.coroutines.launch import com.suno.android.common_res.R as CommonResR @Composable fun SongActionsTray( vm: SongActionsTrayVM = hiltViewModel(), song: SongListData, canRemoveFromPlaylist: Boolean, isShowShareVideoGateEnabled: Boolean = false, sheetState: SheetState, songActionBottomSheetConsumerEvents: (SongActionBottomSheetConsumerEvent) -> Unit, hook: LocalHookData? = null, ) { val scope = rememberCoroutineScope() val viewState by vm.state.collectAsStateWithLifecycle() val context = LocalContext.current val resources = LocalResources.current val clipboardManager = LocalClipboardManager.current val songQualityReportSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, // Allow partial expansion, ) val addToPlaylistSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, ) val shareLinkSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, ) val songDetailsSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, ) val remixTypeSelectionBottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, ) var showAddToPlaylistBottomSheet by remember { mutableStateOf(false) } var showReportQualityBottomSheet by remember { mutableStateOf(false) } var showShareLinkBottomSheet by remember { mutableStateOf(false) } var showSongDetailsBottomSheet by remember { mutableStateOf(false) } var showRemixTypeSelectionBottomSheet by remember { mutableStateOf(false) } var songDetailsBottomSheetIsInPostingMode by remember { mutableStateOf(false) } var showCommercialRightsDialog by remember { mutableStateOf(false) } val createDocumentLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument("audio/mpeg"), ) { uri -> uri?.let { vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnDownloadTapped( songId = song.id, songUrl = song.mediaUrl, songTitle = song.title?.takeIf { it.isNotBlank() } ?: resources.getString(CommonResR.string.untitled), uri = it, ), ) } } LaunchedEffect(addToPlaylistSheetState.isVisible) { showAddToPlaylistBottomSheet = addToPlaylistSheetState.isVisible } // We need this launched effect for the initial setting of the modal sheet state so it'll animate properly for FIRST launch LaunchedEffect(showReportQualityBottomSheet) { if (showReportQualityBottomSheet) { // Sheet is now in composition, animate it songQualityReportSheetState.show() } } // Bidirectional sync for sheet state so it'll animate LaunchedEffect(songQualityReportSheetState.isVisible) { showReportQualityBottomSheet = songQualityReportSheetState.isVisible } LaunchedEffect(showReportQualityBottomSheet) { if (showShareLinkBottomSheet && !songDetailsSheetState.isVisible) { songDetailsSheetState.show() } } LaunchedEffect(songDetailsSheetState.isVisible) { showSongDetailsBottomSheet = songDetailsSheetState.isVisible } LaunchedEffect(showShareLinkBottomSheet) { if (showShareLinkBottomSheet) { // Sheet is now in composition, animate it shareLinkSheetState.show() } } // Bidirectional sync for sheet state so it'll animate LaunchedEffect(shareLinkSheetState.isVisible) { showShareLinkBottomSheet = shareLinkSheetState.isVisible } // Bidirectional sync for sheet state so it'll animate LaunchedEffect(remixTypeSelectionBottomSheetState.isVisible) { showRemixTypeSelectionBottomSheet = remixTypeSelectionBottomSheetState.isVisible } LaunchedEffect(song) { vm.sendEvent( SongActionBottomSheetScreenEvent.Internal.InitializeSong( song = song, ), ) if (viewState.isSongOwnedByViewer) { vm.sendEvent( SongActionBottomSheetScreenEvent.SetVideoCoverInHooksToggle( enabled = song.optOutVideoCoverHook == false, ), ) } } fun handleCloseSheet() { scope.launch { sheetState.hide() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } } val songUnpublishedMessage = stringResource(CommonResR.string.song_actions_tray_toast_unpublished) val songPublishedMessage = stringResource(CommonResR.string.song_actions_tray_toast_published) LaunchedEffect(Unit) { vm.effects.collect { effect -> when (effect) { SongActionBottomSheetScreenEffect.DismissSongActionsContainerHost -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } is SongActionBottomSheetScreenEffect.NavigateToRemix -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.NavigateToRemix( songId = effect.songId, task = effect.task, ), ) } is SongActionBottomSheetScreenEffect.NavigateToShareVideo -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.NavigateToShareVideo( songId = effect.songId, ), ) } is SongActionBottomSheetScreenEffect.OnShowSnackbar -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnShowSnackbar( message = effect.message, ), ) } is SongActionBottomSheetScreenEffect.OnSongRenamed -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnSongRenamed( song = effect.song, ), ) songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } is SongActionBottomSheetScreenEffect.OnSongDownloadComplete -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnShowSnackbar( message = context.getString( if (effect.success) { CommonResR.string.saved_to_downloads } else { CommonResR.string.unable_to_download }, effect.songTitle, ), ), ) songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } is SongActionBottomSheetScreenEffect.OnSongDeleted -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnSongDeleted( effect.deletedSong, ), ) songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } is SongActionBottomSheetScreenEffect.OnSongUndoDeleted -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnSongUndoDeleted( effect.undoDeletedSong, ), ) songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } is SongActionBottomSheetScreenEffect.OnSongVisibilityChange -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnShowSnackbar( message = if (effect.isPublic) songPublishedMessage else songUnpublishedMessage, ), ) songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } is SongActionBottomSheetScreenEffect.ShareLink -> { launch { handleShareClicked( platform = effect.sharePlatform, shareLink = effect.link.url, context = context, clipboardManager = clipboardManager, ) vm.onSongShared( platform = effect.sharePlatform.backendValue, ) } shareLinkSheetState.hide() handleCloseSheet() } is SongActionBottomSheetScreenEffect.ShareVideo -> { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.NavigateToShareVideo( songId = effect.songId, ), ) } } } } val statusBarHeightDp = with(LocalDensity.current) { WindowInsets.statusBars.getTop(LocalDensity.current).toDp() } Column( modifier = Modifier .fillMaxWidth() .defaultMinSize(minHeight = 128.dp) .heightIn( max = (LocalConfiguration.current.screenHeightDp - statusBarHeightDp.value).dp, ) .padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { SongActionsTrayHeader( songTitle = song.title, songImageUrl = song.displayImageUrl, songTags = song.displayTags ?: song.tags, onClose = { handleCloseSheet() }, ) // Action items LazyColumn( modifier = Modifier .fillMaxWidth() .weight(weight = 1f, fill = false), verticalArrangement = Arrangement.spacedBy(12.dp), ) { item { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { GroupedActionsCard(modifier = Modifier.weight(1f)) { SongVerticalAction( text = stringResource(CommonResR.string.add_to_playlist), iconRes = R.drawable.plus, disabled = song.isPreview, ) { vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnAddToPlaylistTapped( songId = song.id, ), ) showAddToPlaylistBottomSheet = true scope.launch { sheetState.hide() } } } GroupedActionsCard(modifier = Modifier.weight(1f)) { SongVerticalAction( text = stringResource(id = CommonResR.string.share_song_title), iconRes = R.drawable.share_android, ) { vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnShareTapped( songId = song.id, ), ) showShareLinkBottomSheet = true } } } } if (viewState.isSongOwnedByViewer) { item { GroupedActionsCard { SongHorizontalAction( text = stringResource(id = CommonResR.string.song_actions_tray_action_edit_song), iconRes = R.drawable.info, ) { songDetailsBottomSheetIsInPostingMode = false showSongDetailsBottomSheet = true } } } if (!song.isPreview) { item { GroupedActionsCard { SongHorizontalAction( text = stringResource(id = CommonResR.string.remaster), iconRes = R.drawable.remaster, disabled = viewState.remasterModel == null || viewState.hasCreditsLeft != true || song.status != ClipStatus.Complete, textSuffix = { viewState.remasterModel?.let { Pill( text = it.name, color = ExtendedTheme.colors.aukPink, contentColor = Color.Black, ) } }, rightContent = { if (viewState.hasCreditsLeft == false) { Text( color = Color.Red, style = ExtendedTheme.typography.userTitle, text = stringResource(CommonResR.string.insufficient_credits_short), ) } else if (song.status == ClipStatus.Streaming) { Text( color = Color.Red, style = ExtendedTheme.typography.userTitle, text = stringResource(CommonResR.string.still_generating), ) } }, ) { viewState.remasterModel?.let { model -> vm.sendEvent( SongActionBottomSheetScreenEvent.OnRemasterTapped( songId = song.id, modelExternalKey = model.externalKey, ), ) } handleCloseSheet() } GroupedActionDivider() SongHorizontalAction( text = stringResource(id = CommonResR.string.reuse_style), iconRes = R.drawable.reuse, ) { vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnReuseTapped( songId = song.id, ), ) handleCloseSheet() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnReusePromptClicked( formData = CreatePromptForm( isCustom = true, prompt = song.prompt, gptDescriptionPrompt = song.gptPrompt, isInstrumental = false, title = song.title, tags = song.tags, modelVersionName = song.modelName ?: "v4", generationType = GenParamsSpec.GenerationType.TEXT, ), ), ) } if (viewState.isRemixGateEnabled == true) { GroupedActionDivider() SongHorizontalAction( text = stringResource(CommonResR.string.remix), iconRes = R.drawable.remix, ) { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnRemixClicked( songId = song.id, ), ) scope.launch { remixTypeSelectionBottomSheetState.show() showRemixTypeSelectionBottomSheet = true } } } } } } } else { if (!song.isPreview) { item { GroupedActionsCard { SongHorizontalAction( text = stringResource(id = CommonResR.string.reuse_style), iconRes = R.drawable.reuse, ) { handleCloseSheet() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnReusePromptClicked( formData = CreatePromptForm( isCustom = true, prompt = song.prompt, gptDescriptionPrompt = song.gptPrompt, isInstrumental = false, title = song.title, tags = song.tags, modelVersionName = song.modelName ?: "v4", generationType = GenParamsSpec.GenerationType.TEXT, ), ), ) } val canRemix = song.canRemix == true if (viewState.isRemixGateEnabled == true && canRemix) { GroupedActionDivider() SongHorizontalAction( text = stringResource(CommonResR.string.remix), iconRes = R.drawable.remix, ) { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnRemixClicked( songId = song.id, ), ) scope.launch { remixTypeSelectionBottomSheetState.show() showRemixTypeSelectionBottomSheet = true } } } } } } } val includeRemoveFromPlaylist = canRemoveFromPlaylist val includeDownload = viewState.isDownloadsGateEnabled && viewState.isSongOwnedByViewer if ((includeRemoveFromPlaylist || includeDownload) && !song.isPreview) { item { GroupedActionsCard { if (includeDownload) { val isDownloading = viewState.downloadState != null val disableDownload = if (viewState.isDownloadDisableGateEnabled) { song.downloadDisabledReason != null || isDownloading } else { isDownloading } val downloadDisabledReasonText = if (song.downloadDisabledReason == "remix_contest") { stringResource(CommonResR.string.download_disabled_remix_context) } else { null } SongHorizontalAction( text = stringResource(id = CommonResR.string.download), subtitle = downloadDisabledReasonText, iconRes = R.drawable.download, disabled = disableDownload, rightContent = { if (isDownloading) SunoSpinner(modifier = Modifier.size(24.dp)) }, onClick = { if (viewState.isCommercialRightsEnabled) { createDocumentLauncher.launch( song.title?.takeIf { it.isNotBlank() } ?: resources.getString(CommonResR.string.untitled), ) } else { showCommercialRightsDialog = true } }, ) } if (includeRemoveFromPlaylist) { SongHorizontalAction( text = stringResource(CommonResR.string.remove_from_playlist), iconRes = R.drawable.minus, ) { vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnRemoveFromPlaylistTapped( song = song, ), ) handleCloseSheet() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnRemoveFromPlaylistTapped( song = song, ), ) } } } } } if (viewState.isSongOwnedByViewer && song.isPublic && !song.isPreview) { item { GroupedActionsCard { SongHorizontalAction( text = stringResource(CommonResR.string.song_actions_tray_button_unpublish), iconRes = R.drawable.public_off, ) { vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnSongUnpublishTapped( songId = song.id, ), ) } } } } if (viewState.isSongOwnedByViewer && !song.isPreview) { item { GroupedActionsCard { SongHorizontalAction( text = stringResource(id = CommonResR.string.song_details_more_options_toggle_allow_remix), nonInteractive = true, iconRes = R.drawable.remix, rightContent = { Switch( modifier = Modifier.height(24.dp), colors = SwitchDefaults.colors( checkedTrackColor = ExtendedTheme.colors.sunoPink, ), checked = song.canRemix == true, onCheckedChange = { vm.sendEvent( SongActionBottomSheetScreenEvent.ToggleRemixTapped( allowRemix = it, ), ) }, ) }, onClick = {}, ) val showVideoCoverInHooksFeedToggle = viewState.shouldShowVideoCoverInHooksFeedToggle if (showVideoCoverInHooksFeedToggle) { SongHorizontalAction( text = stringResource(CommonResR.string.show_video_cover_in_hooks_feed), nonInteractive = true, iconRes = R.drawable.hooks, rightContent = { Switch( modifier = Modifier.height(24.dp), colors = SwitchDefaults.colors( checkedTrackColor = ExtendedTheme.colors.sunoPink, ), checked = viewState.isVideoCoverInHooksEnabled, onCheckedChange = { vm.sendEvent( SongActionBottomSheetScreenEvent.OptOutVideoCoverHookToggled( optedOut = !it, // really? it's the opposite? ), ) }, ) }, ) {} } } } } item { val songReportedMsg = stringResource(id = CommonResR.string.song_reported) GroupedActionsCard { if (viewState.isSongOwnedByViewer) { SongHorizontalAction( text = stringResource(id = CommonResR.string.report_quality), iconRes = R.drawable.music_note_slash, ) { showReportQualityBottomSheet = true scope.launch { sheetState.hide() } } GroupedActionDivider() } val isSongDisliked = song.reaction == ReactionType.DISLIKE val dislikeTextId = if (isSongDisliked) { CommonResR.string.dislike_undo } else { CommonResR.string.dislike_song } SongHorizontalAction( text = stringResource(dislikeTextId), iconRes = R.drawable.thumbs_down, ) { vm.sendEvent( SongActionBottomSheetScreenEvent.ToggleDislikeSong( songId = song.id, currentReaction = song.reaction, hook = hook, ), ) } SongHorizontalAction( text = stringResource(id = CommonResR.string.report_inappropriate), iconRes = R.drawable.face_swear, ) { vm.sendEvent( SongActionBottomSheetScreenEvent.OnReportInappropriateTapped( songId = song.id, flagged = true, flaggedReason = "flag_inappropriate", ), ) handleCloseSheet() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnSongReported( message = songReportedMsg, ), ) } } } if (viewState.isSongOwnedByViewer) { item { GroupedActionsCard { SongHorizontalAction( text = stringResource(id = CommonResR.string.delete_song_button), iconRes = R.drawable.trash, foregroundColor = ExtendedTheme.colors.warning, ) { vm.sendEvent( SongActionBottomSheetScreenEvent.DeleteSong( song = song, undoDelete = false, ), ) handleCloseSheet() } } } } } if (viewState.isSongOwnedByViewer && !song.isPublic && !song.isPreview) { SunoMediumButton( text = stringResource(CommonResR.string.song_actions_tray_button_publish), onClick = { vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnSongPublishTapped( songId = song.id, ), ) songDetailsBottomSheetIsInPostingMode = true showSongDetailsBottomSheet = true }, modifier = Modifier.fillMaxWidth(), ) } if (showAddToPlaylistBottomSheet) { AddToPlaylistBottomSheet( sheetState = addToPlaylistSheetState, songId = song.id, onDismiss = { scope.launch { addToPlaylistSheetState.hide() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } }, ) } if (showShareLinkBottomSheet) { ShareLinkBottomSheet( sheetState = shareLinkSheetState, isShowShareVideoGateEnabled = isShowShareVideoGateEnabled, onShareClicked = { platform -> val songId = song.id vm.sendEvent( SongActionBottomSheetScreenEvent.StartShare( songId = songId, platform = platform, ), ) }, onDismiss = { showShareLinkBottomSheet = false }, ) } if (showReportQualityBottomSheet) { val songReportedMsg = stringResource(id = CommonResR.string.song_reported) SingleSelectionBottomSheet( sheetState = songQualityReportSheetState, headerName = stringResource(CommonResR.string.report_quality), confirmButtonName = stringResource(CommonResR.string.report), options = listOf( stringResource(CommonResR.string.report_audio_quality), stringResource(CommonResR.string.report_follow_lyrics), stringResource(CommonResR.string.report_follow_style), stringResource(CommonResR.string.report_inappropriate), stringResource(CommonResR.string.report_other), ), onConfirm = { selection -> vm.sendEvent( event = SongActionBottomSheetScreenEvent.OnReportBadQualityTapped( song = song, reason = selection, ), ) scope.launch { songQualityReportSheetState.hide() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnShowSnackbar( message = songReportedMsg, ), ) songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } }, onDismiss = { scope.launch { songQualityReportSheetState.hide() songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.DismissSongActionsContainerHost, ) } }, ) } if (showSongDetailsBottomSheet) { SongDetailsBottomSheet( sheetState = songDetailsSheetState, songId = song.id, isInPostingMode = songDetailsBottomSheetIsInPostingMode, ) } if (showRemixTypeSelectionBottomSheet) { RemixTypeSelectionBottomSheet( clip = song.xAsLocalClipData(), onDismiss = { showRemixTypeSelectionBottomSheet = false }, onOptionClick = { showRemixTypeSelectionBottomSheet = false songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.NavigateToRemix( songId = song.id, task = it.task, ), ) }, ) } if (showCommercialRightsDialog) { Dialog( onDismissRequest = { showCommercialRightsDialog = false }, properties = DialogProperties( usePlatformDefaultWidth = false, ), ) { CommercialRightsDialog( onUpgradeClick = { songActionBottomSheetConsumerEvents.invoke( SongActionBottomSheetConsumerEvent.OnUpgradeClicked, ) }, onDownloadClick = { createDocumentLauncher.launch( song.title?.takeIf { it.isNotBlank() } ?: resources.getString(CommonResR.string.untitled), ) }, onFAQClick = { val webIntent = Intent( Intent.ACTION_VIEW, "https://help.suno.com/en/categories/550145-rights-ownership".toUri(), ) context.startActivity(webIntent) }, onDismiss = { showCommercialRightsDialog = false }, ) } } } } @Composable private fun GroupedActionsCard( modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit, ) { val shape = RoundedCornerShape(10.dp) Column( modifier = modifier .clip(shape) .border( width = 1.dp, color = ExtendedTheme.colors.backgroundTertiary, shape = shape, ) .background(ExtendedTheme.colors.backgroundSecondary), ) { content() } } @Composable private fun GroupedActionDivider() { HorizontalDivider( thickness = 1.dp, color = ExtendedTheme.colors.backgroundTertiary, ) }