diff --git a/app/src/main/java/com/acitelight/aether/model/ComicResponse.kt b/app/src/main/java/com/acitelight/aether/model/ComicResponse.kt index 5ba3c07..4303553 100644 --- a/app/src/main/java/com/acitelight/aether/model/ComicResponse.kt +++ b/app/src/main/java/com/acitelight/aether/model/ComicResponse.kt @@ -5,5 +5,6 @@ data class ComicResponse( val page_count: Int, val bookmarks: List, val list: List, + val tags: List, val author: String ) \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt b/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt index f89f227..36b44fb 100644 --- a/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt +++ b/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt @@ -56,27 +56,44 @@ 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, - fontWeight = FontWeight.Bold, - color = Color.Black, - maxLines = 1, - modifier = Modifier.padding(4.dp) - ) - - Text( - text = comic!!.comic.author, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - color = Color.Black, - maxLines = 1, - modifier = Modifier.padding(4.dp).fillMaxWidth() - ) - } + Text( + text = comic!!.comic.comic_name, + fontSize = 18.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 = comic!!.comic.author, + 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))) { items(comicGridViewModel.chapterList) diff --git a/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt index 35d4b63..a7db4bb 100644 --- a/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt +++ b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt @@ -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) ) } } diff --git a/app/src/main/java/com/acitelight/aether/view/ComicScreen.kt b/app/src/main/java/com/acitelight/aether/view/ComicScreen.kt index 7621233..bc4eeb5 100644 --- a/app/src/main/java/com/acitelight/aether/view/ComicScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/ComicScreen.kt @@ -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,40 +59,166 @@ import com.acitelight.aether.viewModel.ComicScreenViewModel import java.nio.charset.Charset @Composable -fun ComicScreen(navController: NavHostController, comicScreenViewModel: ComicScreenViewModel = viewModel()) -{ - comicScreenViewModel.SetupClient() +fun VariableGrid( + modifier: Modifier = Modifier, + rowHeight: Dp, + horizontalSpacing: Dp = 4.dp, + verticalSpacing: Dp = 4.dp, + content: @Composable () -> Unit +) { + val scrollState = rememberScrollState() - LazyVerticalGrid( - columns = GridCells.Adaptive(128.dp), - contentPadding = PaddingValues(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) - { - items(comicScreenViewModel.comics) { comic -> - ComicCard(comic, navController, comicScreenViewModel) + 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>() + var currentRow = mutableListOf() + 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 -fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewModel: ComicScreenViewModel) { +fun ComicCard( + comic: Comic, + navController: NavHostController, + comicScreenViewModel: ComicScreenViewModel +) { Card( shape = RoundedCornerShape(6.dp), modifier = Modifier .fillMaxWidth() .wrapContentHeight(), onClick = { - val route = "comic_grid_route/${"${comic.id}".toHex() }" + val route = "comic_grid_route/${"${comic.id}".toHex()}" navController.navigate(route) } ) { Column( modifier = Modifier .fillMaxWidth() - ) { - Box(modifier = Modifier.fillMaxSize()){ + ) { + Box(modifier = Modifier.fillMaxSize()) { AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(comic.getPage(0)) @@ -95,25 +232,30 @@ fun ComicCard(comic: Comic, navController: NavHostController, comicScreenViewMod contentScale = ContentScale.Crop, ) - Box( Modifier .fillMaxWidth() .height(24.dp) - .background( brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black.copy(alpha = 0.45f) + .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) ) } } diff --git a/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt b/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt index 6ff29a0..ddda8b6 100644 --- a/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt +++ b/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt @@ -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( diff --git a/app/src/main/java/com/acitelight/aether/viewModel/ComicScreenViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/ComicScreenViewModel.kt index f7af104..04f14e0 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/ComicScreenViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/ComicScreenViewModel.kt @@ -20,6 +20,28 @@ class ComicScreenViewModel : ViewModel() { var imageLoader: ImageLoader? = null; val comics = mutableStateListOf() + val excluded = mutableStateListOf() + val included = mutableStateListOf() + val tags = mutableStateListOf() + private val counter = mutableMapOf() + + 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) + } + } } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 734ef90..71cb063 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.12.1" +agp = "8.13.0" bcprovJdk15on = "1.70" bcprovJdk18on = "1.81" coilCompose = "3.3.0"