[feat] Comic Tags
This commit is contained in:
@@ -5,5 +5,6 @@ data class ComicResponse(
|
||||
val page_count: Int,
|
||||
val bookmarks: List<BookMark>,
|
||||
val list: List<String>,
|
||||
val tags: List<String>,
|
||||
val author: String
|
||||
)
|
||||
@@ -56,7 +56,6 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
|
||||
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp))
|
||||
)
|
||||
{
|
||||
Column {
|
||||
Text(
|
||||
text = comic!!.comic.comic_name,
|
||||
fontSize = 18.sp,
|
||||
@@ -65,18 +64,36 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
|
||||
maxLines = 1,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.padding(horizontal = 16.dp).padding(top = 4.dp)
|
||||
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp))
|
||||
) {
|
||||
Text(
|
||||
text = comic!!.comic.author,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.Black,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.padding(4.dp).fillMaxWidth()
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(horizontal = 16.dp).padding(top = 4.dp)
|
||||
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp))
|
||||
) {
|
||||
Text(
|
||||
text = "Tags : ${comic!!.comic.tags.joinToString(", ")}",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.Black,
|
||||
maxLines = 5,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f).padding(top = 6.dp).clip(RoundedCornerShape(6.dp)))
|
||||
{
|
||||
items(comicGridViewModel.chapterList)
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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
|
||||
@@ -224,14 +225,14 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
|
||||
Card(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.wrapContentHeight()
|
||||
.padding(horizontal = 6.dp).padding(vertical = 6.dp),
|
||||
onClick = {
|
||||
pagerState.requestScrollToPage(page = r)
|
||||
}
|
||||
){
|
||||
Box(Modifier.fillMaxSize())
|
||||
Box()
|
||||
{
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
@@ -242,7 +243,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
|
||||
contentDescription = null,
|
||||
imageLoader = comicPageViewModel.imageLoader!!,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.fillMaxHeight()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.align(Alignment.Center),
|
||||
contentScale = ContentScale.Fit,
|
||||
@@ -260,7 +261,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically)
|
||||
modifier = Modifier.padding(2.dp).widthIn(max = 100.dp).align(Alignment.CenterVertically)
|
||||
)
|
||||
|
||||
Text(
|
||||
@@ -269,7 +270,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.padding(4.dp).fillMaxWidth().align(Alignment.CenterVertically)
|
||||
modifier = Modifier.padding(2.dp).align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.acitelight.aether.view
|
||||
|
||||
import android.nfc.Tag
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -12,13 +14,17 @@ 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.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -35,11 +41,16 @@ 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.TopAppBar
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
import androidx.compose.ui.modifier.modifierLocalOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.navigation.NavHostController
|
||||
import coil3.request.ImageRequest
|
||||
import com.acitelight.aether.Global
|
||||
@@ -48,9 +59,127 @@ import com.acitelight.aether.viewModel.ComicScreenViewModel
|
||||
import java.nio.charset.Charset
|
||||
|
||||
@Composable
|
||||
fun ComicScreen(navController: NavHostController, comicScreenViewModel: ComicScreenViewModel = viewModel())
|
||||
{
|
||||
fun VariableGrid(
|
||||
modifier: Modifier = Modifier,
|
||||
rowHeight: Dp,
|
||||
horizontalSpacing: Dp = 4.dp,
|
||||
verticalSpacing: Dp = 4.dp,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Layout(
|
||||
modifier = modifier
|
||||
.verticalScroll(scrollState), // ✅ 支持垂直滚动
|
||||
content = content
|
||||
) { measurables, constraints ->
|
||||
|
||||
val rowHeightPx = rowHeight.roundToPx()
|
||||
val hSpacePx = horizontalSpacing.roundToPx()
|
||||
val vSpacePx = verticalSpacing.roundToPx()
|
||||
|
||||
val placeables = measurables.map { measurable ->
|
||||
measurable.measure(
|
||||
constraints.copy(
|
||||
minWidth = 0,
|
||||
minHeight = rowHeightPx,
|
||||
maxHeight = rowHeightPx
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val rows = mutableListOf<List<Placeable>>()
|
||||
var currentRow = mutableListOf<Placeable>()
|
||||
var currentWidth = 0
|
||||
val maxWidth = constraints.maxWidth
|
||||
|
||||
for (placeable in placeables) {
|
||||
if (currentRow.isNotEmpty() && currentWidth + placeable.width + hSpacePx > maxWidth) {
|
||||
rows.add(currentRow)
|
||||
currentRow = mutableListOf()
|
||||
currentWidth = 0
|
||||
}
|
||||
currentRow.add(placeable)
|
||||
currentWidth += placeable.width + hSpacePx
|
||||
}
|
||||
if (currentRow.isNotEmpty()) {
|
||||
rows.add(currentRow)
|
||||
}
|
||||
|
||||
val layoutHeight = if (rows.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
rows.size * rowHeightPx + (rows.size - 1) * vSpacePx
|
||||
}
|
||||
|
||||
layout(
|
||||
width = constraints.maxWidth.coerceAtLeast(constraints.minWidth),
|
||||
height = layoutHeight.coerceAtLeast(constraints.minHeight)
|
||||
) {
|
||||
var y = 0
|
||||
for (row in rows) {
|
||||
var x = 0
|
||||
for (placeable in row) {
|
||||
placeable.placeRelative(x, y)
|
||||
x += placeable.width + hSpacePx
|
||||
}
|
||||
y += rowHeightPx + vSpacePx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun ComicScreen(
|
||||
navController: NavHostController,
|
||||
comicScreenViewModel: ComicScreenViewModel = viewModel()
|
||||
) {
|
||||
comicScreenViewModel.SetupClient()
|
||||
val included = comicScreenViewModel.included
|
||||
|
||||
Column {
|
||||
|
||||
VariableGrid(
|
||||
modifier = Modifier
|
||||
.heightIn(max = 120.dp)
|
||||
.padding(8.dp),
|
||||
rowHeight = 32.dp
|
||||
)
|
||||
{
|
||||
for (i in comicScreenViewModel.tags) {
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.background(
|
||||
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else Color.White.copy(
|
||||
alpha = 0.65f
|
||||
),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
.height(32.dp).widthIn(max = 72.dp)
|
||||
.clickable {
|
||||
if (included.contains(i))
|
||||
included.remove(i)
|
||||
else
|
||||
included.add(i)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = i,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.padding(2.dp)
|
||||
.align(Alignment.Center),
|
||||
color = Color.Black
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(thickness = 1.5.dp)
|
||||
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(128.dp),
|
||||
@@ -59,14 +188,22 @@ fun ComicScreen(navController: NavHostController, comicScreenViewModel: ComicScr
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
)
|
||||
{
|
||||
items(comicScreenViewModel.comics) { comic ->
|
||||
items(comicScreenViewModel.comics.filter { x ->
|
||||
included.all { y -> y in x.comic.tags } || included.isEmpty()
|
||||
})
|
||||
{ comic ->
|
||||
ComicCard(comic, navController, comicScreenViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewModel: ComicScreenViewModel) {
|
||||
fun ComicCard(
|
||||
comic: Comic,
|
||||
navController: NavHostController,
|
||||
comicScreenViewModel: ComicScreenViewModel
|
||||
) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
modifier = Modifier
|
||||
@@ -95,25 +232,30 @@ fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewMod
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(24.dp)
|
||||
.background( brush = Brush.verticalGradient(
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Black.copy(alpha = 0.45f)
|
||||
)
|
||||
))
|
||||
.align(Alignment.BottomCenter))
|
||||
)
|
||||
)
|
||||
.align(Alignment.BottomCenter)
|
||||
)
|
||||
{
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(2.dp),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(2.dp),
|
||||
fontSize = 12.sp,
|
||||
text = "${comic.comic.list.size} Pages",
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White)
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
@@ -121,7 +263,10 @@ fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewMod
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 2,
|
||||
modifier = Modifier.padding(8.dp).background(Color.Transparent).heightIn(48.dp)
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.background(Color.Transparent)
|
||||
.heightIn(48.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +487,6 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 2.dp)
|
||||
.align(Alignment.BottomCenter).background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
|
||||
@@ -20,6 +20,28 @@ class ComicScreenViewModel : ViewModel() {
|
||||
var imageLoader: ImageLoader? = null;
|
||||
|
||||
val comics = mutableStateListOf<Comic>()
|
||||
val excluded = mutableStateListOf<String>()
|
||||
val included = mutableStateListOf<String>()
|
||||
val tags = mutableStateListOf<String>()
|
||||
private val counter = mutableMapOf<String, Int>()
|
||||
|
||||
fun insertItem(newItem: String) {
|
||||
val newCount = (counter[newItem] ?: 0) + 1
|
||||
counter[newItem] = newCount
|
||||
|
||||
if (newItem !in tags) {
|
||||
val insertIndex = tags.indexOfFirst { counter[it]!! < newCount }
|
||||
.takeIf { it >= 0 } ?: tags.size
|
||||
tags.add(insertIndex, newItem)
|
||||
} else {
|
||||
var currentIndex = tags.indexOf(newItem)
|
||||
while (currentIndex > 0 && counter[tags[currentIndex - 1]]!! < newCount) {
|
||||
tags[currentIndex] = tags[currentIndex - 1]
|
||||
tags[currentIndex - 1] = newItem
|
||||
currentIndex--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetupClient()
|
||||
@@ -38,8 +60,13 @@ class ComicScreenViewModel : ViewModel() {
|
||||
for(i in l)
|
||||
{
|
||||
val m = MediaManager.queryComicInfo(i)
|
||||
if(m != null)
|
||||
if(m != null) {
|
||||
comics.add(m)
|
||||
for(i in m.comic.tags)
|
||||
{
|
||||
insertItem(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.12.1"
|
||||
agp = "8.13.0"
|
||||
bcprovJdk15on = "1.70"
|
||||
bcprovJdk18on = "1.81"
|
||||
coilCompose = "3.3.0"
|
||||
|
||||
Reference in New Issue
Block a user