Created
October 15, 2025 15:39
-
-
Save stevdza-san/c812ba5ccebbcbc6aac5ba15fac1d297 to your computer and use it in GitHub Desktop.
Homework - REFACTORED
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.compose.animation.AnimatedContent | |
import androidx.compose.animation.AnimatedVisibility | |
import androidx.compose.animation.core.tween | |
import androidx.compose.animation.fadeIn | |
import androidx.compose.animation.fadeOut | |
import androidx.compose.animation.scaleIn | |
import androidx.compose.animation.scaleOut | |
import androidx.compose.animation.slideInVertically | |
import androidx.compose.animation.slideOutVertically | |
import androidx.compose.animation.togetherWith | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.clickable | |
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.Spacer | |
import androidx.compose.foundation.layout.fillMaxHeight | |
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.layout.safeDrawingPadding | |
import androidx.compose.foundation.layout.size | |
import androidx.compose.foundation.layout.width | |
import androidx.compose.foundation.lazy.LazyColumn | |
import androidx.compose.foundation.lazy.LazyRow | |
import androidx.compose.foundation.lazy.items | |
import androidx.compose.foundation.shape.CircleShape | |
import androidx.compose.foundation.shape.RoundedCornerShape | |
import androidx.compose.material3.Card | |
import androidx.compose.material3.CardDefaults | |
import androidx.compose.material3.FilterChip | |
import androidx.compose.material3.MaterialTheme | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.derivedStateOf | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.layout.ContentScale | |
import androidx.compose.ui.text.font.FontWeight | |
import androidx.compose.ui.text.style.TextAlign | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
import androidx.lifecycle.compose.collectAsStateWithLifecycle | |
import androidx.lifecycle.viewmodel.compose.viewModel | |
import com.stevdza_san.demo.homework.compose.viewmodel.heroImages | |
import com.stevdza_san.demo.homework.compose_new.repository.Channel | |
import com.stevdza_san.demo.homework.compose_new.repository.ChannelCategory | |
import com.stevdza_san.demo.homework.compose_new.repository.CurrentShow | |
import com.stevdza_san.demo.homework.compose_new.repository.Program | |
import com.stevdza_san.demo.homework.compose_new.viewmodel.TvViewModelNew | |
import org.jetbrains.compose.resources.DrawableResource | |
import org.jetbrains.compose.resources.painterResource | |
@Composable | |
fun ChannelScreen() { | |
val viewModel: TvViewModelNew = viewModel() | |
val currentImageIndex by viewModel.currentImageIndex.collectAsStateWithLifecycle(initialValue = 0) | |
val currentImage by remember { derivedStateOf { heroImages[currentImageIndex] } } | |
val channels by viewModel.channels.collectAsStateWithLifecycle() | |
val filteredPrograms by viewModel.filteredPrograms.collectAsStateWithLifecycle() | |
val selectedChannel by viewModel.selectedChannel.collectAsStateWithLifecycle() | |
val channelCategories by viewModel.channelCategories.collectAsStateWithLifecycle() | |
val selectedChannelCategory by viewModel.selectedChannelCategory.collectAsStateWithLifecycle() | |
val overlayVisibility by viewModel.overlayVisibility.collectAsStateWithLifecycle() | |
val currentShow by viewModel.currentShow.collectAsStateWithLifecycle() | |
Box( | |
modifier = Modifier | |
.fillMaxSize() | |
.safeDrawingPadding() | |
.background(Color.White), | |
contentAlignment = Alignment.BottomCenter | |
) { | |
Column( | |
modifier = Modifier.fillMaxSize() | |
) { | |
// Hero Banner | |
HeroBanner( | |
currentImage = currentImage, | |
modifier = Modifier | |
.fillMaxWidth() | |
.height(200.dp) | |
) | |
Spacer(modifier = Modifier.height(16.dp)) | |
Categories( | |
categories = channelCategories, | |
selected = selectedChannelCategory, | |
onCategoryClick = viewModel::selectCategory | |
) | |
Spacer(modifier = Modifier.height(16.dp)) | |
// Content Lists | |
Row( | |
modifier = Modifier | |
.fillMaxWidth() | |
.weight(1f) | |
.padding(horizontal = 16.dp) | |
) { | |
// Channel List | |
ChannelList( | |
channels = selectedChannelCategory?.let { category -> | |
channels.filter { it.categoryId == category.id } | |
} ?: channels, | |
selectedChannel = selectedChannel, | |
onChannelClick = viewModel::selectChannel, | |
modifier = Modifier | |
.weight(1f) | |
.fillMaxHeight() | |
) | |
Spacer(modifier = Modifier.width(16.dp)) | |
// Program List | |
ProgramList( | |
modifier = Modifier | |
.weight(1f) | |
.fillMaxHeight(), | |
programs = filteredPrograms, | |
onClick = viewModel::selectProgram | |
) | |
} | |
} | |
// Current Show Overlay | |
AnimatedVisibility( | |
visible = overlayVisibility, | |
enter = CUSTOM_ENTER_ANIMATION, | |
exit = CUSTOM_EXIT_ANIMATION, | |
modifier = Modifier.align(Alignment.BottomCenter) | |
) { | |
currentShow?.let { CurrentShowOverlay(currentShow = it) } | |
} | |
} | |
} | |
@Composable | |
fun HeroBanner( | |
currentImage: DrawableResource, | |
modifier: Modifier = Modifier, | |
) { | |
Card( | |
modifier = modifier | |
.padding(horizontal = 16.dp), | |
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) | |
) { | |
Box( | |
modifier = Modifier.fillMaxSize() | |
) { | |
AnimatedContent( | |
targetState = currentImage, | |
transitionSpec = { | |
fadeIn(animationSpec = tween(800)) togetherWith | |
fadeOut(animationSpec = tween(800)) | |
} | |
) { image -> | |
Image( | |
painter = painterResource(image), | |
contentDescription = "Hero Banner", | |
modifier = Modifier.fillMaxSize(), | |
contentScale = ContentScale.Crop | |
) | |
} | |
} | |
} | |
} | |
@Composable | |
fun Categories( | |
categories: List<ChannelCategory>, | |
selected: ChannelCategory?, | |
onCategoryClick: (ChannelCategory) -> Unit, | |
) { | |
LazyRow( | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(all = 12.dp), | |
horizontalArrangement = Arrangement.spacedBy(8.dp) | |
) { | |
items( | |
items = categories, | |
key = { it.id } | |
) { | |
FilterChip( | |
selected = it == selected, | |
label = { Text(text = it.name) }, | |
onClick = { onCategoryClick(it) } | |
) | |
} | |
} | |
} | |
@Composable | |
fun ChannelList( | |
channels: List<Channel>, | |
selectedChannel: Channel?, | |
onChannelClick: (Channel) -> Unit, | |
modifier: Modifier = Modifier, | |
) { | |
Card( | |
modifier = modifier, | |
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) | |
) { | |
Column( | |
modifier = Modifier.fillMaxSize() | |
) { | |
Text( | |
text = "Channels", | |
fontSize = 18.sp, | |
fontWeight = FontWeight.Bold, | |
modifier = Modifier | |
.fillMaxWidth() | |
.background(MaterialTheme.colorScheme.primary) | |
.padding(16.dp), | |
color = MaterialTheme.colorScheme.onPrimary, | |
textAlign = TextAlign.Center | |
) | |
AnimatedContent( | |
targetState = channels, | |
transitionSpec = { | |
fadeIn(animationSpec = tween(300)) togetherWith | |
fadeOut(animationSpec = tween(300)) | |
} | |
) { channelsData -> | |
if (channelsData.isNotEmpty()) { | |
LazyColumn( | |
modifier = Modifier.fillMaxSize(), | |
contentPadding = PaddingValues(8.dp), | |
verticalArrangement = Arrangement.spacedBy(4.dp) | |
) { | |
items(channelsData) { channel -> | |
ChannelItem( | |
channel = channel, | |
isSelected = channel == selectedChannel, | |
onClick = { onChannelClick(channel) } | |
) | |
} | |
} | |
} else { | |
Box( | |
modifier = Modifier.fillMaxSize(), | |
contentAlignment = Alignment.Center | |
) { | |
Text( | |
text = "No channels available", | |
fontSize = 14.sp, | |
color = MaterialTheme.colorScheme.onSurface, | |
textAlign = TextAlign.Center | |
) | |
} | |
} | |
} | |
} | |
} | |
} | |
@Composable | |
fun ChannelItem( | |
channel: Channel, | |
isSelected: Boolean, | |
onClick: () -> Unit, | |
) { | |
Card( | |
modifier = Modifier | |
.fillMaxWidth() | |
.clickable { onClick() }, | |
colors = CardDefaults.cardColors( | |
containerColor = if (isSelected) { | |
MaterialTheme.colorScheme.primaryContainer | |
} else { | |
MaterialTheme.colorScheme.surface | |
} | |
), | |
elevation = CardDefaults.cardElevation( | |
defaultElevation = if (isSelected) 4.dp else 2.dp | |
) | |
) { | |
Text( | |
text = channel.name, | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(16.dp), | |
fontSize = 14.sp, | |
color = if (isSelected) { | |
MaterialTheme.colorScheme.onPrimaryContainer | |
} else { | |
MaterialTheme.colorScheme.onSurface | |
} | |
) | |
} | |
} | |
@Composable | |
fun ProgramList( | |
modifier: Modifier = Modifier, | |
programs: List<Program>, | |
onClick: (Program) -> Unit, | |
) { | |
Card( | |
modifier = modifier, | |
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) | |
) { | |
Column( | |
modifier = Modifier.fillMaxSize() | |
) { | |
Text( | |
text = "Programs", | |
fontSize = 18.sp, | |
fontWeight = FontWeight.Bold, | |
modifier = Modifier | |
.fillMaxWidth() | |
.background(MaterialTheme.colorScheme.secondary) | |
.padding(16.dp), | |
color = MaterialTheme.colorScheme.onSecondary, | |
textAlign = TextAlign.Center | |
) | |
AnimatedContent( | |
targetState = programs, | |
transitionSpec = { | |
fadeIn(animationSpec = tween(300)) togetherWith | |
fadeOut(animationSpec = tween(300)) | |
} | |
) { programList -> | |
if (programList.isNotEmpty()) { | |
LazyColumn( | |
modifier = Modifier.fillMaxSize(), | |
contentPadding = PaddingValues(8.dp), | |
verticalArrangement = Arrangement.spacedBy(4.dp) | |
) { | |
items( | |
items = programList, | |
key = { program -> program.id } | |
) { program -> | |
ProgramItem( | |
program = program, | |
onClick = onClick | |
) | |
} | |
} | |
} else { | |
Box( | |
modifier = Modifier.fillMaxSize(), | |
contentAlignment = Alignment.Center | |
) { | |
Text( | |
text = "Select a channel to view programs", | |
fontSize = 14.sp, | |
color = MaterialTheme.colorScheme.onSurface, | |
textAlign = TextAlign.Center | |
) | |
} | |
} | |
} | |
} | |
} | |
} | |
@Composable | |
fun ProgramItem( | |
program: Program, | |
onClick: (Program) -> Unit, | |
) { | |
Card( | |
modifier = Modifier | |
.fillMaxWidth() | |
.clickable { onClick(program) }, | |
colors = CardDefaults.cardColors( | |
containerColor = MaterialTheme.colorScheme.surface | |
), | |
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) | |
) { | |
Text( | |
text = program.name, | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(16.dp), | |
fontSize = 14.sp, | |
color = MaterialTheme.colorScheme.onSurface | |
) | |
} | |
} | |
@Composable | |
fun CurrentShowOverlay( | |
currentShow: CurrentShow, | |
) { | |
Card( | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(16.dp), | |
colors = CardDefaults.cardColors( | |
containerColor = Color.Black.copy(alpha = 0.85f) | |
), | |
shape = RoundedCornerShape(12.dp), | |
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) | |
) { | |
Column( | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(20.dp) | |
) { | |
// Show title | |
Text( | |
text = currentShow.title, | |
fontSize = 22.sp, | |
fontWeight = FontWeight.Bold, | |
color = Color.White, | |
modifier = Modifier.padding(bottom = 8.dp) | |
) | |
// Time slot | |
Row( | |
verticalAlignment = Alignment.CenterVertically, | |
modifier = Modifier.padding(bottom = 8.dp) | |
) { | |
Text( | |
text = "⏰ ", | |
fontSize = 16.sp, | |
color = Color.White.copy(alpha = 0.8f) | |
) | |
Text( | |
text = currentShow.timeSlot, | |
fontSize = 16.sp, | |
fontWeight = FontWeight.Medium, | |
color = Color.White.copy(alpha = 0.9f) | |
) | |
} | |
// Rating | |
Row( | |
verticalAlignment = Alignment.CenterVertically, | |
modifier = Modifier.padding(bottom = 12.dp) | |
) { | |
Text( | |
text = currentShow.rating, | |
fontSize = 16.sp, | |
fontWeight = FontWeight.Medium, | |
color = Color(0xFFFFD700) // Gold color for rating | |
) | |
} | |
// Description | |
Text( | |
text = currentShow.description, | |
fontSize = 14.sp, | |
color = Color.White.copy(alpha = 0.85f), | |
lineHeight = 20.sp, | |
textAlign = TextAlign.Justify | |
) | |
Spacer(modifier = Modifier.height(8.dp)) | |
// "Now Playing" indicator | |
Row( | |
verticalAlignment = Alignment.CenterVertically | |
) { | |
Box( | |
modifier = Modifier | |
.size(8.dp) | |
.background( | |
Color.Red, | |
CircleShape | |
) | |
) | |
Spacer(modifier = Modifier.width(8.dp)) | |
Text( | |
text = "NOW PLAYING", | |
fontSize = 12.sp, | |
fontWeight = FontWeight.Bold, | |
color = Color.Red, | |
letterSpacing = 1.sp | |
) | |
} | |
} | |
} | |
} | |
val CUSTOM_ENTER_ANIMATION = slideInVertically( | |
animationSpec = tween(400), | |
initialOffsetY = { it } | |
) + scaleIn( | |
animationSpec = tween(400), | |
initialScale = 0.8f | |
) + fadeIn( | |
animationSpec = tween(300) | |
) | |
val CUSTOM_EXIT_ANIMATION = slideOutVertically( | |
animationSpec = tween(300), | |
targetOffsetY = { it } | |
) + scaleOut( | |
animationSpec = tween(300), | |
targetScale = 0.9f | |
) + fadeOut( | |
animationSpec = tween(200) | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kotlinx.coroutines.flow.Flow | |
import kotlinx.coroutines.flow.flow | |
import kotlinx.coroutines.delay | |
import kotlin.collections.listOf | |
data class Channel( | |
val id: String, | |
val name: String, | |
val categoryId: Int? = null | |
) | |
data class ChannelCategory( | |
val id: Int, | |
val name: String | |
) | |
data class CurrentShow( | |
val title: String, | |
val description: String, | |
val timeSlot: String, | |
val rating: String, | |
) | |
data class Program( | |
val id: String, | |
val channelId: String, | |
val name: String, | |
) | |
class FakeTvRepository { | |
/** | |
* Returns a Flow that continuously emits shuffled channel lists every 10 seconds. | |
* | |
* This function simulates dynamic channel data by: | |
* 1. Initially emitting the original list of channels | |
* 2. Every 10 seconds, moving the last channel to the first position | |
* 3. Emitting the newly shuffled list | |
* 4. Repeating this rotation indefinitely | |
* | |
* Example sequence for channels [A, B, C, D]: | |
* - Initial: [A, B, C, D] | |
* - After 10s: [D, A, B, C] | |
* - After 20s: [C, D, A, B] | |
* - After 30s: [B, C, D, A] | |
* - After 40s: [A, B, C, D] (back to original) | |
* | |
* This creates a continuous stream of data changes to test UI reactivity | |
* and simulate real-world scenarios where channel lists might update periodically. | |
* | |
* @return Flow<List<Channel>> A cold flow that emits shuffled channel lists every 10 seconds | |
*/ | |
fun getChannels(): Flow<List<Channel>> = flow { | |
var channels = fetchChannels() | |
emit(channels) | |
while (true) { | |
delay(10_000) // 10 seconds | |
if (channels.isNotEmpty()) { | |
// Move the last element to the first position | |
channels = listOf(channels.last()) + channels.dropLast(1) | |
emit(channels) | |
} | |
} | |
} | |
fun getPrograms(): Flow<List<Program>> = flow { | |
emit(fetchPrograms()) | |
} | |
fun getCurrentShows(): Flow<List<CurrentShow>> = flow { | |
emit(fetchCurrentShowsList()) | |
} | |
fun getChannelCategories(): Flow<List<ChannelCategory>> = flow { | |
emit(fetchChannelCategories()) | |
} | |
private fun fetchChannels(): List<Channel> { | |
return (1..10).map { id -> | |
Channel( | |
id = "$id", | |
name = "Channel $id", | |
categoryId = listOf(1, 2, 3).random() | |
) | |
} | |
} | |
private fun fetchPrograms(): List<Program> { | |
val programNames = listOf( | |
"Morning News", "Weather Report", "Talk Show", "Comedy Hour", "Drama Series", | |
"Documentary", "Sports Update", "Cooking Show", "Reality TV", "Game Show", | |
"Movie Night", "Late Show", "Kids Program", "Educational", "Music Video", | |
"Travel Guide", "Science Today", "History Channel", "Nature Watch", "Tech Review" | |
) | |
return (1..20).flatMap { channelId -> | |
programNames.shuffled().mapIndexed { index, baseName -> | |
Program( | |
id = "${baseName}_${channelId}_${index}", | |
channelId = "$channelId", | |
name = "$baseName ${(1..99).random()}" | |
) | |
} | |
} | |
} | |
private fun fetchCurrentShowsList(): List<CurrentShow> { | |
return listOf( | |
CurrentShow( | |
"The Crown", | |
"A biographical drama about Queen Elizabeth II's reign, exploring personal relationships and political events.", | |
"8:00 PM - 9:00 PM", | |
"★★★★☆ 4.2/5" | |
), | |
CurrentShow( | |
"Stranger Things", | |
"Supernatural horror series set in the 1980s following kids in a small town dealing with mysterious events.", | |
"9:00 PM - 10:00 PM", | |
"★★★★★ 4.8/5" | |
), | |
CurrentShow( | |
"Breaking Bad", | |
"A high school chemistry teacher turned methamphetamine manufacturer in this critically acclaimed crime drama.", | |
"10:00 PM - 11:00 PM", | |
"★★★★★ 4.9/5" | |
), | |
CurrentShow( | |
"The Office", | |
"Mockumentary sitcom depicting the everyday work lives of office employees in Scranton, Pennsylvania.", | |
"7:30 PM - 8:00 PM", | |
"★★★★☆ 4.4/5" | |
), | |
CurrentShow( | |
"Game of Thrones", | |
"Epic fantasy series featuring political intrigue, dragons, and the battle for the Iron Throne.", | |
"9:00 PM - 10:00 PM", | |
"★★★★☆ 4.1/5" | |
), | |
CurrentShow( | |
"Friends", | |
"Classic sitcom following six friends living in Manhattan navigating life, love, and careers.", | |
"8:00 PM - 8:30 PM", | |
"★★★★☆ 4.5/5" | |
), | |
CurrentShow( | |
"The Mandalorian", | |
"Star Wars series following a bounty hunter's adventures in the outer reaches of the galaxy.", | |
"8:00 PM - 9:00 PM", | |
"★★★★☆ 4.3/5" | |
), | |
CurrentShow( | |
"Squid Game", | |
"Korean survival thriller where contestants play deadly children's games for a massive cash prize.", | |
"9:00 PM - 10:00 PM", | |
"★★★★☆ 4.2/5" | |
), | |
CurrentShow( | |
"The Witcher", | |
"Fantasy series following Geralt of Rivia, a monster hunter in a world full of magic and political intrigue.", | |
"8:00 PM - 9:00 PM", | |
"★★★★☆ 4.0/5" | |
), | |
CurrentShow( | |
"Wednesday", | |
"Dark comedy following Wednesday Addams as she navigates her years as a student at Nevermore Academy.", | |
"7:00 PM - 8:00 PM", | |
"★★★★☆ 4.1/5" | |
) | |
) | |
} | |
private fun fetchChannelCategories(): List<ChannelCategory> { | |
return listOf( | |
ChannelCategory(1, "Sport"), | |
ChannelCategory(2, "News"), | |
ChannelCategory(3, "Entertainment") | |
) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.lifecycle.ViewModel | |
import androidx.lifecycle.viewModelScope | |
import com.stevdza_san.demo.homework.compose_new.repository.Channel | |
import com.stevdza_san.demo.homework.compose_new.repository.ChannelCategory | |
import com.stevdza_san.demo.homework.compose_new.repository.FakeTvRepository | |
import com.stevdza_san.demo.homework.compose_new.repository.Program | |
import demo.composeapp.generated.resources.Res | |
import demo.composeapp.generated.resources.hero1 | |
import demo.composeapp.generated.resources.hero2 | |
import demo.composeapp.generated.resources.hero3 | |
import kotlinx.coroutines.ExperimentalCoroutinesApi | |
import kotlinx.coroutines.FlowPreview | |
import kotlinx.coroutines.delay | |
import kotlinx.coroutines.flow.* | |
import kotlin.collections.filter | |
val heroImages = listOf( | |
Res.drawable.hero1, | |
Res.drawable.hero2, | |
Res.drawable.hero3 | |
) | |
class TvViewModelNew : ViewModel() { | |
private val repository = FakeTvRepository() | |
val selectedChannel: StateFlow<Channel?> | |
field = MutableStateFlow(null) | |
val selectedChannelCategory: StateFlow<ChannelCategory?> | |
field = MutableStateFlow(null) | |
private val selectedProgram: StateFlow<Program?> | |
field = MutableStateFlow(null) | |
val currentImageIndex = flow { | |
var index = 0 | |
while (true) { | |
emit(index) | |
delay(5000) | |
index = (index + 1) % heroImages.size | |
} | |
}.stateIn( | |
scope = viewModelScope, | |
started = SharingStarted.WhileSubscribed(5000), | |
initialValue = 0 | |
) | |
val channels = repository.getChannels() | |
.stateIn( | |
scope = viewModelScope, | |
started = SharingStarted.WhileSubscribed(5000), | |
initialValue = emptyList() | |
) | |
val channelCategories = repository.getChannelCategories() | |
.stateIn( | |
scope = viewModelScope, | |
started = SharingStarted.WhileSubscribed(5000), | |
initialValue = emptyList() | |
) | |
private val programs = repository.getPrograms() | |
.stateIn( | |
scope = viewModelScope, | |
started = SharingStarted.WhileSubscribed(5000), | |
initialValue = emptyList() | |
) | |
// Public: Programs filtered by selected channel | |
@OptIn(ExperimentalCoroutinesApi::class) | |
val filteredPrograms = combine(selectedChannel, programs) { channel, allPrograms -> | |
channel?.let { | |
allPrograms.filter { it.channelId == channel.id } | |
} ?: emptyList() | |
}.stateIn( | |
scope = viewModelScope, | |
started = SharingStarted.WhileSubscribed(5000), | |
initialValue = emptyList() | |
) | |
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) | |
val currentShow = selectedProgram | |
.filterNotNull() | |
.flatMapLatest { program -> | |
repository.getCurrentShows() | |
.map { shows -> shows.shuffled().firstOrNull() } | |
} | |
.stateIn( | |
scope = viewModelScope, | |
started = SharingStarted.WhileSubscribed(5000), | |
initialValue = null | |
) | |
@OptIn(ExperimentalCoroutinesApi::class) | |
val overlayVisibility = selectedProgram | |
.filterNotNull() | |
.flatMapLatest { programId -> | |
flow { | |
emit(true) // Show overlay | |
delay(3000) // Wait 3 seconds | |
emit(false) // Hide overlay | |
} | |
} | |
.stateIn( | |
scope = viewModelScope, | |
started = SharingStarted.WhileSubscribed(), | |
initialValue = false | |
) | |
fun selectChannel(channel: Channel) { | |
selectedChannel.value = channel | |
} | |
fun selectProgram(program: Program) { | |
selectedProgram.value = program | |
} | |
fun selectCategory(category: ChannelCategory) { | |
selectedChannelCategory.value = category | |
// Improve consistency | |
selectedChannel.value = null | |
selectedProgram.value = null | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment