package com.suno.android.ui.screens.create.text import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import com.suno.android.common_res.R import com.suno.android.common_ui.components.buttons.RoundedPillContainer import com.suno.android.common_ui.components.omni.CircleIconButton import com.suno.android.common_ui.components.preview.SunoPreview import com.suno.android.common_ui.components.text_fields.OneBoxInputTextField import com.suno.android.common_ui.models.SeekInteraction import com.suno.android.common_ui.theme.ExtendedTheme import com.suno.android.ui.screens.create.audio.AudioCreateSection import com.suno.android.ui.screens.create.audio.CreateAudioEffect import com.suno.android.ui.screens.create.audio.CreateAudioEvent import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @Composable fun CreateTextSimpleMode( modifier: Modifier = Modifier, viewState: CreateTextState, sendEvent: (CreateTextEvent) -> Unit, audioEffects: Flow, seekInteraction: SeekInteraction, contentPadding: PaddingValues = PaddingValues.Zero, ) { Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = modifier .verticalScroll(rememberScrollState()) .padding(contentPadding), ) { AudioCreateSection( onStartButtonClick = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnAddAudioClick)) }, showEntryPointButton = viewState.showAudioCreateEntryPoint && viewState.isShowAudioOnSimpleModeGateEnabled, state = viewState.audioState, effects = audioEffects, onAudioSelected = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnAudioToUploadSelected(it))) }, onUploadCancelClick = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnCancelUploadClick)) }, onAudioRemoveClick = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnRemoveUploadClick)) }, onAudioFailureDismissClick = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnDismissFailureClick)) }, onChangeCreateTypeClick = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnChangeCreateTypeClick)) }, onPlayClick = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnPlayClick)) }, onPauseClick = { sendEvent(CreateTextEvent.AudioEvent(CreateAudioEvent.OnPauseClick)) }, seekInteraction = seekInteraction, ) SimpleModeSongDescriptionInput( songDescriptionInput = viewState.songDescriptionInput, onSongDescriptionTextInput = { textInput -> sendEvent( CreateTextEvent.OnSongDescriptionTextInput( songDescriptionText = textInput, ), ) }, isInstrumental = viewState.isInstrumental, onInstrumentalCheckChange = { sendEvent( CreateTextEvent.OnInstrumentalModeToggled( isInstrumentalMode = it, ), ) }, recommendStyles = viewState.recommendStyles, onRefreshRecommendedStyles = { sendEvent(CreateTextEvent.OnRefreshRecommendedStyles) }, onLyricsButtonPress = { sendEvent( CreateTextEvent.OnCustomModeToggled( isCustomMode = true, ), ) }, modifier = Modifier.fillMaxWidth(), ) } } @Composable fun SimpleModeSongDescriptionInput( songDescriptionInput: String, onSongDescriptionTextInput: (String) -> Unit, isInstrumental: Boolean, onInstrumentalCheckChange: (Boolean) -> Unit, recommendStyles: List, onRefreshRecommendedStyles: () -> Unit, onLyricsButtonPress: () -> Unit, modifier: Modifier = Modifier, ) { val focusManager = LocalFocusManager.current val adjectives = persistentListOf( stringResource(R.string.acoustic), stringResource(R.string.aggressive), stringResource(R.string.anthemic), stringResource(R.string.atmospheric), stringResource(R.string.bouncy), stringResource(R.string.chill), stringResource(R.string.dark), stringResource(R.string.dreamy), stringResource(R.string.electronic), stringResource(R.string.emotional), stringResource(R.string.epic), stringResource(R.string.experimental), stringResource(R.string.futuristic), stringResource(R.string.groovy), stringResource(R.string.heartfelt), stringResource(R.string.infectious), stringResource(R.string.melodic), stringResource(R.string.mellow), stringResource(R.string.powerful), stringResource(R.string.psychedelic), stringResource(R.string.romantic), stringResource(R.string.smooth), stringResource(R.string.syncopated), stringResource(R.string.uplifting), ) val genres = persistentListOf( stringResource(R.string.afrobeat), stringResource(R.string.anime), stringResource(R.string.ballad), stringResource(R.string.bedroom_pop), stringResource(R.string.bluegrass), stringResource(R.string.blues), stringResource(R.string.classical), stringResource(R.string.country), stringResource(R.string.cumbia), stringResource(R.string.dance), stringResource(R.string.dancepop), stringResource(R.string.delta_blues), stringResource(R.string.electropop), stringResource(R.string.disco), stringResource(R.string.dream_pop), stringResource(R.string.drum_and_bass), stringResource(R.string.edm), stringResource(R.string.emo), stringResource(R.string.folk), stringResource(R.string.funk), stringResource(R.string.future_bass), stringResource(R.string.gospel), stringResource(R.string.grunge), stringResource(R.string.grime), stringResource(R.string.hip_hop), stringResource(R.string.house), stringResource(R.string.indie), stringResource(R.string.j_pop), stringResource(R.string.jazz), stringResource(R.string.k_pop), stringResource(R.string.kids_music), stringResource(R.string.metal), stringResource(R.string.new_jack_swing), stringResource(R.string.new_wave), stringResource(R.string.opera), stringResource(R.string.pop), stringResource(R.string.punk), stringResource(R.string.raga), stringResource(R.string.rap), stringResource(R.string.reggae), stringResource(R.string.reggaeton), stringResource(R.string.rock), stringResource(R.string.rumba), stringResource(R.string.salsa), stringResource(R.string.samba), stringResource(R.string.sertanejo), stringResource(R.string.soul), stringResource(R.string.synthpop), stringResource(R.string.swing), stringResource(R.string.synthwave), stringResource(R.string.techno), stringResource(R.string.trap), stringResource(R.string.uk_garage), ) val topics = persistentListOf( stringResource(R.string.a_bad_breakup), stringResource(R.string.finding_love_on_a_rainy_day), stringResource(R.string.a_cozy_rainy_day), stringResource(R.string.dancing_all_night_long), stringResource(R.string.dancing_with_you_for_the_last_time), stringResource(R.string.not_being_able_to_wait_to_see_you_again), stringResource(R.string.how_youre_always_there_for_me), stringResource(R.string.when_youre_not_around), stringResource(R.string.a_faded_photo_on_the_mantel), stringResource(R.string.a_literal_banana), stringResource(R.string.wanting_to_be_with_you), stringResource(R.string.writing_a_face_melting_guitar_solo), stringResource(R.string.the_place_where_we_used_to_go), stringResource(R.string.being_trapped_in_an_AI_song_factory_help), ) fun insertRandomPrompt() { val adjective = adjectives.random() val genre = genres.random() val topic = topics.random() val article = if (adjective.firstOrNull()?.let { "aeiou".contains(it.lowercaseChar()) } == true ) { "An" } else { "A" } val prompt = "$article $adjective $genre song about $topic" onSongDescriptionTextInput.invoke(prompt) focusManager.clearFocus() } var oneBoxPlaceholder by remember { mutableIntStateOf(R.string.prompt_placeholder_1) } LaunchedEffect(Unit) { while (true) { delay(5.seconds) oneBoxPlaceholder = when (oneBoxPlaceholder) { R.string.prompt_placeholder_1 -> R.string.prompt_placeholder_2 R.string.prompt_placeholder_2 -> R.string.prompt_placeholder_3 else -> R.string.prompt_placeholder_1 } } } Card( colors = CardDefaults.cardColors( containerColor = ExtendedTheme.colors.backgroundTertiary, ), shape = MaterialTheme.shapes.small, modifier = modifier, ) { Row( modifier = Modifier.padding(start = 16.dp, top = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp), ) { Text( text = stringResource(R.string.song_description), style = ExtendedTheme.typography.body, color = ExtendedTheme.colors.textPrimary, ) CircleIconButton( onClick = { insertRandomPrompt() }, vectorDrawableResId = com.suno.android.common_ui.R.drawable.dice, contentDescription = null, ) } OneBoxInputTextField( placeHolderText = stringResource(oneBoxPlaceholder), inputText = songDescriptionInput, onValueChange = { textInput -> onSongDescriptionTextInput.invoke(textInput) }, keyboardOptions = KeyboardOptions.Default.copy( showKeyboardOnFocus = true, keyboardType = KeyboardType.Text, imeAction = ImeAction.Done, ), ) Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = if (isInstrumental) Arrangement.End else Arrangement.SpaceBetween, ) { Row( modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { AnimatedVisibility(!isInstrumental) { RoundedPillContainer( onClick = { onLyricsButtonPress.invoke() }, content = { Row( verticalAlignment = Alignment.CenterVertically, ) { Icon( imageVector = Icons.Default.Add, contentDescription = null, tint = ExtendedTheme.colors.textPrimary, ) Spacer(modifier = Modifier.width(4.dp)) Text( text = stringResource(R.string.lyrics), style = ExtendedTheme.typography.smallBody, color = ExtendedTheme.colors.textPrimary, ) } }, ) } } InstrumentalToggleButton( label = stringResource(id = R.string.instrumental), isChecked = isInstrumental, onCheckedChange = { onInstrumentalCheckChange.invoke(it) }, ) } HorizontalDivider( modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp), color = ExtendedTheme.colors.borderPrimary.copy(alpha = 0.5f), ) Text( modifier = Modifier.padding(start = 16.dp, top = 8.dp, bottom = 4.dp), text = stringResource(R.string.inspiration), style = ExtendedTheme.typography.body, color = ExtendedTheme.colors.foregroundInactive, ) TagStyleRow( recommendStyles = recommendStyles, songDescriptionInput = songDescriptionInput, onSongDescriptionTextInput = onSongDescriptionTextInput, onRefreshRecommendedStyles = onRefreshRecommendedStyles, ) } } @Composable fun TagStyleRow( recommendStyles: List, songDescriptionInput: String, onSongDescriptionTextInput: (String) -> Unit, onRefreshRecommendedStyles: () -> Unit, ) { LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), modifier = Modifier.height(56.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { if (recommendStyles.isNotEmpty()) { val filteredStyles = recommendStyles.filter { style -> !songDescriptionInput.contains(style) } items(filteredStyles) { songStyle -> RoundedPillContainer( onClick = { val prompt = songDescriptionInput if (prompt.isEmpty()) { onSongDescriptionTextInput.invoke(songStyle) } else { onSongDescriptionTextInput.invoke("$prompt, $songStyle") } }, ) { Row( verticalAlignment = Alignment.CenterVertically, ) { Icon( imageVector = Icons.Default.Add, contentDescription = null, tint = ExtendedTheme.colors.textPrimary, ) Spacer(modifier = Modifier.width(4.dp)) Text( text = songStyle, style = ExtendedTheme.typography.smallBody, color = ExtendedTheme.colors.textPrimary, ) } } } // Check if we're near the end of the list and fetch more styles if (filteredStyles.size <= 3) { onRefreshRecommendedStyles.invoke() } } } } @PreviewLightDark @Composable private fun CreateSimpleTextMode_Preview() { SunoPreview { CreateTextSimpleMode( audioEffects = emptyFlow(), viewState = CreateTextState(), sendEvent = {}, seekInteraction = object : SeekInteraction { override fun onSeekStarted() = Unit override fun onSeekPositionChanged( newSeekPosition: Duration, ) = Unit override fun onSeekStopped() = Unit }, ) } }