package com.suno.android.ui.screens.create.audio.precondition import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withLink import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.rememberPermissionState import com.suno.android.common_res.R import com.suno.android.common_ui.components.preview.SunoPreview import com.suno.android.common_ui.theme.ExtendedTheme import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @Composable fun AudioPreconditionContent( controller: AudioPreconditionController, modifier: Modifier = Modifier, ) { AudioPreconditionContent( viewState = controller.state.collectAsStateWithLifecycle().value, onEvent = controller::sendEvent, effects = controller.effects, modifier = modifier, ) } @Composable fun AudioPreconditionContent( viewState: AudioPreconditionState, onEvent: (AudioPreconditionEvent) -> Unit, effects: Flow, modifier: Modifier = Modifier, ) { AudioPreconditionEffectHandler( effects = effects, onRecordingPermissionGranted = { onEvent(AudioPreconditionEvent.OnRecordingPermissionAccepted) }, ) Column( verticalArrangement = Arrangement.spacedBy(8.dp), horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.padding( horizontal = 32.dp, vertical = 16.dp, ), ) { Text( text = viewState.flow.title(), style = ExtendedTheme.typography.largeSubtitle, textAlign = TextAlign.Center, ) viewState.preconditionItems.forEach { item -> key(item.precondition) { PreconditionItem( item = item, onTosClick = { onEvent(AudioPreconditionEvent.ShowTosDialog) }, onStartCompleting = { onEvent(AudioPreconditionEvent.OnAcceptPrecondition(item.precondition)) }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), ) } } } } @Composable private fun AudioPreconditionEffectHandler( effects: Flow, onRecordingPermissionGranted: () -> Unit, ) { val recordingPermission = rememberPermissionState( permission = android.Manifest.permission.RECORD_AUDIO, onPermissionResult = { granted -> if (granted) { onRecordingPermissionGranted() } }, ) LaunchedEffect(effects) { effects.collect { effect -> when (effect) { AudioPreconditionEffect.RequestRecordingPermission -> { recordingPermission.launchPermissionRequest() } } } } } @Composable fun PreconditionItem( item: AudioPreconditionState.PreconditionItem, onTosClick: () -> Unit, onStartCompleting: () -> Unit, modifier: Modifier = Modifier, ) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = modifier, ) { Text( text = item.precondition.label( onTosClick = onTosClick, ), style = ExtendedTheme.typography.body, modifier = Modifier.weight(1f), ) Switch( checked = item.isComplete, onCheckedChange = { if (it) { onStartCompleting() } }, colors = SwitchDefaults.colors( checkedTrackColor = ExtendedTheme.colors.sunoPink, ), ) } } @Composable fun AudioPreconditionController.Flow.title() = when (this) { AudioPreconditionController.Flow.Upload -> stringResource(R.string.upload_precondition_title) AudioPreconditionController.Flow.Record -> stringResource(R.string.recording_precondition_title) } @Composable fun AudioPreconditionController.Flow.subtitle() = when (this) { AudioPreconditionController.Flow.Upload -> stringResource(R.string.upload_precondition_subtitle) AudioPreconditionController.Flow.Record -> stringResource(R.string.recording_precondition_subtitle) } @Composable private fun AudioPreconditionState.Precondition.label( onTosClick: () -> Unit, ) = when (this) { AudioPreconditionState.Precondition.TermsOfService -> buildAnnotatedString { append(stringResource(R.string.agree_to)) append(" ") withLink( LinkAnnotation.Clickable( tag = "tos", styles = TextLinkStyles( style = SpanStyle(color = ExtendedTheme.colors.sunoPink), focusedStyle = SpanStyle(color = ExtendedTheme.colors.sunoPink.copy(alpha = .75f)), hoveredStyle = SpanStyle(color = ExtendedTheme.colors.sunoPink.copy(alpha = .75f)), pressedStyle = SpanStyle(color = ExtendedTheme.colors.sunoPink.copy(alpha = .5f)), ), ) { onTosClick() }, ) { append(stringResource(R.string.terms_and_conditions)) } } AudioPreconditionState.Precondition.RecordingPermission -> buildAnnotatedString { append(stringResource(R.string.allow_recording)) } } @PreviewLightDark @Composable private fun AudioPreconditionContent_Preview() { SunoPreview { AudioPreconditionContent( viewState = AudioPreconditionState( flow = AudioPreconditionController.Flow.Record, preconditions = persistentListOf( AudioPreconditionState.Precondition.TermsOfService, AudioPreconditionState.Precondition.RecordingPermission, ), preconditionStates = emptyMap(), ), onEvent = {}, effects = emptyFlow(), ) } }