ui: Fix compile errors in first build
Composable API mismatches with tv-material 1.0.1: NavigationDrawerItem takes leadingContent/content (not icon/label), Card needs no shape. Move VideoListItem to top level so its supertype reference resolves. Pass onOpenVideo by name because the trailing lambda slot on VideoListScreen goes to the non-functional viewModel parameter. Nullable search param on local channel listings; make StateFlow collection use collectAsState directly to avoid a JVM signature clash. Verified by building app-debug.apk inside the container (JDK 21 installed there for this purpose).
This commit is contained in:
parent
59029438ad
commit
7eecfd72ac
8 changed files with 60 additions and 59 deletions
|
|
@ -72,7 +72,7 @@ interface PeerTubeApi {
|
||||||
|
|
||||||
@GET("video-channels")
|
@GET("video-channels")
|
||||||
suspend fun channels(
|
suspend fun channels(
|
||||||
@Query("search") search: String,
|
@Query("search") search: String?,
|
||||||
@Query("searchTarget") searchTarget: String = "local",
|
@Query("searchTarget") searchTarget: String = "local",
|
||||||
@Query("start") start: Long,
|
@Query("start") start: Long,
|
||||||
@Query("count") count: Int,
|
@Query("count") count: Int,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import net.jeena.jtubetv.data.api.PeerTubeApi
|
||||||
import net.jeena.jtubetv.data.api.TokenResponse
|
import net.jeena.jtubetv.data.api.TokenResponse
|
||||||
import net.jeena.jtubetv.data.auth.TokenStore
|
import net.jeena.jtubetv.data.auth.TokenStore
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.Authenticator
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ sealed interface SessionState {
|
||||||
|
|
||||||
class AppViewModel(private val repository: PeerTubeRepository = AppGraph.repository) : ViewModel() {
|
class AppViewModel(private val repository: PeerTubeRepository = AppGraph.repository) : ViewModel() {
|
||||||
|
|
||||||
val sessionState: StateFlow<SessionState> = _sessionState
|
|
||||||
private val _sessionState = MutableStateFlow<SessionState>(SessionState.Loading)
|
private val _sessionState = MutableStateFlow<SessionState>(SessionState.Loading)
|
||||||
|
val sessionState: StateFlow<SessionState> = _sessionState
|
||||||
|
|
||||||
val loginError = MutableStateFlow<String?>(null)
|
val loginError = MutableStateFlow<String?>(null)
|
||||||
val loggingIn = MutableStateFlow(false)
|
val loggingIn = MutableStateFlow(false)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ sealed interface ListSource {
|
||||||
data class Channel(val handle: String, val displayName: String) : ListSource
|
data class Channel(val handle: String, val displayName: String) : ListSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** One list row: a plain video, or a history entry (same video, server metadata). */
|
||||||
|
sealed interface VideoListItem {
|
||||||
|
data class Video(val video: net.jeena.jtubetv.data.api.VideoDto) : VideoListItem
|
||||||
|
data class HistoryEntry(val history: net.jeena.jtubetv.data.api.HistoryElement) : VideoListItem
|
||||||
|
}
|
||||||
|
|
||||||
class VideoListViewModel(
|
class VideoListViewModel(
|
||||||
private val repository: PeerTubeRepository,
|
private val repository: PeerTubeRepository,
|
||||||
val source: ListSource,
|
val source: ListSource,
|
||||||
|
|
@ -53,12 +59,6 @@ class VideoListViewModel(
|
||||||
if (items.isEmpty() && loadingInitial) loadMore(force = true)
|
if (items.isEmpty() && loadingInitial) loadMore(force = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** History/search entries can hide the underlying video shape. */
|
|
||||||
sealed interface VideoListItem {
|
|
||||||
data class Video(val video: net.jeena.jtubetv.data.api.VideoDto) : VideoListItem
|
|
||||||
data class HistoryEntry(val history: net.jeena.jtubetv.data.api.HistoryElement) : VideoListItem
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun factory(source: ListSource): ViewModelProvider.Factory =
|
fun factory(source: ListSource): ViewModelProvider.Factory =
|
||||||
object : ViewModelProvider.Factory {
|
object : ViewModelProvider.Factory {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
|
@ -119,8 +120,6 @@ private fun ChannelCard(channel: VideoChannelDto, onClick: () -> Unit) {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 6.dp),
|
.padding(horizontal = 16.dp, vertical = 6.dp),
|
||||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(12.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(12.dp)) {
|
||||||
val avatar = channel.avatars.maxByOrNull { it.width }
|
val avatar = channel.avatars.maxByOrNull { it.width }
|
||||||
|
|
|
||||||
|
|
@ -60,43 +60,9 @@ fun HomeScreen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationDrawer(drawerContent = {
|
NavigationDrawer(drawerContent = {
|
||||||
NavigationDrawerColumn(
|
androidx.compose.foundation.layout.Column(
|
||||||
selected = destination,
|
modifier = Modifier.width(220.dp).padding(vertical = 16.dp),
|
||||||
onSelect = {
|
|
||||||
destination = it
|
|
||||||
channelDetails = null
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}) {
|
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
when (destination) {
|
|
||||||
Destination.LocalVideos ->
|
|
||||||
VideoListScreen(ListSource.LocalVideos) { playingVideo = it.shortUUID }
|
|
||||||
Destination.MyVideos ->
|
|
||||||
VideoListScreen(ListSource.MyVideos) { playingVideo = it.shortUUID }
|
|
||||||
Destination.Channels -> ChannelsScreen(
|
|
||||||
onOpenChannel = { handle -> channelDetails = handle },
|
|
||||||
)
|
|
||||||
Destination.Search ->
|
|
||||||
VideoListScreen(ListSource.Search) { playingVideo = it.shortUUID }
|
|
||||||
Destination.Subscriptions ->
|
|
||||||
VideoListScreen(ListSource.Subscriptions) { playingVideo = it.shortUUID }
|
|
||||||
Destination.History ->
|
|
||||||
VideoListScreen(ListSource.History) { playingVideo = it.shortUUID }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun NavigationDrawerColumn(
|
|
||||||
selected: Destination,
|
|
||||||
onSelect: (Destination) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
) {
|
||||||
androidx.compose.foundation.layout.Column(modifier = modifier.width(220.dp).padding(vertical = 16.dp)) {
|
|
||||||
Text(
|
Text(
|
||||||
text = "JTube",
|
text = "JTube",
|
||||||
modifier = Modifier.padding(start = 28.dp, bottom = 16.dp),
|
modifier = Modifier.padding(start = 28.dp, bottom = 16.dp),
|
||||||
|
|
@ -104,13 +70,54 @@ private fun NavigationDrawerColumn(
|
||||||
)
|
)
|
||||||
Destination.entries.forEach { dest ->
|
Destination.entries.forEach { dest ->
|
||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
icon = { Icon(imageVector = dest.icon(), contentDescription = null) },
|
selected = dest == destination,
|
||||||
label = { Text(dest.title) },
|
onClick = {
|
||||||
selected = dest == selected,
|
destination = dest
|
||||||
onClick = { onSelect(dest) },
|
channelDetails = null
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(imageVector = dest.icon(), contentDescription = null)
|
||||||
|
},
|
||||||
|
content = { Text(dest.title) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
when (destination) {
|
||||||
|
Destination.LocalVideos ->
|
||||||
|
VideoListScreen(
|
||||||
|
source = ListSource.LocalVideos,
|
||||||
|
onOpenVideo = { playingVideo = it.shortUUID },
|
||||||
|
)
|
||||||
|
Destination.MyVideos ->
|
||||||
|
VideoListScreen(
|
||||||
|
source = ListSource.MyVideos,
|
||||||
|
onOpenVideo = { playingVideo = it.shortUUID },
|
||||||
|
)
|
||||||
|
Destination.Channels -> ChannelsScreen(
|
||||||
|
onOpenChannel = { handle -> channelDetails = handle },
|
||||||
|
)
|
||||||
|
Destination.Search ->
|
||||||
|
VideoListScreen(
|
||||||
|
source = ListSource.Search,
|
||||||
|
onOpenVideo = { playingVideo = it.shortUUID },
|
||||||
|
)
|
||||||
|
Destination.Subscriptions ->
|
||||||
|
VideoListScreen(
|
||||||
|
source = ListSource.Subscriptions,
|
||||||
|
onOpenVideo = { playingVideo = it.shortUUID },
|
||||||
|
)
|
||||||
|
Destination.History ->
|
||||||
|
VideoListScreen(
|
||||||
|
source = ListSource.History,
|
||||||
|
onOpenVideo = { playingVideo = it.shortUUID },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Destination.icon(): androidx.compose.ui.graphics.vector.ImageVector =
|
private fun Destination.icon(): androidx.compose.ui.graphics.vector.ImageVector =
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ fun SetupScreen(
|
||||||
val url = rememberSaveable { mutableStateOf("https://tube.jeena.net") }
|
val url = rememberSaveable { mutableStateOf("https://tube.jeena.net") }
|
||||||
val username = rememberSaveable { mutableStateOf("") }
|
val username = rememberSaveable { mutableStateOf("") }
|
||||||
val password = rememberSaveable { mutableStateOf("") }
|
val password = rememberSaveable { mutableStateOf("") }
|
||||||
val busy = loggingIn.collectAsStateFlow()
|
val busy = loggingIn.collectAsState()
|
||||||
val message = error.collectAsStateFlow()
|
val message = error.collectAsState()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -92,11 +92,3 @@ fun SetupScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun StateFlow<Boolean>.collectAsStateFlow(): State<Boolean> =
|
|
||||||
collectAsState()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun StateFlow<String?>.collectAsStateFlow(): State<String?> =
|
|
||||||
collectAsState()
|
|
||||||
|
|
@ -27,6 +27,7 @@ import net.jeena.jtubetv.R
|
||||||
import net.jeena.jtubetv.data.api.VideoDto
|
import net.jeena.jtubetv.data.api.VideoDto
|
||||||
import net.jeena.jtubetv.ui.list.PagedListViewModel
|
import net.jeena.jtubetv.ui.list.PagedListViewModel
|
||||||
import net.jeena.jtubetv.ui.list.ListSource
|
import net.jeena.jtubetv.ui.list.ListSource
|
||||||
|
import net.jeena.jtubetv.ui.list.VideoListItem
|
||||||
import net.jeena.jtubetv.ui.list.VideoListViewModel
|
import net.jeena.jtubetv.ui.list.VideoListViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -82,10 +83,11 @@ fun VideoListScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(vertical = 8.dp)) {
|
LazyColumn(modifier = Modifier.fillMaxSize().padding(vertical = 8.dp)) {
|
||||||
itemsIndexed(items) { index, item ->
|
itemsIndexed<VideoListItem>(items) { index, item ->
|
||||||
val video = when (item) {
|
val video: VideoDto = when (item) {
|
||||||
is VideoListViewModel.VideoListItem.HistoryEntry -> item.history.video
|
is VideoListItem.HistoryEntry -> item.history.video
|
||||||
is VideoListViewModel.VideoListItem.Video -> item.video
|
is VideoListItem.Video -> item.video
|
||||||
|
else -> return@itemsIndexed
|
||||||
}
|
}
|
||||||
VideoCard(
|
VideoCard(
|
||||||
video = video,
|
video = video,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue