[feat] Comic Tags

This commit is contained in:
acite
2025-09-07 13:09:25 +08:00
parent 514e99d7db
commit aacd226260
7 changed files with 243 additions and 53 deletions

View File

@@ -5,5 +5,6 @@ data class ComicResponse(
val page_count: Int, val page_count: Int,
val bookmarks: List<BookMark>, val bookmarks: List<BookMark>,
val list: List<String>, val list: List<String>,
val tags: List<String>,
val author: String val author: String
) )

View File

@@ -56,27 +56,44 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp)) .background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp))
) )
{ {
Column { Text(
Text( text = comic!!.comic.comic_name,
text = comic!!.comic.comic_name, fontSize = 18.sp,
fontSize = 18.sp, fontWeight = FontWeight.Bold,
fontWeight = FontWeight.Bold, color = Color.Black,
color = Color.Black, maxLines = 1,
maxLines = 1, modifier = Modifier.padding(4.dp)
modifier = Modifier.padding(4.dp) )
) }
Box(
Text( Modifier
text = comic!!.comic.author, .padding(horizontal = 16.dp).padding(top = 4.dp)
fontSize = 16.sp, .background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp))
fontWeight = FontWeight.Bold, ) {
color = Color.Black, Text(
maxLines = 1, text = comic!!.comic.author,
modifier = Modifier.padding(4.dp).fillMaxWidth() fontSize = 16.sp,
) fontWeight = FontWeight.Bold,
} color = Color.Black,
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 = "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))) LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f).padding(top = 6.dp).clip(RoundedCornerShape(6.dp)))
{ {
items(comicGridViewModel.chapterList) items(comicGridViewModel.chapterList)

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -224,14 +225,14 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
Card( Card(
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxHeight()
.wrapContentHeight() .wrapContentHeight()
.padding(horizontal = 6.dp).padding(vertical = 6.dp), .padding(horizontal = 6.dp).padding(vertical = 6.dp),
onClick = { onClick = {
pagerState.requestScrollToPage(page = r) pagerState.requestScrollToPage(page = r)
} }
){ ){
Box(Modifier.fillMaxSize()) Box()
{ {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest.Builder(LocalContext.current)
@@ -242,7 +243,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
contentDescription = null, contentDescription = null,
imageLoader = comicPageViewModel.imageLoader!!, imageLoader = comicPageViewModel.imageLoader!!,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxHeight()
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.align(Alignment.Center), .align(Alignment.Center),
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
@@ -260,7 +261,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.White, color = Color.White,
maxLines = 1, maxLines = 1,
modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically) modifier = Modifier.padding(2.dp).widthIn(max = 100.dp).align(Alignment.CenterVertically)
) )
Text( Text(
@@ -269,7 +270,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.White, color = Color.White,
maxLines = 1, maxLines = 1,
modifier = Modifier.padding(4.dp).fillMaxWidth().align(Alignment.CenterVertically) modifier = Modifier.padding(2.dp).align(Alignment.CenterVertically)
) )
} }
} }

View File

