Finished videos view

This commit is contained in:
Ivan Agosto 2025-02-06 22:53:02 -06:00
parent 012fdd5b8e
commit 10836f538d
17 changed files with 443 additions and 207 deletions

View File

@ -58,6 +58,7 @@ dependencies {
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.navigation:navigation-compose:2.8.6'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
@ -82,5 +83,4 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0"
implementation "io.coil-kt.coil3:coil-compose:3.0.4"
implementation "io.coil-kt.coil3:coil-network-okhttp:3.0.4"
}

View File

@ -1,41 +1,54 @@
package org.libre.agosto.p2play.activities
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import org.libre.agosto.p2play.MainActivity
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import org.libre.agosto.p2play.ui.Routes
import org.libre.agosto.p2play.viewModels.VideosViewModel
import org.libre.agosto.p2play.activities.ui.theme.P2playTheme
import org.libre.agosto.p2play.components.MainNavigationBar
import org.libre.agosto.p2play.components.MainTopAppBar
import org.libre.agosto.p2play.components.organisms.VideoItem
import org.libre.agosto.p2play.components.views.VideosView
import org.libre.agosto.p2play.models.VideoModel
import org.libre.agosto.p2play.ui.bars.MainNavigationBar
import org.libre.agosto.p2play.ui.bars.MainTopAppBar
import org.libre.agosto.p2play.ui.bars.SearchTopBar
import org.libre.agosto.p2play.ui.views.SearchView
import org.libre.agosto.p2play.ui.views.SubscriptionsView
import org.libre.agosto.p2play.ui.views.VideosView
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val videoViewModel: VideosViewModel = viewModel()
val navController = rememberNavController()
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
P2playTheme {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = { MainTopAppBar() },
bottomBar = { MainNavigationBar() }
topBar = {
when(currentRoute) {
Routes.Videos.route -> MainTopAppBar(navController)
Routes.Search.route -> SearchTopBar()
else -> MainTopAppBar(navController)
}
},
bottomBar = { MainNavigationBar(navController) }
) { innerPadding ->
VideosView(
modifier = Modifier.padding(innerPadding)
)
NavHost(navController, startDestination = Routes.Videos.route, modifier = Modifier.padding(innerPadding)) {
composable(Routes.Videos.route) { VideosView(videoViewModel) }
composable(Routes.Subscriptions.route) { SubscriptionsView() }
composable(Routes.Search.route) { SearchView() }
}
}
}
}

View File

@ -1,44 +0,0 @@
package org.libre.agosto.p2play.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import org.libre.agosto.p2play.R
@Composable
fun MainNavigationBar () {
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Filled.Home, "home") },
label = { Text(text = stringResource(R.string.nav_menu_videos), fontSize = 11.sp) },
selected = true,
onClick = {},
)
NavigationBarItem(
icon = { Icon(painterResource(R.drawable.ic_live_tv_black_24dp), "home") },
label = { Text(stringResource(R.string.nav_subscriptions), fontSize = 11.sp) },
selected = false,
onClick = {}
)
// NavigationBarItem(
// icon = { Icon(painterResource(R.drawable.ic_menu_slideshow), "home") },
// label = { Text(stringResource(R.string.playlists), fontSize = 11.sp) },
// selected = false,
// onClick = {}
// )
NavigationBarItem(
icon = { Icon(Icons.Filled.AccountCircle, "home") },
label = { Text(stringResource(R.string.you), fontSize = 11.sp) },
selected = false,
onClick = {}
)
}
}

View File

@ -1,2 +0,0 @@
package org.libre.agosto.p2play.components.views

View File

@ -1,123 +0,0 @@
package org.libre.agosto.p2play.components.views
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import kotlinx.coroutines.flow.distinctUntilChanged
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.components.lists.VideoList
import org.libre.agosto.p2play.components.molecules.ChipSelector
import org.libre.agosto.p2play.components.molecules.ChipValues
import org.libre.agosto.p2play.helpers.TaskManager
import org.libre.agosto.p2play.models.VideoModel
@SuppressLint("MutableCollectionMutableState")
@Composable
fun VideosView (modifier: Modifier) {
val chips = arrayListOf(
object : ChipValues<String> {
override val text = stringResource(R.string.nav_trending)
override val value = "trending"
override val icon = ImageVector.vectorResource(R.drawable.ic_trending_up_black_24dp)
},
object : ChipValues<String> {
override val text = stringResource(R.string.nav_likes)
override val value = "likes"
override val icon = ImageVector.vectorResource(R.drawable.ic_thumb_up_black_24dp)
},
object : ChipValues<String> {
override val text = stringResource(R.string.nav_popular)
override val value = "popular"
override val icon = Icons.Filled.Star
},
object : ChipValues<String> {
override val text = stringResource(R.string.nav_recent)
override val value = "recent"
override val icon = ImageVector.vectorResource(R.drawable.ic_add_circle_black_24dp)
},
object : ChipValues<String> {
override val text = stringResource(R.string.nav_local)
override val value = "local"
override val icon = ImageVector.vectorResource(R.drawable.ic_home_black_24dp)
}
)
var videoFilter by rememberSaveable { mutableStateOf("trending") }
val videos by rememberSaveable { mutableStateOf(mutableListOf<VideoModel>()) }
var isLoading by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(isLoading) {
if (isLoading) {
val task = TaskManager<List<VideoModel>>()
task.runTask(
{
val client = Videos()
when (videoFilter) {
"trending" -> {
client.getTrendingVideos(videos.size)
}
"popular" -> {
client.getPopularVideos(videos.size)
}
"likes" -> {
client.getMostLikedVideos(videos.size)
}
"recent" -> {
client.getLastVideos(videos.size)
}
"local" -> {
client.getLocalVideos(videos.size)
}
else -> {
videoFilter = "trending"
client.getTrendingVideos(videos.size)
}
}
},
{
videos.addAll(it)
isLoading = false
}
)
}
}
Column (modifier) {
VideoList(
videos,
header = {
ChipSelector(chips, videoFilter) {
videoFilter = it
videos.clear()
isLoading = true
}
},
isLoading = isLoading,
onRefresh = {
if (!isLoading) {
videos.clear()
isLoading = true
}
},
onLoadMore = {
if (!isLoading) {
isLoading = true
}
}
)
}
}

View File

@ -0,0 +1,10 @@
package org.libre.agosto.p2play.domain.data
enum class VideoFilterEnum(val filter: String) {
TRENDING("trending"),
HOT("hot"),
LOCAL("local"),
RECENT("recent"),
LIKES("likes"),
POPULAR("popular")
}

View File

@ -0,0 +1,7 @@
package org.libre.agosto.p2play.ui
sealed class Routes (val route: String) {
data object Videos: Routes("videos")
data object Subscriptions: Routes("subscriptions")
data object Search: Routes("search")
}

View File

