package com.suno.android.ui.screens.following_clips import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.suno.android.common_core_utils.Id import com.suno.android.common_data.mappers.clips.SongListData import com.suno.android.common_ui.components.list_items.SongListItem import com.suno.android.common_ui.components.loading.SunoSpinner import com.suno.android.common_ui.components.omni.CircleIconButton import com.suno.android.common_ui.components.preview.SunoPreview import com.suno.android.common_ui.effects.OnEndOfListReachedEffect import com.suno.android.common_ui.extensions.xAsFormattedCount import com.suno.android.common_ui.models.text import com.suno.android.common_ui.theme.ExtendedTheme import com.suno.android.extensions.xAsUiModelBadge import com.suno.android.preview.FakeSongListData import kotlinx.collections.immutable.toImmutableList import com.suno.android.common_res.R as CommonResR import com.suno.android.common_ui.R as CommonUiR @Composable fun FollowingClipsScreen( vm: FollowingClipsVM = hiltViewModel(), onNavigationRequest: (FollowingClipsNavigationRequest) -> Unit, modifier: Modifier = Modifier, ) { val viewState by vm.state.collectAsStateWithLifecycle() FollowingClipsScreen( viewState = viewState, sendEvent = vm::sendEvent, onNavigationRequest = onNavigationRequest, modifier = modifier, ) } @Composable fun FollowingClipsScreen( viewState: FollowingClipsState, sendEvent: (FollowingClipsEvent) -> Unit, onNavigationRequest: (FollowingClipsNavigationRequest) -> Unit, modifier: Modifier = Modifier, ) { val listState = rememberLazyListState() OnEndOfListReachedEffect(listState) { sendEvent(FollowingClipsEvent.OnListBottomReached) } Scaffold( containerColor = ExtendedTheme.colors.backgroundPrimary, topBar = { TopAppBar( modifier = Modifier.padding(horizontal = 12.dp), title = {}, colors = TopAppBarDefaults.topAppBarColors( containerColor = Color.Transparent, ), navigationIcon = { CircleIconButton( vectorDrawableResId = CommonUiR.drawable.arrow_left, contentDescription = stringResource(CommonResR.string.back), onClick = { onNavigationRequest(FollowingClipsNavigationRequest.Back) }, ) }, ) }, modifier = modifier, ) { insetPadding -> LazyColumn( state = listState, contentPadding = insetPadding, modifier = Modifier.consumeWindowInsets(insetPadding), ) { item { FollowingClipsScreenHeader() } when (viewState.listState) { is FollowingClipsState.ListState.Error -> { item { Text(viewState.listState.error.text()) } } FollowingClipsState.ListState.Loading -> { item { Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth(), ) { SunoSpinner(Modifier.padding(32.dp)) } } } is FollowingClipsState.ListState.Loaded -> { items(viewState.listState.clips) { mySong: SongListData -> SongListItem( clipImageUrl = mySong.displayImageUrl, songTitle = mySong.title?.takeIf { it.isNotEmpty() } ?: stringResource(id = CommonResR.string.untitled), songTags = mySong.tags ?: stringResource(CommonResR.string.no_style), playCount = mySong.playCount?.xAsFormattedCount(), upvoteCount = mySong.upvoteCount?.xAsFormattedCount(), modelBadge = mySong.modelBadge?.xAsUiModelBadge(), onClick = { sendEvent(FollowingClipsEvent.OnSongSelected(song = mySong)) }, onUpgradeClicked = { onNavigationRequest(FollowingClipsNavigationRequest.Billing) }, onOverflowClick = null, isPreviewClip = mySong.isPreview, ) HorizontalDivider( thickness = 1.dp, ) } if (viewState.listState.isLoadingMore) { item { Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth(), ) { SunoSpinner(Modifier.padding(32.dp)) } } } } } item { Spacer(Modifier.height(200.dp)) } } } } @Composable private fun FollowingClipsScreenHeader( modifier: Modifier = Modifier, title: String = stringResource(CommonResR.string.following), ) { Column( modifier = modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.Bottom, ) { Text( modifier = Modifier.padding(bottom = 8.dp), text = title, style = ExtendedTheme.typography.largeHeadline, color = ExtendedTheme.colors.textPrimary, maxLines = 2, ) } } @PreviewLightDark @Composable private fun FollowingClipsScreen_Loading_Preview() { SunoPreview { FollowingClipsScreen( viewState = FollowingClipsState( listState = FollowingClipsState.ListState.Loading, ), sendEvent = { }, onNavigationRequest = { }, ) } } @PreviewLightDark @Composable @Suppress("MagicNumber") private fun FollowingClipsScreen_Loaded_Preview() { SunoPreview { FollowingClipsScreen( viewState = FollowingClipsState( listState = FollowingClipsState.ListState.Loaded( clips = List(15) { FakeSongListData.cat.copy(id = Id(it.toString())) }.toImmutableList(), ), ), sendEvent = { }, onNavigationRequest = { }, ) } }