[feat] UI optimization 3

This commit is contained in:
acite
2025-10-01 19:47:00 +08:00
parent 7c99ea394b
commit 603c2c38aa
17 changed files with 439 additions and 215 deletions

View File

@@ -54,6 +54,8 @@ dependencies {
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
implementation(libs.androidx.compose.material.core)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.compose.animation)
ksp(libs.hilt.android.compiler)
implementation(libs.androidx.room.runtime)

View File

@@ -10,6 +10,7 @@ object Global {
var sameClassVideos: List<Video>? = null
private set
var isFullScreen by mutableStateOf(false)
fun updateRelate(v: List<Video>, s: Video)
{
sameClassVideos = if (v.contains(s)) {

View File

@@ -4,11 +4,15 @@ import android.app.Activity
import android.content.Intent
import androidx.compose.material.icons.Icons
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
@@ -95,22 +99,17 @@ class MainScreenActivity : ComponentActivity() {
}
@Composable
fun ToggleFullScreen(isFullScreen: Boolean)
{
val view = LocalView.current
fun setFullScreen(view: View, isFullScreen: Boolean) {
Global.isFullScreen = isFullScreen
val window = (view.context as Activity).window
val insetsController = WindowCompat.getInsetsController(window, view)
LaunchedEffect(isFullScreen) {
val window = (view.context as Activity).window
val insetsController = WindowCompat.getInsetsController(window, view)
if (isFullScreen) {
insetsController.hide(WindowInsetsCompat.Type.systemBars())
insetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} else {
insetsController.show(WindowInsetsCompat.Type.systemBars())
}
if (isFullScreen) {
insetsController.hide(WindowInsetsCompat.Type.systemBars())
insetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} else {
insetsController.show(WindowInsetsCompat.Type.systemBars())
}
}
@@ -136,40 +135,64 @@ fun AppNavigation() {
) {
BottomNavigationBar(navController = navController)
}
if(shouldShowBottomBar)
ToggleFullScreen(false)
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = Screen.Me.route,
modifier = if(shouldShowBottomBar)Modifier.padding(innerPadding) else Modifier.padding(0.dp)
modifier = if(!Global.isFullScreen) Modifier.padding(innerPadding) else Modifier.padding(0.dp)
) {
composable(Screen.Home.route) {
composable(
Screen.Home.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }
) {
CardPage(title = "Home") {
HomeScreen(navController = navController)
}
}
composable(Screen.Video.route) {
composable(Screen.Video.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }) {
VideoScreen(navController = navController)
}
composable(Screen.Comic.route) {
composable(Screen.Comic.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }) {
CardPage(title = "Comic") {
ComicScreen(navController = navController)
}
}
composable(Screen.Transmission.route) {
composable(Screen.Transmission.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }) {
CardPage(title = "Tasks") {
TransmissionScreen(navigator = navController)
}
}
composable(Screen.Me.route) {
composable(Screen.Me.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) }) {
MeScreen();
}
composable(
route = Screen.VideoPlayer.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
arguments = listOf(navArgument("videoId") { type = NavType.StringType })
) {
backStackEntry ->
@@ -181,6 +204,10 @@ fun AppNavigation() {
composable(
route = Screen.ComicGrid.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
arguments = listOf(navArgument("comicId") { type = NavType.StringType })
) {
backStackEntry ->
@@ -192,6 +219,10 @@ fun AppNavigation() {
composable(
route = Screen.ComicPage.route,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, animationSpec = tween(200)) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, animationSpec = tween(200)) },
arguments = listOf(navArgument("comicId") { type = NavType.StringType }, navArgument("page") { type = NavType.StringType })
) {
backStackEntry ->
@@ -199,7 +230,6 @@ fun AppNavigation() {
val page = backStackEntry.arguments?.getString("page")
if (comicId != null && page != null) {
ComicPageView(comicId = comicId, page = page, navController = navController)
ToggleFullScreen(true)
}
}
}

View File

@@ -1,5 +1,8 @@
package com.acitelight.aether.model
import kotlinx.serialization.Serializable
@Serializable
data class BookMark(
val name: String,
val page: String

View File

@@ -7,6 +7,19 @@ class Comic(
val id: String
)
{
fun getCover(api: ApiClient): String
{
if(id == "101")
print("")
if(comic.cover != "")
{
return "${api.getBase()}api/image/$id/${comic.cover}"
}
return "${api.getBase()}api/image/$id/${comic.list[0]}"
}
fun getPage(pageNumber: Int, api: ApiClient): String
{
return "${api.getBase()}api/image/$id/${comic.list[pageNumber]}"

View File

@@ -1,10 +1,14 @@
package com.acitelight.aether.model
import kotlinx.serialization.Serializable
@Serializable
data class ComicResponse(
val comic_name: String,
val page_count: Int,
val bookmarks: List<BookMark>,
val list: List<String>,
val tags: List<String>,
val author: String
val author: String,
val cover: String
)

View File

@@ -219,6 +219,8 @@ class ApiClient @Inject constructor(
suspend fun apply(context: Context, urls: String, crt: String): String? {
try {
client = createOkHttp()
val urlList = urls.split(";").map { it.trim() }
var selectedUrl: String? = null
@@ -231,7 +233,6 @@ class ApiClient @Inject constructor(
}
if (selectedUrl == null) {
client = createOkHttp()
throw Exception("No reachable URL found")
}

View File

@@ -59,9 +59,9 @@ fun ComicCard(
Box(modifier = Modifier.fillMaxSize()) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(0, comicScreenViewModel.apiClient))
.memoryCacheKey("${comic.id}/${0}")
.diskCacheKey("${comic.id}/${0}")
.data(comic.getCover(comicScreenViewModel.apiClient))
.memoryCacheKey("${comic.id}/cover")
.diskCacheKey("${comic.id}/cover")
.build(),
contentDescription = null,
imageLoader = comicScreenViewModel.imageLoader!!,
@@ -115,14 +115,6 @@ fun ComicCard(
maxLines = 1,
modifier = Modifier.align(Alignment.CenterStart)
)
Text(
text = comic.comic.author,
fontSize = 12.sp,
lineHeight = 14.sp,
maxLines = 1,
modifier = Modifier.align(Alignment.CenterEnd)
)
}
}
}

View File

@@ -51,6 +51,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
@@ -61,6 +62,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -69,7 +71,7 @@ import androidx.lifecycle.viewModelScope
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.acitelight.aether.ToggleFullScreen
import com.acitelight.aether.setFullScreen
import com.acitelight.aether.view.pages.formatTime
import com.acitelight.aether.view.pages.moveBrit
import com.acitelight.aether.viewModel.VideoPlayerViewModel
@@ -108,7 +110,14 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
videoPlayerViewModel.isLandscape = false
}
ToggleFullScreen(true)
val view = LocalView.current
DisposableEffect(Unit) {
setFullScreen(view, true)
onDispose {
setFullScreen(view, false)
}
}
Box(Modifier.fillMaxSize())
{
Box(

View File

@@ -48,7 +48,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.acitelight.aether.Global
import com.acitelight.aether.ToggleFullScreen
import com.acitelight.aether.view.pages.formatTime
import com.acitelight.aether.view.pages.toHex
import com.acitelight.aether.viewModel.VideoPlayerViewModel
@@ -110,7 +109,6 @@ fun VideoPlayerPortal(
val name by videoPlayerViewModel.currentName
val duration by videoPlayerViewModel.currentDuration
ToggleFullScreen(false)
Column(
Modifier
.nestedScroll(nestedScrollConnection)
@@ -120,7 +118,6 @@ fun VideoPlayerPortal(
Box {
PortalCorePlayer(
Modifier
.padding(top = 32.dp)
.heightIn(max = playerHeight)
.onGloballyPositioned { layoutCoordinates ->
if (!posed && videoPlayerViewModel.renderedFirst) {

View File

@@ -2,44 +2,72 @@ package com.acitelight.aether.view.pages
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
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
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.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavHostController
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.ToggleFullScreen
import com.acitelight.aether.model.BookMark
import com.acitelight.aether.model.Comic
import com.acitelight.aether.setFullScreen
import com.acitelight.aether.view.components.BiliMiniSlider
import com.acitelight.aether.viewModel.ComicGridViewModel
@Composable
fun ComicGridView(
comicId: String,
@@ -48,104 +76,206 @@ fun ComicGridView(
) {
comicGridViewModel.resolve(comicId.hexToString())
comicGridViewModel.updateProcess(comicId.hexToString()) {}
ToggleFullScreen(false)
val colorScheme = MaterialTheme.colorScheme
val comic by comicGridViewModel.comic
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val screenWidth = configuration.screenWidthDp.dp
val record by comicGridViewModel.record
val comic by comicGridViewModel.comic
val view = LocalView.current
DisposableEffect(Unit) {
setFullScreen(view, true)
onDispose {
val nextRoute = navController.currentBackStackEntry?.destination?.route
if (nextRoute?.startsWith("comic_page_route") != true) {
setFullScreen(view, false)
}
}
}
val dens = LocalDensity.current
val listState = rememberLazyListState()
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val deltaY = available.y // px
val deltaDp = with(dens) { deltaY.toDp() }
val r = if (deltaY < 0 && comicGridViewModel.coverHeight > 0.dp) {
val newHeight = (comicGridViewModel.coverHeight + deltaDp).coerceIn(0.dp, comicGridViewModel.maxHeight)
val consumedDp = newHeight - comicGridViewModel.coverHeight
comicGridViewModel.coverHeight = newHeight
val consumedPx = with(dens) { consumedDp.toPx() }
Offset(0f, consumedPx)
} else if (
deltaY > 0
&& comicGridViewModel.coverHeight < comicGridViewModel.maxHeight
&& listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0
) {
val newHeight = (comicGridViewModel.coverHeight + deltaDp).coerceIn(0.dp, comicGridViewModel.maxHeight)
val consumedDp = newHeight - comicGridViewModel.coverHeight
comicGridViewModel.coverHeight = newHeight
val consumedPx = with(dens) { consumedDp.toPx() }
Offset(0f, consumedPx)
} else {
Offset.Zero
}
return r
}
}
}
if (comic != null) {
Column {
Card(
Modifier
.padding(horizontal = 16.dp)
.padding(top = 36.dp)
.heightIn(min = 42.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
shape = RoundedCornerShape(12.dp)
)
val comic = comic!!
val pagerState = rememberPagerState(
initialPage = 0,
pageCount = { comic.comic.bookmarks.size })
Column(Modifier
.nestedScroll(nestedScrollConnection).fillMaxSize()) {
Box(Modifier
.fillMaxWidth()
.height(comicGridViewModel.coverHeight))
{
Box(
Modifier
.heightIn(min = 42.dp)
.fillMaxWidth()
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
)
{
Text(
text = comic!!.comic.comic_name,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
modifier = Modifier.padding(4.dp).align(Alignment.CenterStart)
{ page ->
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(comic.comic.bookmarks[page].page, comicGridViewModel.apiClient))
.memoryCacheKey("${comic.id}/${comic.comic.bookmarks[page].page}")
.diskCacheKey("${comic.id}/${comic.comic.bookmarks[page].page}")
.build(),
contentDescription = null,
imageLoader = comicGridViewModel.imageLoader!!,
modifier = Modifier
.fillMaxSize(),
contentScale = ContentScale.FillWidth,
onSuccess = { success ->
val drawable = success.result.image
val width = drawable.width
val height = drawable.height
val aspectRatio = width.toFloat() / height.toFloat()
comicGridViewModel.maxHeight = screenWidth / aspectRatio
if(comicGridViewModel.coverHeight > comicGridViewModel.maxHeight)
comicGridViewModel.coverHeight = comicGridViewModel.maxHeight
},
)
}
Box(modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.height(50.dp)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.5f),
)
)
)
)
BiliMiniSlider(
value = (pagerState.currentPage + 1) / pagerState.pageCount.toFloat(),
modifier = Modifier
.height(6.dp)
.width(100.dp)
.align(Alignment.BottomCenter)
.fillMaxWidth(),
onValueChange = {
}
)
}
Card(
Modifier
.padding(horizontal = 16.dp)
.padding(top = 4.dp)
.heightIn(min = 42.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
shape = RoundedCornerShape(12.dp)
) {
Box(
Modifier
.heightIn(min = 42.dp)
.fillMaxWidth()
)
{
Text(
text = comic!!.comic.author,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
modifier = Modifier
.padding(4.dp)
.align(Alignment.CenterStart)
)
}
}
Card(
Modifier
.padding(horizontal = 16.dp)
.padding(top = 4.dp)
.heightIn(min = 42.dp),
colors = CardDefaults.cardColors(containerColor = colorScheme.primary),
shape = RoundedCornerShape(12.dp)
) {
Box(
Modifier
.heightIn(min = 42.dp)
.fillMaxWidth()
)
{
Text(
text = "Tags : ${comic!!.comic.tags.joinToString(", ")}",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
maxLines = 5,
modifier = Modifier
.padding(4.dp)
.align(Alignment.CenterStart)
)
}
}
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.fillMaxSize()
.padding(top = 6.dp)
.clip(RoundedCornerShape(6.dp))
)
{
item()
{
Text(
text = comic.comic.comic_name,
fontSize = 18.sp,
lineHeight = 22.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
modifier = Modifier.padding(horizontal = 16.dp).padding(top = 16.dp).padding(bottom = 4.dp)
)
FlowRow(
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 4.dp)
)
{
comic.comic.tags.take(15).forEach()
{
ic ->
Card(
Modifier.padding(1.dp),
shape = RoundedCornerShape(8.dp)
) {
Text(
text = ic,
fontSize = 10.sp,
lineHeight = 12.sp,
fontWeight = FontWeight.Bold,
maxLines = 2,
modifier = Modifier
.padding(4.dp)
)
}
}
}
Box(Modifier.fillMaxWidth())
{
Text(
text = "Author: ${comic.comic.author} \n${comic.comic.list.size} Pages",
fontSize = 11.sp,
lineHeight = 15.sp,
maxLines = 3,
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 4.dp)
)
Button(onClick = {
comicGridViewModel.updateProcess(comicId.hexToString())
{
if (record != null) {
val route = "comic_page_route/${comic.id.toHex()}/${
record!!.position
}"
navController.navigate(route)
} else {
val route = "comic_page_route/${comic.id.toHex()}/${0}"
navController.navigate(route)
}
}
}, modifier = Modifier.align(Alignment.CenterEnd))
{
Text(text = "Continue", fontSize = 16.sp)
}
}
HorizontalDivider(Modifier.padding(horizontal = 12.dp).padding(bottom = 4.dp), thickness = 1.5.dp)
}
items(comicGridViewModel.chapterList)
{ c ->
ChapterCard(comic!!, navController, c, comicGridViewModel)
ChapterCard(comic, navController, c, comicGridViewModel)
HorizontalDivider(Modifier.padding(horizontal = 26.dp), thickness = 1.5.dp)
}
}
/*
Card(
Modifier
.padding(horizontal = 16.dp)
@@ -156,12 +286,12 @@ fun ComicGridView(
comicGridViewModel.updateProcess(comicId.hexToString())
{
if (record != null) {
val route = "comic_page_route/${comic!!.id.toHex()}/${
val route = "comic_page_route/${comic.id.toHex()}/${
record!!.position
}"
navController.navigate(route)
} else {
val route = "comic_page_route/${comic!!.id.toHex()}/${0}"
val route = "comic_page_route/${comic.id.toHex()}/${0}"
navController.navigate(route)
}
}
@@ -178,11 +308,11 @@ fun ComicGridView(
.padding(horizontal = 8.dp)
) {
if (record != null) {
val k = comic!!.getPageChapterIndex(record!!.position)
val k = comic.getPageChapterIndex(record!!.position)
Text(
text = "Last Read Position: ${k.first.name} ${k.second}/${
comic!!.getChapterLength(
comic.getChapterLength(
k.first.page
)
}",
@@ -207,6 +337,8 @@ fun ComicGridView(
}
}
}
*/
}
}
}
@@ -220,8 +352,10 @@ fun ChapterCard(
) {
val c = chapter
val iv = comic.getPageIndex(c.page)
val r = comic.comic.list.subList(iv, iv + comic.getChapterLength(c.page))
Card(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface.copy(0.65f)),
shape = RoundedCornerShape(6.dp),
modifier = Modifier
.fillMaxWidth()
@@ -235,57 +369,31 @@ fun ChapterCard(
) {
Column(Modifier.fillMaxWidth())
{
Row(Modifier.padding(6.dp))
{
Box(
Modifier
.heightIn(max = 170.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color(0x44FFFFFF))
)
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(comic.getPage(c.page, comicGridViewModel.apiClient))
.memoryCacheKey("${comic.id}/${c.page}")
.diskCacheKey("${comic.id}/${c.page}")
.build(),
contentDescription = null,
imageLoader = comicGridViewModel.imageLoader!!,
modifier = Modifier
.padding(8.dp)
.widthIn(max = 170.dp),
contentScale = ContentScale.Fit,
)
}
Text(
text = chapter.name,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
maxLines = 2,
lineHeight = 16.sp,
modifier = Modifier
.padding(horizontal = 8.dp).padding(vertical = 4.dp)
.background(Color.Transparent)
)
Text(
text = "${comic.getChapterLength(chapter.page)} Pages",
fontSize = 14.sp,
lineHeight = 16.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
modifier = Modifier
.padding(horizontal = 8.dp)
.background(Color.Transparent)
)
Column(modifier = Modifier.padding(horizontal = 12.dp)) {
Text(
text = chapter.name,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
maxLines = 5,
modifier = Modifier
.padding(8.dp)
.background(Color.Transparent)
)
Text(
text = "${comic.getChapterLength(chapter.page)} Pages",
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
modifier = Modifier
.padding(8.dp)
.background(Color.Transparent)
)
}
}
val r = comic.comic.list.subList(iv, iv + comic.getChapterLength(c.page))
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(6.dp)
.padding(horizontal = 8.dp).padding(vertical = 4.dp)
) {
items(r)
{ r ->
@@ -294,8 +402,8 @@ fun ChapterCard(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.height(140.dp)
.padding(horizontal = 6.dp),
.height(120.dp)
.padding(horizontal = 2.dp),
onClick = {
val route =
"comic_page_route/${comic.id.toHex()}/${comic.getPageIndex(r)}"
@@ -313,7 +421,7 @@ fun ChapterCard(
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(12.dp)),
contentScale = ContentScale.Crop,
contentScale = ContentScale.Fit,
)
}
}

View File

@@ -28,6 +28,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -38,6 +39,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -47,6 +49,7 @@ import androidx.navigation.NavHostController
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.acitelight.aether.model.BookMark
import com.acitelight.aether.setFullScreen
import com.acitelight.aether.view.components.BookmarkPop
import com.acitelight.aether.viewModel.ComicPageViewModel
import kotlinx.coroutines.launch
@@ -71,6 +74,15 @@ fun ComicPageView(
comicPageViewModel.updateProcess(pagerState.currentPage)
val comic by comicPageViewModel.comic
val view = LocalView.current
DisposableEffect(Unit) {
setFullScreen(view, true)
onDispose {
}
}
comic?.let {
Box()
{
@@ -233,7 +245,7 @@ fun ComicPageView(
.fillMaxWidth()
.padding(bottom = 18.dp)
.padding(horizontal = 12.dp)
.height(240.dp)
.height(180.dp)
.align(Alignment.BottomCenter)
)
{
@@ -241,7 +253,7 @@ fun ComicPageView(
{ r ->
Card(
colors = CardDefaults.cardColors(containerColor = colorScheme.primary.copy(0.8f)),
shape = RoundedCornerShape(12.dp),
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxHeight()
.wrapContentHeight()
@@ -250,7 +262,7 @@ fun ComicPageView(
pagerState.requestScrollToPage(page = r)
}
) {
Box(Modifier.padding(4.dp))
Box(Modifier.padding(1.dp))
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
@@ -262,7 +274,7 @@ fun ComicPageView(
imageLoader = comicPageViewModel.imageLoader!!,
modifier = Modifier
.fillMaxHeight()
.clip(RoundedCornerShape(12.dp))
.clip(RoundedCornerShape(8.dp))
.align(Alignment.Center),
contentScale = ContentScale.Fit,
)
@@ -278,18 +290,6 @@ fun ComicPageView(
)
{
Row {
Text(
text = k.first.name,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
maxLines = 1,
modifier = Modifier
.padding(2.dp)
.widthIn(max = 200.dp)
.align(Alignment.CenterVertically)
)
Text(
text = "${k.second}/${it.getChapterLength(k.first.page)}",
fontSize = 16.sp,

View File

@@ -6,11 +6,15 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.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.staggeredgrid.LazyVerticalStaggeredGrid
@@ -19,17 +23,25 @@ import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -118,16 +130,50 @@ fun ComicScreen(
val included = comicScreenViewModel.included
val state = rememberLazyStaggeredGridState()
val colorScheme = MaterialTheme.colorScheme
var searchFilter by comicScreenViewModel.searchFilter
Column {
Text(
text = "Comic& Images",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.padding(8.dp)
.align(Alignment.Start)
)
HorizontalDivider(Modifier.padding(1.dp), thickness = 1.5.dp)
Row(Modifier
.padding(4.dp)
.align(Alignment.CenterHorizontally)) {
Text(
text = "Comics",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.align(Alignment.CenterVertically)
)
Spacer(Modifier.weight(1f))
Row(
modifier = Modifier
.align(Alignment.CenterVertically)
.height(36.dp)
.widthIn(max = 240.dp)
.background(colorScheme.primary, RoundedCornerShape(8.dp))
.padding(horizontal = 6.dp)
) {
Icon(
modifier = Modifier
.size(30.dp)
.align(Alignment.CenterVertically),
imageVector = Icons.Default.Search,
contentDescription = "Catalogue"
)
Spacer(Modifier.width(4.dp))
BasicTextField(
value = searchFilter,
onValueChange = { searchFilter = it },
textStyle = LocalTextStyle.current.copy(
fontSize = 18.sp,
color = Color.White,
textAlign = TextAlign.Start
),
singleLine = true,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
}
VariableGrid(
modifier = Modifier
.heightIn(max = 88.dp)
@@ -140,7 +186,7 @@ fun ComicScreen(
Box(
Modifier
.background(
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.primary,
if (included.contains(i)) Color.Green.copy(alpha = 0.65f) else colorScheme.surface,
shape = RoundedCornerShape(4.dp)
)
.height(32.dp).widthIn(max = 72.dp)
@@ -167,15 +213,15 @@ fun ComicScreen(
HorizontalDivider(Modifier.padding(1.dp), thickness = 1.5.dp)
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(136.dp),
contentPadding = PaddingValues(8.dp),
verticalItemSpacing = 8.dp,
columns = StaggeredGridCells.Adaptive(120.dp),
contentPadding = PaddingValues(4.dp),
verticalItemSpacing = 6.dp,
horizontalArrangement = Arrangement.spacedBy(4.dp),
state = state,
modifier = Modifier.fillMaxSize()
) {
items(
items = comicScreenViewModel.comics.filter { x ->
items = comicScreenViewModel.comics.filter { searchFilter.isEmpty() || searchFilter in it.comic.comic_name }.filter { x ->
included.all { y -> y in x.comic.tags } || included.isEmpty()
},
key = { it.id }

View File

@@ -89,10 +89,13 @@ fun HomeScreen(
val group =
fv.filter { it.klass == i.klass && it.video.group == i.video.group && it.video.group != "null" }
for (i in group) {
playList.add("${i.klass}/${i.id}")
for (ix in group) {
playList.add("${ix.klass}/${ix.id}")
}
if(!playList.contains("${i.klass}/${i.id}"))
playList.add("${i.klass}/${i.id}")
val route =
"video_player_route/${(playList.joinToString(",") + "|${i.id}").toHex()}"
navController.navigate(route)

View File

@@ -1,8 +1,12 @@
package com.acitelight.aether.viewModel
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import coil3.ImageLoader
@@ -27,6 +31,9 @@ class ComicGridViewModel @Inject constructor(
val apiClient: ApiClient
) : ViewModel()
{
var coverHeight by mutableStateOf(220.dp)
var maxHeight = 220.dp
var imageLoader: ImageLoader? = null
var comic = mutableStateOf<Comic?>(null)
val chapterList = mutableStateListOf<BookMark>()

View File

@@ -2,6 +2,7 @@ package com.acitelight.aether.viewModel
import android.content.Context
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import coil3.ImageLoader
@@ -23,6 +24,7 @@ class ComicScreenViewModel @Inject constructor(
var imageLoader: ImageLoader? = null;
val searchFilter = mutableStateOf("")
val comics = mutableStateListOf<Comic>()
val excluded = mutableStateListOf<String>()
val included = mutableStateListOf<String>()

View File

@@ -1,4 +1,5 @@
[versions]
accompanistNavigationAnimation = "0.37.3"
agp = "8.13.0"
ariaCompiler = "latest"
bcprovJdk15on = "1.70"
@@ -10,7 +11,7 @@ datastorePreferences = "1.1.7"
exoplayerplus = "0.2.0"
fetch2 = "3.4.1"
fetch2okhttp = "3.4.1"
gson = "2.13.1"
gson = "2.13.2"
kotlin = "2.2.20"
coreKtx = "1.17.0"
junit = "4.13.2"
@@ -19,28 +20,31 @@ espressoCore = "3.7.0"
kotlinxSerializationJson = "1.9.0"
lifecycleRuntimeKtx = "2.9.4"
activityCompose = "1.11.0"
composeBom = "2025.09.00"
composeBom = "2025.09.01"
media3Common = "1.8.0"
media3Exoplayer = "1.8.0"
media3ExoplayerFfmpeg = "1.8.0"
media3Ui = "1.8.0"
navigationCompose = "2.9.4"
navigationCompose = "2.9.5"
okhttp = "5.1.0"
persistentcookiejar = "1.0.1"
repo = "Tag"
retrofit = "3.0.0"
retrofit2KotlinxSerializationConverter = "1.0.0"
media3DatasourceOkhttp = "1.8.0"
roomCompiler = "2.8.0"
roomKtx = "2.8.0"
roomRuntime = "2.8.0"
roomCompiler = "2.8.1"
roomKtx = "2.8.1"
roomRuntime = "2.8.1"
ksp = "2.1.21-2.0.2"
hilt = "2.57.1"
hilt = "2.57.2"
hilt-navigation-compose = "1.3.0"
composeMaterialCore = "1.5.1"
composeMaterialCore = "1.5.2"
constraintlayout = "2.2.1"
animation = "1.9.2"
[libraries]
accompanist-navigation-animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanistNavigationAnimation" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
@@ -83,6 +87,8 @@ hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose" }
androidx-compose-material-core = { group = "androidx.wear.compose", name = "compose-material-core", version.ref = "composeMaterialCore" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "animation" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }