mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Organize composables
This commit is contained in:
parent
5656703080
commit
efeecc4b98
3 changed files with 255 additions and 153 deletions
|
|
@ -0,0 +1,186 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.detail.livetv
|
||||||
|
|
||||||
|
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.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.tv.material3.Button
|
||||||
|
import androidx.tv.material3.Icon
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||||
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProgramDialog(
|
||||||
|
item: BaseItem?,
|
||||||
|
loading: LoadingState,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onWatch: () -> Unit,
|
||||||
|
onRecord: (series: Boolean) -> Unit,
|
||||||
|
onCancelRecord: (series: Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
) {
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
).focusRequester(focusRequester),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
) {
|
||||||
|
when (val st = loading) {
|
||||||
|
is LoadingState.Error -> ErrorMessage(st)
|
||||||
|
LoadingState.Loading,
|
||||||
|
LoadingState.Pending,
|
||||||
|
->
|
||||||
|
CircularProgress(
|
||||||
|
Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.size(48.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
LoadingState.Success ->
|
||||||
|
item?.let { item ->
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
val dto = item.data
|
||||||
|
val isRecording = dto.timerId.isNotNullOrBlank()
|
||||||
|
val isSeriesRecording = dto.seriesTimerId.isNotNullOrBlank()
|
||||||
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
|
Text(
|
||||||
|
text = item.name ?: "",
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
)
|
||||||
|
if (dto.isSeries ?: false) {
|
||||||
|
listOfNotNull(dto.seasonEpisode, dto.episodeTitle)
|
||||||
|
.joinToString(" - ")
|
||||||
|
.ifBlank { null }
|
||||||
|
?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dto.overview?.let { overview ->
|
||||||
|
Text(
|
||||||
|
text = overview,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (dto.isSeries ?: false) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (isSeriesRecording) {
|
||||||
|
onCancelRecord.invoke(true)
|
||||||
|
} else {
|
||||||
|
onRecord.invoke(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (isSeriesRecording) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.Red,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = if (isSeriesRecording) "Cancel Series Recording" else "Record Series",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (isRecording) {
|
||||||
|
onCancelRecord.invoke(false)
|
||||||
|
} else {
|
||||||
|
onRecord.invoke(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (isRecording) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.Red,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = if (isRecording) "Cancel Recording" else "Record Program",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (now.isAfter(dto.startDate!!) && now.isBefore(dto.endDate!!)) {
|
||||||
|
Button(
|
||||||
|
onClick = onWatch,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.PlayArrow,
|
||||||
|
contentDescription = "Delete",
|
||||||
|
tint = Color.Green.copy(alpha = .75f),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Watch live",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.detail.livetv
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
|
|
||||||
|
// .align(Alignment.BottomEnd)
|
||||||
|
@Composable
|
||||||
|
fun RecordingMarker(
|
||||||
|
isRecording: Boolean,
|
||||||
|
isSeriesRecording: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
if (isSeriesRecording) {
|
||||||
|
val color = if (isRecording) Color.Red else Color.Gray
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.size(16.dp)
|
||||||
|
.background(color, shape = CircleShape),
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(start = 4.dp, top = 4.dp, bottom = 4.dp, end = 10.dp)
|
||||||
|
.offset(6.dp)
|
||||||
|
.size(16.dp)
|
||||||
|
.background(color.copy(alpha = .5f), shape = CircleShape),
|
||||||
|
)
|
||||||
|
} else if (isRecording) {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.size(16.dp)
|
||||||
|
.background(Color.Red, shape = CircleShape),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewTvSpec
|
||||||
|
@Composable
|
||||||
|
private fun RecordingMarkerPreview() {
|
||||||
|
WholphinTheme {
|
||||||
|
Column {
|
||||||
|
RecordingMarker(true, false)
|
||||||
|
RecordingMarker(true, true)
|
||||||
|
RecordingMarker(false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,6 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -28,7 +26,6 @@ import androidx.compose.ui.focus.FocusDirection
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
|
|
@ -36,11 +33,8 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
import androidx.compose.ui.input.key.type
|
import androidx.compose.ui.input.key.type
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.tv.material3.Button
|
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Surface
|
import androidx.tv.material3.Surface
|
||||||
import androidx.tv.material3.SurfaceDefaults
|
import androidx.tv.material3.SurfaceDefaults
|
||||||
|
|
@ -48,18 +42,14 @@ import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.contentColorFor
|
import androidx.tv.material3.contentColorFor
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
|
||||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.enableMarquee
|
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.rememberInt
|
import com.github.damontecres.wholphin.ui.rememberInt
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.google.common.cache.CacheBuilder
|
||||||
import com.google.common.cache.CacheLoader
|
import com.google.common.cache.CacheLoader
|
||||||
import eu.wewox.programguide.ProgramGuide
|
import eu.wewox.programguide.ProgramGuide
|
||||||
|
|
@ -180,121 +170,6 @@ fun TvGuideGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ProgramDialog(
|
|
||||||
item: BaseItem?,
|
|
||||||
loading: LoadingState,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onWatch: () -> Unit,
|
|
||||||
onRecord: (series: Boolean) -> Unit,
|
|
||||||
onCancelRecord: (series: Boolean) -> Unit,
|
|
||||||
) {
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
) {
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
Box(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
).focusRequester(focusRequester),
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(16.dp),
|
|
||||||
) {
|
|
||||||
when (val st = loading) {
|
|
||||||
is LoadingState.Error -> ErrorMessage(st)
|
|
||||||
LoadingState.Loading,
|
|
||||||
LoadingState.Pending,
|
|
||||||
->
|
|
||||||
CircularProgress(
|
|
||||||
Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(48.dp),
|
|
||||||
)
|
|
||||||
|
|
||||||
LoadingState.Success ->
|
|
||||||
item?.let { item ->
|
|
||||||
val now = LocalDateTime.now()
|
|
||||||
val dto = item.data
|
|
||||||
val isRecording = dto.timerId.isNotNullOrBlank()
|
|
||||||
val isSeriesRecording = dto.seriesTimerId.isNotNullOrBlank()
|
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
|
||||||
Text(
|
|
||||||
text = item.name ?: "",
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
)
|
|
||||||
if (dto.isSeries ?: false) {
|
|
||||||
listOfNotNull(dto.seasonEpisode, dto.episodeTitle)
|
|
||||||
.joinToString(" - ")
|
|
||||||
.ifBlank { null }
|
|
||||||
?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dto.overview?.let { overview ->
|
|
||||||
Text(
|
|
||||||
text = overview,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 3,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (dto.isSeries ?: false) {
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
if (isSeriesRecording) {
|
|
||||||
onCancelRecord.invoke(true)
|
|
||||||
} else {
|
|
||||||
onRecord.invoke(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = if (isSeriesRecording) "Cancel Series Recording" else "Record Series",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
if (isRecording) {
|
|
||||||
onCancelRecord.invoke(false)
|
|
||||||
} else {
|
|
||||||
onRecord.invoke(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = if (isRecording) "Cancel Recording" else "Record Program",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (now.isAfter(dto.startDate!!) && now.isBefore(dto.endDate!!)) {
|
|
||||||
Button(
|
|
||||||
onClick = onWatch,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Watch live",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TvGuideGrid(
|
fun TvGuideGrid(
|
||||||
channels: List<TvChannel>,
|
channels: List<TvChannel>,
|
||||||
|
|
@ -579,34 +454,11 @@ fun TvGuideGrid(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (program.isSeriesRecording) {
|
RecordingMarker(
|
||||||
val color = if (program.isRecording) Color.Red else Color.Gray
|
isRecording = program.isRecording,
|
||||||
Box(
|
isSeriesRecording = program.isSeriesRecording,
|
||||||
modifier =
|
modifier = Modifier.align(Alignment.BottomEnd),
|
||||||
Modifier
|
)
|
||||||
.padding(4.dp)
|
|
||||||
.size(16.dp)
|
|
||||||
.background(color.copy(alpha = .5f), shape = CircleShape)
|
|
||||||
.align(Alignment.BottomEnd),
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(start = 4.dp, top = 4.dp, bottom = 4.dp, end = 10.dp)
|
|
||||||
.size(16.dp)
|
|
||||||
.background(color, shape = CircleShape)
|
|
||||||
.align(Alignment.BottomEnd),
|
|
||||||
)
|
|
||||||
} else if (program.isRecording) {
|
|
||||||
Box(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.size(16.dp)
|
|
||||||
.background(Color.Red, shape = CircleShape)
|
|
||||||
.align(Alignment.BottomEnd),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue