Finished videos view
This commit is contained in:
parent
012fdd5b8e
commit
10836f538d
@ -58,6 +58,7 @@ dependencies {
|
|||||||
implementation 'androidx.compose.ui:ui'
|
implementation 'androidx.compose.ui:ui'
|
||||||
implementation 'androidx.compose.ui:ui-graphics'
|
implementation 'androidx.compose.ui:ui-graphics'
|
||||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||||
|
implementation 'androidx.navigation:navigation-compose:2.8.6'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
@ -82,5 +83,4 @@ dependencies {
|
|||||||
implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0"
|
implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0"
|
||||||
implementation "io.coil-kt.coil3:coil-compose:3.0.4"
|
implementation "io.coil-kt.coil3:coil-compose:3.0.4"
|
||||||
implementation "io.coil-kt.coil3:coil-network-okhttp:3.0.4"
|
implementation "io.coil-kt.coil3:coil-network-okhttp:3.0.4"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,54 @@
|
|||||||
package org.libre.agosto.p2play.activities
|
package org.libre.agosto.p2play.activities
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
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.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import org.libre.agosto.p2play.MainActivity
|
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.activities.ui.theme.P2playTheme
|
||||||
import org.libre.agosto.p2play.components.MainNavigationBar
|
import org.libre.agosto.p2play.ui.bars.MainNavigationBar
|
||||||
import org.libre.agosto.p2play.components.MainTopAppBar
|
import org.libre.agosto.p2play.ui.bars.MainTopAppBar
|
||||||
import org.libre.agosto.p2play.components.organisms.VideoItem
|
import org.libre.agosto.p2play.ui.bars.SearchTopBar
|
||||||
import org.libre.agosto.p2play.components.views.VideosView
|
import org.libre.agosto.p2play.ui.views.SearchView
|
||||||
import org.libre.agosto.p2play.models.VideoModel
|
import org.libre.agosto.p2play.ui.views.SubscriptionsView
|
||||||
|
import org.libre.agosto.p2play.ui.views.VideosView
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
|
val videoViewModel: VideosViewModel = viewModel()
|
||||||
|
val navController = rememberNavController()
|
||||||
|
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
|
||||||
P2playTheme {
|
P2playTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = { MainTopAppBar() },
|
topBar = {
|
||||||
bottomBar = { MainNavigationBar() }
|
when(currentRoute) {
|
||||||
|
Routes.Videos.route -> MainTopAppBar(navController)
|
||||||
|
Routes.Search.route -> SearchTopBar()
|
||||||
|
else -> MainTopAppBar(navController)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottomBar = { MainNavigationBar(navController) }
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
VideosView(
|
NavHost(navController, startDestination = Routes.Videos.route, modifier = Modifier.padding(innerPadding)) {
|
||||||
modifier = Modifier.padding(innerPadding)
|
composable(Routes.Videos.route) { VideosView(videoViewModel) }
|
||||||
)
|
composable(Routes.Subscriptions.route) { SubscriptionsView() }
|
||||||
|
composable(Routes.Search.route) { SearchView() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
package org.libre.agosto.p2play.components.views
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
||||||
|
}
|
7
app/src/main/java/org/libre/agosto/p2play/ui/Routes.kt
Normal file
7
app/src/main/java/org/libre/agosto/p2play/ui/Routes.kt
Normal 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")
|
||||||
|
}
|
@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package org.libre.agosto.p2play.components
|
package org.libre.agosto.p2play.ui.bars
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Notifications
|
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@ -20,25 +19,33 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
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.AboutActivity
|
||||||
import org.libre.agosto.p2play.ChannelActivity
|
|
||||||
import org.libre.agosto.p2play.LoginActivity
|
import org.libre.agosto.p2play.LoginActivity
|
||||||
import org.libre.agosto.p2play.ManagerSingleton
|
import org.libre.agosto.p2play.ManagerSingleton
|
||||||
import org.libre.agosto.p2play.R
|
import org.libre.agosto.p2play.R
|
||||||
|
import org.libre.agosto.p2play.ui.Routes
|
||||||
import org.libre.agosto.p2play.SettingsActivity2
|
import org.libre.agosto.p2play.SettingsActivity2
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Preview(showSystemUi = true)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainTopAppBar(modifier: Modifier = Modifier) {
|
fun MainTopAppBar(navController: NavController, modifier: Modifier = Modifier) {
|
||||||
var isMenuOpen by remember { mutableStateOf(false) }
|
var isMenuOpen by remember { mutableStateOf(false) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
{ Text(stringResource(R.string.nav_menu_videos)) },
|
{ Text(stringResource(R.string.nav_menu_videos)) },
|
||||||
modifier,
|
modifier,
|
||||||
actions = {
|
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({}) { Icon(Icons.Default.Notifications, "More") }
|
||||||
IconButton({ isMenuOpen = true }) {
|
IconButton({ isMenuOpen = true }) {
|
||||||
Icon(Icons.Default.MoreVert, "More")
|
Icon(Icons.Default.MoreVert, "More")
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 android.graphics.drawable.Icon
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
@ -1,4 +1,4 @@
|
|||||||
package org.libre.agosto.p2play.components.organisms
|
package org.libre.agosto.p2play.ui.components.organisms
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.foundation.background
|
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.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.layout.width
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.Text
|
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.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.constraintlayout.compose.ChainStyle
|
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import androidx.constraintlayout.compose.Dimension
|
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import org.libre.agosto.p2play.ChannelActivity
|
import org.libre.agosto.p2play.ChannelActivity
|
||||||
import org.libre.agosto.p2play.MainActivity
|
|
||||||
import org.libre.agosto.p2play.ManagerSingleton
|
import org.libre.agosto.p2play.ManagerSingleton
|
||||||
import org.libre.agosto.p2play.R
|
import org.libre.agosto.p2play.R
|
||||||
import org.libre.agosto.p2play.ReproductorActivity
|
import org.libre.agosto.p2play.ReproductorActivity
|
@ -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.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.snapshotFlow
|
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 org.libre.agosto.p2play.models.VideoModel
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -26,8 +25,7 @@ import org.libre.agosto.p2play.helpers.InfiniteScrollHandler
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoList (videos: List<VideoModel>, header: @Composable (() -> Unit)?, isLoading: Boolean = false, onRefresh: (() -> Unit)? = null, onLoadMore: (() -> Unit)? = null) {
|
fun VideoList (videos: ArrayList<VideoModel>, header: @Composable (() -> Unit)? = null, isLoading: Boolean = false, onRefresh: (() -> Unit)? = null, onLoadMore: (() -> Unit)? = null) {
|
||||||
val videoList by remember { derivedStateOf { videos } }
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
val lazyState = rememberLazyListState()
|
val lazyState = rememberLazyListState()
|
||||||
|
|
||||||
@ -43,7 +41,7 @@ fun VideoList (videos: List<VideoModel>, header: @Composable (() -> Unit)?, isLo
|
|||||||
if (header !== null) {
|
if (header !== null) {
|
||||||
item(key = "header") { header() }
|
item(key = "header") { header() }
|
||||||
}
|
}
|
||||||
items(videoList, key = { it.id }) {
|
items(videos, key = { it.id }) {
|
||||||
VideoItem(it)
|
VideoItem(it)
|
||||||
}
|
}
|
||||||
if (isLoading) {
|
if (isLoading) {
|
@ -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))
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user