@@ -1,6 +1,8 @@
package com.acitelight.aether.view package com.acitelight.aether.view
import android.nfc.Tag
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -35,11 +41,16 @@ import coil3.compose.AsyncImage
import com.acitelight.aether.model.Video import com.acitelight.aether.model.Video
import com.acitelight.aether.viewModel.VideoScreenViewModel import com.acitelight.aether.viewModel.VideoScreenViewModel
import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Brush 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.platform.LocalContext
import androidx.compose.ui.unit.Dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import coil3.request.ImageRequest import coil3.request.ImageRequest
import com.acitelight.aether.Global import com.acitelight.aether.Global
@@ -48,40 +59,166 @@ import com.acitelight.aether.viewModel.ComicScreenViewModel
import java.nio.charset.Charset import java.nio.charset.Charset
@Composable @Composable
fun ComicScreen(navController: NavHostController, comicScreenViewModel: ComicScreenViewModel = viewModel()) fun VariableGrid(
{ modifier: Modifier = Modifier,
comicScreenViewModel.SetupClient() rowHeight: Dp,
horizontalSpacing: Dp = 4.dp,
verticalSpacing: Dp = 4.dp,
content: @Composable () -> Unit
) {
val scrollState = rememberScrollState()
LazyVerticalGrid( Layout(
columns = GridCells.Adaptive(128.dp), modifier = modifier
contentPadding = PaddingValues(8.dp), .verticalScroll(scrollState), // ✅ 支持垂直滚动
verticalArrangement = Arrangement.spacedBy(8.dp), content = content
horizontalArrangement = Arrangement.spacedBy(8.dp) ) { measurables, constraints ->
)
{ val rowHeightPx = rowHeight.roundToPx()
items(comicScreenViewModel.comics) { comic -> val hSpacePx = horizontalSpacing.roundToPx()
ComicCard(comic, navController, comicScreenViewModel) 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),
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
)
{
items(comicScreenViewModel.comics.filter { x ->
included.all { y -> y in x.comic.tags } || included.isEmpty()
})
{ comic ->
ComicCard(comic, navController, comicScreenViewModel)
}
} }
} }
} }
@Composable @Composable
fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewModel: ComicScreenViewModel) { fun ComicCard(
comic: Comic,
navController: NavHostController,
comicScreenViewModel: ComicScreenViewModel
) {
Card( Card(
shape = RoundedCornerShape(6.dp), shape = RoundedCornerShape(6.dp),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight(), .wrapContentHeight(),
onClick = { onClick = {
val route = "comic_grid_route/${"${comic.id}".toHex() }" val route = "comic_grid_route/${"${comic.id}".toHex()}"
navController.navigate(route) navController.navigate(route)
} }
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Box(modifier = Modifier.fillMaxSize()){ Box(modifier = Modifier.fillMaxSize()) {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(LocalContext.current) model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(0)) .data(comic.getPage(0))
@@ -95,25 +232,30 @@ fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewMod
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
) )
Box( Box(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.height(24.dp) .height(24.dp)
.background( brush = Brush.verticalGradient( .background(
colors = listOf( brush = Brush.verticalGradient(
Color.Transparent, colors = listOf(
Color.Black.copy(alpha = 0.45f) Color.Transparent,
Color.Black.copy(alpha = 0.45f)
)
) )
)) )
.align(Alignment.BottomCenter)) .align(Alignment.BottomCenter)
)
{ {
Text( Text(
modifier = Modifier.align(Alignment.BottomEnd).padding(2.dp), modifier = Modifier
.align(Alignment.BottomEnd)
.padding(2.dp),
fontSize = 12.sp, fontSize = 12.sp,
text = "${comic.comic.list.size} Pages", text = "${comic.comic.list.size} Pages",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.White) color = Color.White
)
} }
} }
Text( Text(
@@ -121,7 +263,10 @@ fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewMod
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
maxLines = 2, maxLines = 2,
modifier = Modifier.padding(8.dp).background(Color.Transparent).heightIn(48.dp) modifier = Modifier
.padding(8.dp)
.background(Color.Transparent)
.heightIn(48.dp)
) )
} }
} }

View File

@@ -487,7 +487,6 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 2.dp)
.align(Alignment.BottomCenter).background( .align(Alignment.BottomCenter).background(
brush = Brush.verticalGradient( brush = Brush.verticalGradient(
colors = listOf( colors = listOf(

View File

@@ -20,6 +20,28 @@ class ComicScreenViewModel : ViewModel() {
var imageLoader: ImageLoader? = null; var imageLoader: ImageLoader? = null;
val comics = mutableStateListOf<Comic>() 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 @Composable
fun SetupClient() fun SetupClient()
@@ -38,8 +60,13 @@ class ComicScreenViewModel : ViewModel() {
for(i in l) for(i in l)
{ {
val m = MediaManager.queryComicInfo(i) val m = MediaManager.queryComicInfo(i)
if(m != null) if(m != null) {
comics.add(m) comics.add(m)
for(i in m.comic.tags)
{
insertItem(i)
}
}
} }
} }
} }

View File

@@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.12.1" agp = "8.13.0"
bcprovJdk15on = "1.70" bcprovJdk15on = "1.70"
bcprovJdk18on = "1.81" bcprovJdk18on = "1.81"
coilCompose = "3.3.0" coilCompose = "3.3.0"