[feat] New video UI& Basic Search feature
This commit is contained in:
10
README.md
10
README.md
@@ -24,14 +24,14 @@ _🚀This is the client of the multimedia server Abyss, which can also be extend
|
||||
- [x] Optimize API call logic, do not create crashes
|
||||
- [x] Fix the issue of freezing when entering the client without configuring the private key
|
||||
- [x] Replace Android robot icon with custom design
|
||||
- [ ] Configure server baseURL in client settings
|
||||
- [x] Configure server baseURL in client settings
|
||||
- [ ] Implement proper access control for directory queries
|
||||
|
||||
### Medium Priority
|
||||
- [ ] Increase minHeight for video playback
|
||||
- [ ] Add top bar with title and back button in full-screen mode
|
||||
- [ ] Optimize data transfer system
|
||||
- [ ] Improve manga/comic page display
|
||||
- [x] Increase minHeight for video playback
|
||||
- [x] Add top bar with title and back button in full-screen mode
|
||||
- [x] Optimize data transfer system
|
||||
- [x] Improve manga/comic page display
|
||||
|
||||
### Future
|
||||
- [ ] (Prospective) Implement search functionality
|
||||
@@ -167,10 +167,8 @@ fun AppNavigation() {
|
||||
}
|
||||
}
|
||||
composable(Screen.Video.route) {
|
||||
CardPage(title = "Videos") {
|
||||
VideoScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
composable(Screen.Comic.route) {
|
||||
CardPage(title = "Comic") {
|
||||
ComicScreen(navController = navController)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package com.acitelight.aether.view
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
@@ -10,19 +15,35 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CheckboxDefaults.colors
|
||||
import androidx.compose.material3.DividerDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
@@ -40,21 +61,33 @@ import coil3.compose.AsyncImage
|
||||
import com.acitelight.aether.model.Video
|
||||
import com.acitelight.aether.viewModel.VideoScreenViewModel
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.min
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavHostController
|
||||
import coil3.request.ImageRequest
|
||||
import com.acitelight.aether.CardPage
|
||||
import com.acitelight.aether.Global
|
||||
import com.acitelight.aether.Global.updateRelate
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.charset.Charset
|
||||
import java.security.KeyPair
|
||||
|
||||
fun String.toHex(): String {
|
||||
return this.toByteArray().joinToString("") { "%02x".format(it) }
|
||||
@@ -76,13 +109,90 @@ fun VideoScreen(
|
||||
videoScreenViewModel: VideoScreenViewModel = hiltViewModel<VideoScreenViewModel>(),
|
||||
navController: NavHostController
|
||||
) {
|
||||
val tabIndex by videoScreenViewModel.tabIndex;
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val tabIndex by videoScreenViewModel.tabIndex
|
||||
var menuVisibility by videoScreenViewModel.menuVisibility
|
||||
var searchFilter by videoScreenViewModel.searchFilter
|
||||
|
||||
CardPage(title = "Videos") {
|
||||
Box(Modifier.fillMaxSize())
|
||||
{
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TopRow(videoScreenViewModel);
|
||||
// TopRow(videoScreenViewModel);
|
||||
Row(Modifier.padding(bottom = 4.dp))
|
||||
{
|
||||
Card(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(horizontal = 2.dp)
|
||||
.size(36.dp),
|
||||
onClick = {
|
||||
menuVisibility = !menuVisibility
|
||||
})
|
||||
{
|
||||
Box(Modifier.fillMaxSize())
|
||||
{
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(30.dp)
|
||||
.align(Alignment.Center),
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = "Catalogue"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Card(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(horizontal = 2.dp)
|
||||
.height(36.dp),
|
||||
onClick = {
|
||||
menuVisibility = !menuVisibility
|
||||
})
|
||||
{
|
||||
Box(Modifier.fillMaxHeight())
|
||||
{
|
||||
Text(
|
||||
text = videoScreenViewModel.videoLibrary.classes.getOrNull(tabIndex)
|
||||
?: "",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.padding(horizontal = 8.dp),
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(36.dp).widthIn(max = 240.dp)
|
||||
.background(colorScheme.primary, RoundedCornerShape(8.dp))
|
||||
.padding(horizontal = 6.dp),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
BasicTextField(
|
||||
value = searchFilter,
|
||||
onValueChange = { searchFilter = it },
|
||||
textStyle = LocalTextStyle.current.copy(
|
||||
fontSize = 18.sp,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Start
|
||||
),
|
||||
singleLine = true,
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider(Modifier.padding(bottom = 8.dp), 1.5.dp, DividerDefaults.color)
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(160.dp),
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
@@ -90,15 +200,82 @@ fun VideoScreen(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
)
|
||||
{
|
||||
if (videoScreenViewModel.videoLibrary.classes.isNotEmpty()) {
|
||||
items(
|
||||
videoScreenViewModel.videoLibrary.classesMap[videoScreenViewModel.videoLibrary.classes[tabIndex]]
|
||||
?: mutableStateListOf()
|
||||
videoScreenViewModel.videoLibrary.classesMap.getOrDefault(
|
||||
videoScreenViewModel.videoLibrary.classes.getOrNull(
|
||||
tabIndex
|
||||
), listOf()
|
||||
).filter { it.video.name.contains(searchFilter) }
|
||||
) { video ->
|
||||
VideoCard(video, navController, videoScreenViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = menuVisibility,
|
||||
enter = slideInHorizontally(initialOffsetX = { full -> full }),
|
||||
exit = slideOutHorizontally(targetOffsetX = { full -> full }),
|
||||
modifier = Modifier.align(Alignment.CenterEnd)
|
||||
) {
|
||||
Card(
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.width(200.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = colorScheme.surface)
|
||||
)
|
||||
{
|
||||
LazyColumn {
|
||||
items(videoScreenViewModel.videoLibrary.classes) { item ->
|
||||
CatalogueItemRow(
|
||||
item = Pair(
|
||||
videoScreenViewModel.videoLibrary.classes.indexOf(item),
|
||||
item
|
||||
),
|
||||
onItemClick = {
|
||||
menuVisibility = false
|
||||
videoScreenViewModel.setTabIndex(
|
||||
videoScreenViewModel.videoLibrary.classes.indexOf(
|
||||
item
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CatalogueItemRow(
|
||||
item: Pair<Int, String>,
|
||||
onItemClick: (Pair<Int, String>) -> Unit
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.clickable { onItemClick(item) }
|
||||
.padding(4.dp)
|
||||
.padding(horizontal = 4.dp)
|
||||
.heightIn(min = 28.dp)
|
||||
.width(200.dp),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = colorScheme.primary)
|
||||
) {
|
||||
Text(
|
||||
text = item.second,
|
||||
fontSize = 18.sp,
|
||||
maxLines = 1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,10 +377,12 @@ fun VideoCard(
|
||||
)
|
||||
|
||||
if (video.isLocal)
|
||||
Card(Modifier
|
||||
Card(
|
||||
Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(5.dp)
|
||||
.widthIn(max = 46.dp)) {
|
||||
.widthIn(max = 46.dp)
|
||||
) {
|
||||
Box(Modifier.fillMaxWidth())
|
||||
{
|
||||
Text(
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -44,6 +45,8 @@ class VideoScreenViewModel @Inject constructor(
|
||||
private val _tabIndex = mutableIntStateOf(0)
|
||||
val tabIndex: State<Int> = _tabIndex
|
||||
var imageLoader: ImageLoader? = null;
|
||||
var menuVisibility = mutableStateOf(false)
|
||||
var searchFilter = mutableStateOf("")
|
||||
|
||||
suspend fun init() {
|
||||
fetchManager.configured.filter { it }.first()
|
||||
|
||||
Reference in New Issue
Block a user