[feat] New video UI& Basic Search feature

This commit is contained in:
acite
2025-09-16 18:23:45 +08:00
parent 829804abee
commit 2260f26d9a
4 changed files with 210 additions and 30 deletions

View File

@@ -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

View File

@@ -167,9 +167,7 @@ fun AppNavigation() {
}
}
composable(Screen.Video.route) {
CardPage(title = "Videos") {
VideoScreen(navController = navController)
}
VideoScreen(navController = navController)
}
composable(Screen.Comic.route) {
CardPage(title = "Comic") {

View File

@@ -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,32 +109,176 @@ 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
Column(
modifier = Modifier.fillMaxSize()
) {
TopRow(videoScreenViewModel);
LazyVerticalGrid(
columns = GridCells.Adaptive(160.dp),
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
)
CardPage(title = "Videos") {
Box(Modifier.fillMaxSize())
{
if (videoScreenViewModel.videoLibrary.classes.isNotEmpty()) {
items(
videoScreenViewModel.videoLibrary.classesMap[videoScreenViewModel.videoLibrary.classes[tabIndex]]
?: mutableStateListOf()
) { video ->
VideoCard(video, navController, videoScreenViewModel)
Column(
modifier = Modifier.fillMaxSize()
) {
// 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),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
)
{
items(
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)
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopRow(videoScreenViewModel: VideoScreenViewModel) {
@@ -200,10 +377,12 @@ fun VideoCard(
)
if (video.isLocal)
Card(Modifier
.align(Alignment.TopStart)
.padding(5.dp)
.widthIn(max = 46.dp)) {
Card(
Modifier
.align(Alignment.TopStart)
.padding(5.dp)
.widthIn(max = 46.dp)
) {
Box(Modifier.fillMaxWidth())
{
Text(

View File

@@ -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()