diff --git a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt index 4efb9a0..a6f5aad 100644 --- a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt +++ b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt @@ -187,7 +187,8 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi upToTopBar = { runCatching { searchFocus.requestFocus() } }, ) Destination.Subscriptions -> - SubscriptionRowsScreen( + VideoListScreen( + source = ListSource.Subscriptions, onOpenVideo = { playingVideo = it.shortUUID }, restoreToken = restoreToken, epoch = epoch, diff --git a/app/src/main/java/net/jeena/jtubetv/ui/screens/SubscriptionsScreen.kt b/app/src/main/java/net/jeena/jtubetv/ui/screens/SubscriptionsScreen.kt deleted file mode 100644 index d6e7eff..0000000 --- a/app/src/main/java/net/jeena/jtubetv/ui/screens/SubscriptionsScreen.kt +++ /dev/null @@ -1,170 +0,0 @@ -package net.jeena.jtubetv.ui.screens - -import androidx.compose.foundation.background -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.Row -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.CircleShape -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.remember -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.compose.viewModel -import coil.compose.AsyncImage -import net.jeena.jtubetv.AppGraph -import net.jeena.jtubetv.data.api.Avatar -import net.jeena.jtubetv.data.api.VideoChannelSummary -import net.jeena.jtubetv.data.api.VideoDto -import net.jeena.jtubetv.ui.list.ImagePrefetcher -import net.jeena.jtubetv.ui.list.PagedListViewModel - -/** - * Subscriptions, SmartTube-style: the feed grouped by channel — one row per - * channel, newest-first within the row, row header = channel avatar + name. - * Card sizes match the Home rows exactly. - */ -class SubscriptionRowsViewModel( - private val repository: net.jeena.jtubetv.data.PeerTubeRepository = AppGraph.repository, -) : PagedListViewModel() { - - override fun onChunkLoaded(chunk: List) { - ImagePrefetcher.videoThumbnails(chunk) - } - - override suspend fun fetch(start: Long, count: Int): Result> = - repository.subscriptionVideos(start, count).map { it.data } -} - -private data class SubGroup( - val channel: VideoChannelSummary, - val startIndex: Int, - val videos: List, -) - -private fun buildGroups(items: List): List { - val groups = mutableListOf() - items.forEachIndexed { index, video -> - val last = groups.lastOrNull() - val same = last != null && - last.channel.name == video.channel.name && - last.channel.host == video.channel.host - if (same) { - groups[groups.lastIndex] = last!!.copy(videos = last.videos + video) - } else { - groups += SubGroup(video.channel, index, listOf(video)) - } - } - return groups -} - -@Composable -fun SubscriptionRowsScreen( - onOpenVideo: (VideoDto) -> Unit, - restoreToken: Int = 0, - epoch: Int = 0, - onOpenDrawer: () -> Unit = {}, - upToTopBar: (() -> Unit)? = null, - viewModel: SubscriptionRowsViewModel = viewModel( - key = "subsrows$epoch", - factory = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - SubscriptionRowsViewModel() as T - }, - ), -) { - val items = viewModel.items - LaunchedEffect(Unit) { if (items.isEmpty()) viewModel.loadMore(force = true) } - LaunchedEffect(restoreToken) { if (restoreToken > 0) viewModel.refresh() } - - if (items.isEmpty()) { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - if (viewModel.initialError != null) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text("Could not load subscriptions", color = MaterialTheme.colorScheme.error) - Text( - viewModel.initialError ?: "", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - androidx.compose.material3.Button(onClick = { viewModel.retryInitial() }) { - Text("Retry") - } - } - } else { - androidx.compose.material3.CircularProgressIndicator() - } - } - return - } - - val groups = remember(items) { buildGroups(items) } - - LazyColumn(Modifier.fillMaxSize()) { - groups.forEach { group -> - item(key = "subrowhead" + group.channel.name + "@" + group.channel.host + group.startIndex) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 4.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - ) { - val avatar = group.channel.avatars.maxByOrNull { it.width } - if (avatar != null) { - AsyncImage( - model = coil.request.ImageRequest.Builder( - androidx.compose.ui.platform.LocalContext.current, - ) - .data(avatar.fileUrl - ?: (AppGraph.tokenStore.baseUrl ?: "") + avatar.path) - .size(80) - .build(), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .size(28.dp) - .clip(CircleShape) - .background(Color(0xFF222222)), - ) - } - Text( - text = group.channel.displayName.ifBlank { group.channel.name }, - style = MaterialTheme.typography.titleMedium, - color = Color.White, - ) - } - } - item(key = "subrow" + group.channel.name + "@" + group.channel.host + group.startIndex) { - FocusScrollRow( - videos = group.videos, - progressOf = { null }, - onOpenVideo = onOpenVideo, - onLeftAtStart = onOpenDrawer, - upToTopBar = upToTopBar.takeIf { group == groups.firstOrNull() }, - ) { rowIndex, _ -> - if (group.startIndex + rowIndex >= items.size - PagedListViewModel.LOOKAHEAD) { - viewModel.loadMore() - } - } - } - } - } -} \ No newline at end of file