package com.suno.android.ui.screens.songdetails import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults 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.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.suno.android.common_ui.components.list_items.comments.MentionState import com.suno.android.common_ui.theme.ExtendedTheme import com.suno.android.common_ui.theme.SunoTheme import com.suno.android.common_res.R as CommonResR import com.suno.android.common_ui.R as CommonUiR @Composable fun SongDetailsMainInput( songTitle: String, songCaptionInputValue: TextFieldValue, songCaptionLimit: Int, captionMentions: List = emptyList(), onTitleFocused: () -> Unit, onTitleUnfocused: () -> Unit, onCaptionFocused: () -> Unit, onCaptionUnfocused: () -> Unit, onTitleChanged: (text: String) -> Unit, onCaptionChanged: (inputValue: TextFieldValue) -> Unit, ) { val titleInteractionSource = remember { MutableInteractionSource() } val captionInteractionSource = remember { MutableInteractionSource() } val isTitleInputFocused by titleInteractionSource.collectIsFocusedAsState() val isCaptionFocused by captionInteractionSource.collectIsFocusedAsState() // Used to skip initial unfocused event when view is first composed var hasTitleInitialized by remember { mutableStateOf(false) } var hasCaptionInitialized by remember { mutableStateOf(false) } LaunchedEffect(isTitleInputFocused) { if (isTitleInputFocused) { onTitleFocused() } else if (!hasTitleInitialized) { hasTitleInitialized = true } else { onTitleUnfocused() } } LaunchedEffect(isCaptionFocused) { if (isCaptionFocused) { onCaptionFocused() } else if (!hasCaptionInitialized) { hasCaptionInitialized = true } else { onCaptionUnfocused() } } val titleTextStyle = ExtendedTheme.typography.largeSubtitle val captionTextStyle = ExtendedTheme.typography.userTitle Column( modifier = Modifier.padding(horizontal = 8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { BasicTextField( value = songTitle, onValueChange = { onTitleChanged(it) }, interactionSource = titleInteractionSource, singleLine = true, textStyle = titleTextStyle.copy(color = ExtendedTheme.colors.textPrimary), cursorBrush = SolidColor(ExtendedTheme.colors.sunoPink), ) { innerTextField -> TextFieldDefaults.DecorationBox( value = songTitle, innerTextField = innerTextField, interactionSource = titleInteractionSource, contentPadding = PaddingValues(0.dp), enabled = true, singleLine = false, visualTransformation = VisualTransformation.None, colors = TextFieldDefaults.colors( cursorColor = ExtendedTheme.colors.sunoPink, focusedTextColor = ExtendedTheme.colors.textPrimary, unfocusedTextColor = ExtendedTheme.colors.textPrimary, unfocusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, ), suffix = { if (!isTitleInputFocused) { Icon( painterResource(CommonUiR.drawable.pen_underlined), contentDescription = null, tint = ExtendedTheme.colors.foregroundInactive, ) } }, ) if (songTitle.isEmpty()) { Text( text = stringResource(CommonResR.string.untitled), style = titleTextStyle, color = ExtendedTheme.colors.foregroundInactive, ) } } val mentionColor = ExtendedTheme.colors.textPrimary BasicTextField( modifier = Modifier .fillMaxWidth() .height(100.dp), value = songCaptionInputValue, onValueChange = { onCaptionChanged(it) }, interactionSource = captionInteractionSource, textStyle = captionTextStyle.copy(color = ExtendedTheme.colors.textPrimary), cursorBrush = SolidColor(ExtendedTheme.colors.sunoPink), visualTransformation = { inputText -> val annotatedString = buildAnnotatedString { append(inputText.text) captionMentions.forEach { mention -> if (mention.start !in 0..inputText.text.length) return@forEach if (mention.end !in 0..inputText.text.length) return@forEach addStyle( style = SpanStyle( fontWeight = FontWeight.Bold, color = mentionColor, ), start = mention.start, end = mention.end, ) } } TransformedText( text = annotatedString, offsetMapping = OffsetMapping.Identity, ) }, ) { innerTextField -> Box( modifier = Modifier.fillMaxWidth(), ) { Text( text = "${songCaptionInputValue.text.length}/$songCaptionLimit", modifier = Modifier.align(Alignment.BottomEnd), style = captionTextStyle, color = if (songCaptionInputValue.text.length < songCaptionLimit) { ExtendedTheme.colors.foregroundInactive } else { ExtendedTheme.colors.warning }, ) } TextFieldDefaults.DecorationBox( value = songCaptionInputValue.text, innerTextField = innerTextField, interactionSource = captionInteractionSource, contentPadding = PaddingValues(0.dp), enabled = true, singleLine = false, visualTransformation = VisualTransformation.None, colors = TextFieldDefaults.colors( cursorColor = ExtendedTheme.colors.sunoPink, focusedTextColor = ExtendedTheme.colors.textSecondary, unfocusedTextColor = ExtendedTheme.colors.textSecondary, unfocusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, ), ) if (songCaptionInputValue.text.isEmpty()) { Text( stringResource(CommonResR.string.song_details_input_captions_placeholder), style = captionTextStyle, color = ExtendedTheme.colors.foregroundInactive, modifier = Modifier.padding(top = 4.dp), ) } } } } @Preview @Composable private fun SongDetailsMainInput_Preview() { SunoTheme { SongDetailsMainInput( songTitle = "Under Neon Skies", songCaptionInputValue = TextFieldValue(), songCaptionLimit = 500, onTitleFocused = {}, onCaptionFocused = {}, onTitleChanged = {}, onCaptionChanged = {}, onTitleUnfocused = {}, onCaptionUnfocused = {}, ) } }