@ -0,0 +1,80 @@
package org.libre.agosto.p2play.ui.bars
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.currentBackStackEntryAsState
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ui.Routes
@Composable
fun MainNavigationBar (navController: NavController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
currentDestination?.route
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Filled.Home, "home") },
label = { Text(text = stringResource(R.string.nav_menu_videos), fontSize = 11.sp) },
selected = currentDestination?.route == Routes.Videos.route,
onClick = {
navController.navigate(Routes.Videos.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
NavigationBarItem(
icon = { Icon(painterResource(R.drawable.ic_live_tv_black_24dp), "home") },
label = { Text(stringResource(R.string.nav_subscriptions), fontSize = 11.sp) },
selected = currentDestination?.route == Routes.Subscriptions.route,
onClick = {
navController.navigate(Routes.Subscriptions.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
// NavigationBarItem(
// icon = { Icon(painterResource(R.drawable.ic_menu_slideshow), "home") },
// label = { Text(stringResource(R.string.playlists), fontSize = 11.sp) },
// selected = false,
// onClick = {}
// )
NavigationBarItem(
icon = { Icon(Icons.Filled.AccountCircle, "home") },
label = { Text(stringResource(R.string.you), fontSize = 11.sp) },
selected = false,
onClick = {}
)
}
}

View File

@ -1,9 +1,8 @@
package org.libre.agosto.p2play.components
package org.libre.agosto.p2play.ui.bars
import android.content.Intent
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@ -20,25 +19,33 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import org.libre.agosto.p2play.AboutActivity
import org.libre.agosto.p2play.ChannelActivity
import org.libre.agosto.p2play.LoginActivity
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ui.Routes
import org.libre.agosto.p2play.SettingsActivity2
@OptIn(ExperimentalMaterial3Api::class)
@Preview(showSystemUi = true)
@Composable
fun MainTopAppBar(modifier: Modifier = Modifier) {
fun MainTopAppBar(navController: NavController, modifier: Modifier = Modifier) {
var isMenuOpen by remember { mutableStateOf(false) }
val context = LocalContext.current
TopAppBar(
{ Text(stringResource(R.string.nav_menu_videos)) },
modifier,
actions = {
IconButton({}) { Icon(Icons.Default.Search, "More") }
IconButton({
navController.navigate(Routes.Search.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}) { Icon(Icons.Default.Search, "More") }
// IconButton({}) { Icon(Icons.Default.Notifications, "More") }
IconButton({ isMenuOpen = true }) {
Icon(Icons.Default.MoreVert, "More")

View File

@ -0,0 +1,52 @@
package org.libre.agosto.p2play.ui.bars
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchTopBar () {
var searchText by remember { mutableStateOf("") }
var active by remember { mutableStateOf(true) }
SearchBar(
query = searchText,
onQueryChange = { searchText = it },
onSearch = { active = false },
active = active,
onActiveChange = { active = it },
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Buscar...") },
leadingIcon = {
IconButton (onClick = {}) {
Icon(Icons.Default.ArrowBack, contentDescription = "Volver")
}
},
trailingIcon = {
if (searchText.isNotEmpty()) {
IconButton(onClick = { searchText = "" }) {
Icon(Icons.Default.Close, contentDescription = "Limpiar")
}
}
}) {
if (searchText.isNotEmpty()) {
Text("Resultados para: $searchText")
} else {
Text("Ingresa un término de búsqueda")
}
}
}

View File

@ -1,4 +1,4 @@
package org.libre.agosto.p2play.components.molecules
package org.libre.agosto.p2play.ui.components.molecules
import android.graphics.drawable.Icon
import androidx.compose.foundation.horizontalScroll

View File

@ -1,4 +1,4 @@
package org.libre.agosto.p2play.components.organisms
package org.libre.agosto.p2play.ui.components.organisms
import android.content.Intent
import androidx.compose.foundation.background
@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Card
import androidx.compose.material3.Text
@ -20,18 +19,14 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ChainStyle
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import coil3.compose.AsyncImage
import org.libre.agosto.p2play.ChannelActivity
import org.libre.agosto.p2play.MainActivity
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ReproductorActivity

View File

@ -1,4 +1,4 @@
package org.libre.agosto.p2play.components.lists
package org.libre.agosto.p2play.ui.lists
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
@ -9,12 +9,11 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import org.libre.agosto.p2play.components.organisms.VideoItem
import org.libre.agosto.p2play.ui.components.organisms.VideoItem
import org.libre.agosto.p2play.models.VideoModel
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -26,8 +25,7 @@ import org.libre.agosto.p2play.helpers.InfiniteScrollHandler
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun VideoList (videos: List<VideoModel>, header: @Composable (() -> Unit)?, isLoading: Boolean = false, onRefresh: (() -> Unit)? = null, onLoadMore: (() -> Unit)? = null) {
val videoList by remember { derivedStateOf { videos } }
fun VideoList (videos: ArrayList<VideoModel>, header: @Composable (() -> Unit)? = null, isLoading: Boolean = false, onRefresh: (() -> Unit)? = null, onLoadMore: (() -> Unit)? = null) {
var isRefreshing by remember { mutableStateOf(false) }
val lazyState = rememberLazyListState()
@ -43,7 +41,7 @@ fun VideoList (videos: List<VideoModel>, header: @Composable (() -> Unit)?, isLo
if (header !== null) {
item(key = "header") { header() }
}
items(videoList, key = { it.id }) {
items(videos, key = { it.id }) {
VideoItem(it)
}
if (isLoading) {

View File

@ -0,0 +1,15 @@
package org.libre.agosto.p2play.ui.views
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun SearchView (modifier: Modifier = Modifier) {
Box(modifier = modifier.background(Color.Red).width(100.dp).height(100.dp))
}

View File

@ -0,0 +1,69 @@
package org.libre.agosto.p2play.ui.views
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import kotlinx.coroutines.flow.distinctUntilChanged
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.ui.lists.VideoList
import org.libre.agosto.p2play.ui.components.molecules.ChipSelector
import org.libre.agosto.p2play.ui.components.molecules.ChipValues
import org.libre.agosto.p2play.helpers.TaskManager
import org.libre.agosto.p2play.models.VideoModel
import java.util.ArrayList
@SuppressLint("MutableCollectionMutableState")
@Composable
fun SubscriptionsView (modifier: Modifier = Modifier) {
val videos by rememberSaveable { mutableStateOf(mutableListOf<VideoModel>()) }
var isLoading by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(isLoading) {
if (isLoading) {
val task = TaskManager<List<VideoModel>>()
task.runTask(
{
val client = Videos()
client.getTrendingVideos(videos.size)
},
{
videos.addAll(it)
isLoading = false
}
)
}
}
Column (modifier) {
VideoList(
ArrayList(videos),
isLoading = isLoading,
onRefresh = {
if (!isLoading) {
videos.clear()
isLoading = true
}
},
onLoadMore = {
if (!isLoading) {
isLoading = true
}
}
)
}
}

View File

@ -0,0 +1,83 @@
package org.libre.agosto.p2play.ui.views
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.viewModels.VideosViewModel
import org.libre.agosto.p2play.ui.lists.VideoList
import org.libre.agosto.p2play.ui.components.molecules.ChipSelector
import org.libre.agosto.p2play.ui.components.molecules.ChipValues
import org.libre.agosto.p2play.domain.data.VideoFilterEnum
import java.util.ArrayList
@SuppressLint("MutableCollectionMutableState")
@Composable
fun VideosView (videosViewModel: VideosViewModel,modifier: Modifier = Modifier) {
val chips = arrayListOf(
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_trending)
override val value = VideoFilterEnum.TRENDING
override val icon = ImageVector.vectorResource(R.drawable.ic_trending_up_black_24dp)
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_likes)
override val value = VideoFilterEnum.LIKES
override val icon = ImageVector.vectorResource(R.drawable.ic_thumb_up_black_24dp)
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_popular)
override val value = VideoFilterEnum.POPULAR
override val icon = Icons.Filled.Star
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_recent)
override val value = VideoFilterEnum.RECENT
override val icon = ImageVector.vectorResource(R.drawable.ic_add_circle_black_24dp)
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_local)
override val value = VideoFilterEnum.LOCAL
override val icon = ImageVector.vectorResource(R.drawable.ic_home_black_24dp)
}
)
val videoFilter by videosViewModel.videoFilter.observeAsState(initial = VideoFilterEnum.TRENDING)
val videos by videosViewModel.videos.observeAsState(initial = listOf())
val isLoading: Boolean by videosViewModel.isLoading.observeAsState(initial = false)
LaunchedEffect(Unit) {
if (videos.isEmpty()) {
videosViewModel.loadVideos()
}
}
Column (modifier) {
VideoList(
ArrayList(videos),
header = {
ChipSelector(chips, videoFilter) {
videosViewModel.changeCategory(it)
}
},
isLoading = isLoading,
onRefresh = {
videosViewModel.refresh()
},
onLoadMore = {
if (!isLoading) {
videosViewModel.loadVideos()
}
}
)
}
}

View File

@ -0,0 +1,76 @@
package org.libre.agosto.p2play.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.domain.data.VideoFilterEnum
import org.libre.agosto.p2play.models.VideoModel
class VideosViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val client = Videos()
private val _videos = MutableLiveData<List<VideoModel>>()
val videos: LiveData<List<VideoModel>> = _videos
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
private val _videoFilter = MutableLiveData<VideoFilterEnum>()
val videoFilter: LiveData<VideoFilterEnum> = _videoFilter
fun loadVideos() {
_isLoading.value = true
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
getVideoResource()
}
val data = if (videos.value !== null)
ArrayList(videos.value!!)
else
ArrayList()
data.addAll(result)
_videos.postValue(data)
_isLoading.value = false
}
}
fun changeCategory(category: VideoFilterEnum) {
_videoFilter.value = category
refresh()
}
fun refresh() {
_videos.value = arrayListOf()
loadVideos()
}
private fun getVideoResource(): ArrayList<VideoModel> {
val skip = videos.value?.size ?: 0
return when (videoFilter.value) {
VideoFilterEnum.TRENDING -> {
return client.getTrendingVideos(skip)
}
VideoFilterEnum.POPULAR -> {
client.getPopularVideos(skip)
}
VideoFilterEnum.LIKES -> {
client.getMostLikedVideos(skip)
}
VideoFilterEnum.RECENT -> {
client.getLastVideos(skip)
}
VideoFilterEnum.LOCAL -> {
client.getLocalVideos(skip)
}
else -> {
client.getTrendingVideos(skip)
}
}
}
}