From daa66a9ecc2eafc9d1fa8a3b4d69d212fb833b32 Mon Sep 17 00:00:00 2001 From: acite <1498045907@qq.com> Date: Mon, 1 Sep 2025 20:41:28 +0800 Subject: [PATCH] [feat] Comic Reader --- .../com/acitelight/aether/MainActivity.kt | 32 ++- .../java/com/acitelight/aether/model/Comic.kt | 54 +++++- .../acitelight/aether/model/ComicResponse.kt | 9 + .../acitelight/aether/service/ApiInterface.kt | 19 +- .../acitelight/aether/service/MediaManager.kt | 24 ++- .../acitelight/aether/view/ComicGridView.kt | 154 +++++++++++++++ .../acitelight/aether/view/ComicPageView.kt | 182 ++++++++++++++++++ .../com/acitelight/aether/view/ComicScreen.kt | 119 +++++++++++- .../com/acitelight/aether/view/VideoScreen.kt | 2 +- .../aether/viewModel/ComicGridViewModel.kt | 45 +++++ .../aether/viewModel/ComicPageViewModel.kt | 62 ++++++ .../aether/viewModel/ComicScreenViewModel.kt | 42 +++- 12 files changed, 703 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/com/acitelight/aether/model/ComicResponse.kt create mode 100644 app/src/main/java/com/acitelight/aether/view/ComicGridView.kt create mode 100644 app/src/main/java/com/acitelight/aether/view/ComicPageView.kt create mode 100644 app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt create mode 100644 app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt diff --git a/app/src/main/java/com/acitelight/aether/MainActivity.kt b/app/src/main/java/com/acitelight/aether/MainActivity.kt index b753ed3..19ce686 100644 --- a/app/src/main/java/com/acitelight/aether/MainActivity.kt +++ b/app/src/main/java/com/acitelight/aether/MainActivity.kt @@ -48,6 +48,8 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import com.acitelight.aether.view.ComicGridView +import com.acitelight.aether.view.ComicPageView import com.acitelight.aether.view.ComicScreen import com.acitelight.aether.view.HomeScreen import com.acitelight.aether.view.MeScreen @@ -98,6 +100,8 @@ fun AppNavigation() { val hideBottomBarRoutes = listOf( Screen.VideoPlayer.route, + Screen.ComicGrid.route, + Screen.ComicPage.route ) val shouldShowBottomBar = currentRoute !in hideBottomBarRoutes @@ -126,7 +130,7 @@ fun AppNavigation() { VideoScreen(navController = navController) } composable(Screen.Comic.route) { - ComicScreen() + ComicScreen(navController = navController) } composable(Screen.Transmission.route) { @@ -146,6 +150,30 @@ fun AppNavigation() { VideoPlayer(videoId = videoId, navController = navController) } } + + composable( + route = Screen.ComicGrid.route, + arguments = listOf(navArgument("comicId") { type = NavType.StringType }) + ) { + backStackEntry -> + val comicId = backStackEntry.arguments?.getString("comicId") + if (comicId != null) { + ComicGridView(comicId = comicId, navController = navController) + } + } + + composable( + route = Screen.ComicPage.route, + arguments = listOf(navArgument("comicId") { type = NavType.StringType }, navArgument("page") { type = NavType.StringType }) + ) { + backStackEntry -> + val comicId = backStackEntry.arguments?.getString("comicId") + val page = backStackEntry.arguments?.getString("page") + if (comicId != null && page != null) { + ComicPageView(comicId = comicId, page = page, navController = navController) + ToggleFullScreen(true) + } + } } } } @@ -195,4 +223,6 @@ sealed class Screen(val route: String, val icon: ImageVector, val title: String) Icons.AutoMirrored.Filled.CompareArrows, "Transmission") data object Me : Screen("me_route", Icons.Filled.AccountCircle, "me") data object VideoPlayer : Screen("video_player_route/{videoId}", Icons.Filled.PlayArrow, "VideoPlayer") + data object ComicGrid : Screen("comic_grid_route/{comicId}", Icons.Filled.PlayArrow, "ComicGrid") + data object ComicPage : Screen("comic_page_route/{comicId}/{page}", Icons.Filled.PlayArrow, "ComicPage") } \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/model/Comic.kt b/app/src/main/java/com/acitelight/aether/model/Comic.kt index 930e687..ef9035b 100644 --- a/app/src/main/java/com/acitelight/aether/model/Comic.kt +++ b/app/src/main/java/com/acitelight/aether/model/Comic.kt @@ -1,8 +1,50 @@ package com.acitelight.aether.model -data class Comic( - val comic_name: String, - val page_count: Int, - val bookmarks: List, - val pages: List -) \ No newline at end of file +import com.acitelight.aether.service.ApiClient + +class Comic( + val comic: ComicResponse, + val id: String, + val token: String +) +{ + fun getPage(pageNumber: Int): String + { + return "${ApiClient.base}api/image/$id/${comic.list[pageNumber]}?token=$token" + } + + fun getPage(pageName: String): String? + { + val v = comic.list.indexOf(pageName) + if(v >= 0) + { + return getPage(v) + } + return null + } + + fun getPageIndex(pageName: String): Int + { + return comic.list.indexOf(pageName) + } + + fun getChapterLength(pageName: String): Int + { + var v = comic.list.indexOf(pageName) + if(v >= 0) + { + var r: Int = 1 + v+=1 + while(v < comic.list.size && !comic.bookmarks.any{ + x -> x.page == comic.list[v] + }){ + r++ + v+=1 + } + + return r + } + + return -1 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/model/ComicResponse.kt b/app/src/main/java/com/acitelight/aether/model/ComicResponse.kt new file mode 100644 index 0000000..5ba3c07 --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/model/ComicResponse.kt @@ -0,0 +1,9 @@ +package com.acitelight.aether.model + +data class ComicResponse( + val comic_name: String, + val page_count: Int, + val bookmarks: List, + val list: List, + val author: String +) \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt b/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt index d716807..5f94cc9 100644 --- a/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt +++ b/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt @@ -1,7 +1,7 @@ package com.acitelight.aether.service import com.acitelight.aether.model.ChallengeResponse -import com.acitelight.aether.model.Comic +import com.acitelight.aether.model.ComicResponse import com.acitelight.aether.model.VideoResponse import okhttp3.ResponseBody import retrofit2.http.Body @@ -9,7 +9,6 @@ import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query -import retrofit2.http.Streaming interface ApiInterface { @GET("api/video") @@ -28,18 +27,10 @@ interface ApiInterface { @Query("token") token: String ): VideoResponse - @GET("api/video/{klass}/{id}/nv") - @Streaming - suspend fun getNailVideo( - @Path("klass") klass: String, - @Path("id") id: String, - @Query("token") token: String - ): ResponseBody - - @GET("api/image/collections") - suspend fun getComicCollections(): List - @GET("api/image/meta") - suspend fun queryComicInfo(@Query("collection") collection: String): Comic + @GET("api/image") + suspend fun getComics(@Query("token") token: String): List + @GET("api/image/{id}") + suspend fun queryComicInfo(@Path("id") id: String, @Query("token") token: String): ComicResponse @GET("api/user/{user}") diff --git a/app/src/main/java/com/acitelight/aether/service/MediaManager.kt b/app/src/main/java/com/acitelight/aether/service/MediaManager.kt index 9de673e..e4f8703 100644 --- a/app/src/main/java/com/acitelight/aether/service/MediaManager.kt +++ b/app/src/main/java/com/acitelight/aether/service/MediaManager.kt @@ -1,10 +1,8 @@ package com.acitelight.aether.service import com.acitelight.aether.model.Comic +import com.acitelight.aether.model.ComicResponse import com.acitelight.aether.model.Video -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.io.IOException object MediaManager @@ -52,13 +50,23 @@ object MediaManager suspend fun listComics() : List { - // TODO: try - return ApiClient.api!!.getComicCollections() + try{ + val j = ApiClient.api!!.getComics(token) + return j + }catch (e: Exception) + { + return listOf() + } } - suspend fun queryComicInfo(c: String) : Comic + suspend fun queryComicInfo(id: String) : Comic? { - // TODO: try - return ApiClient.api!!.queryComicInfo(c) + try{ + val j = ApiClient.api!!.queryComicInfo(id, token) + return Comic(id = id, comic = j, token = token) + }catch (e: Exception) + { + return null + } } } \ 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 new file mode 100644 index 0000000..c61abc7 --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt @@ -0,0 +1,154 @@ +package com.acitelight.aether.view + +import androidx.compose.foundation.background +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.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import com.acitelight.aether.Global +import com.acitelight.aether.model.BookMark +import com.acitelight.aether.model.Comic +import com.acitelight.aether.viewModel.ComicGridViewModel +import java.util.EnumSet.range +import java.util.stream.IntStream.range + +@Composable +fun ComicGridView(comicId: String, navController: NavHostController, comicGridViewModel: ComicGridViewModel = viewModel()) +{ + comicGridViewModel.SetupClient() + comicGridViewModel.Resolve(comicId.hexToString()) + LazyColumn(modifier = Modifier.fillMaxWidth()) + { + items(comicGridViewModel.chapterList) + { + c -> + ChapterCard(comicGridViewModel.comic!!, navController, c, comicGridViewModel) + } + } +} + + +@Composable +fun ChapterCard(comic: Comic, navController: NavHostController, chapter: BookMark, comicGridViewModel: ComicGridViewModel = viewModel()) +{ + val c = chapter + val iv = comic.getPageIndex(c.page) + + Card( + shape = RoundedCornerShape(6.dp), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(16.dp), + onClick = { + val route = "comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(chapter.page)}" + navController.navigate(route) + } + ) { + Column(Modifier.fillMaxWidth()) + { + Row(Modifier.padding(12.dp)) + { + Box(Modifier + .height(260.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Color(0x44FFFFFF))) + { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(comic.getPage(c.page)) + .memoryCacheKey("${comic.id}/${c.page}") + .diskCacheKey("${comic.id}/${c.page}") + .build(), + contentDescription = null, + imageLoader = comicGridViewModel.imageLoader!!, + modifier = Modifier.padding(8.dp), + contentScale = ContentScale.Fit, + ) + } + + 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(8.dp)) { + items(r) + { + r -> + Card( + shape = RoundedCornerShape(12.dp), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .height(200.dp) + .padding(horizontal = 6.dp), + onClick = { + val route = "comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(r)}" + navController.navigate(route) + } + ){ + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(comic.getPage(r)) + .memoryCacheKey("${comic.id}/${r}") + .diskCacheKey("${comic.id}/${r}") + .build(), + contentDescription = null, + imageLoader = comicGridViewModel.imageLoader!!, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(12.dp)), + contentScale = ContentScale.Crop, + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt new file mode 100644 index 0000000..1b07642 --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt @@ -0,0 +1,182 @@ +package com.acitelight.aether.view + +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +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.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.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +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.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.max +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import com.acitelight.aether.ToggleFullScreen +import com.acitelight.aether.viewModel.ComicPageViewModel +import kotlinx.coroutines.launch + +@Composable +fun ComicPageView(comicId: String, page: String, navController: NavHostController, comicPageViewModel: ComicPageViewModel = viewModel()) +{ + comicPageViewModel.SetupClient() + comicPageViewModel.Resolve(comicId.hexToString(), page.toInt()) + + val title by comicPageViewModel.title + val pagerState = rememberPagerState(initialPage = page.toInt(), pageCount = { comicPageViewModel.pageList.size }) + var showPlane by comicPageViewModel.showPlane + + Box() + { + HorizontalPager( + state = pagerState, + modifier = Modifier.fillMaxSize().align(Alignment.Center).background(Color.Black).clickable(){ + showPlane = !showPlane + } + ) { + page -> + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(comicPageViewModel.comic!!.getPage(page)) + .memoryCacheKey("${comicPageViewModel.comic!!.id}/${page}") + .diskCacheKey("${comicPageViewModel.comic!!.id}/${page}") + .build(), + contentDescription = null, + imageLoader = comicPageViewModel.imageLoader!!, + modifier = Modifier.padding(8.dp).fillMaxSize(), + contentScale = ContentScale.Fit, + ) + } + + androidx.compose.animation.AnimatedVisibility( + visible = showPlane, + enter = slideInVertically(initialOffsetY = { fullHeight -> -fullHeight }), + exit = slideOutVertically(targetOffsetY = { fullHeight -> -fullHeight }), + modifier = Modifier + .align(Alignment.TopCenter) + ){ + Box() + { + Box(modifier = Modifier.height(180.dp).align(Alignment.TopCenter).fillMaxWidth().background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Black.copy(alpha = 0.75f), + Color.Transparent, + )))) + + + Row(modifier = Modifier + .fillMaxWidth() + .padding(top = 18.dp).padding(horizontal = 12.dp) + .height(60.dp) + .align(Alignment.TopCenter) + .background(Color(0x90FFFFFF), shape = RoundedCornerShape(12.dp))) + { + Text( + text = title, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + maxLines = 1, + modifier = Modifier.padding(8.dp).padding(horizontal = 10.dp).weight(1f).align(Alignment.CenterVertically) + ) + + Text( + text = "${pagerState.currentPage + 1}/${pagerState.pageCount}", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = Color.Black, + maxLines = 1, + modifier = Modifier.padding(8.dp).widthIn(120.dp).align(Alignment.CenterVertically) + ) + } + } + } + + androidx.compose.animation.AnimatedVisibility( + visible = showPlane, + enter = slideInVertically(initialOffsetY = { fullHeight -> fullHeight }), + exit = slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }), + modifier = Modifier + .align(Alignment.BottomCenter) + ) + { + Box{ + Box(modifier = Modifier.height(360.dp).align(Alignment.BottomCenter).fillMaxWidth().background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.90f), + )))) + + LazyRow (state = comicPageViewModel.listState!!, modifier = Modifier + .fillMaxWidth() + .padding(bottom = 18.dp).padding(horizontal = 12.dp) + .height(240.dp) + .align(Alignment.BottomCenter) + .background(Color(0x90999999), shape = RoundedCornerShape(12.dp))) + { + items(comicPageViewModel.pageList.size) + { + r -> + Card( + shape = RoundedCornerShape(12.dp), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 6.dp).padding(vertical = 6.dp), + onClick = { + pagerState.requestScrollToPage(page = r) + } + ){ + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(comicPageViewModel.comic!!.getPage(r)) + .memoryCacheKey("${comicPageViewModel.comic!!.id}/${r}") + .diskCacheKey("${comicPageViewModel.comic!!.id}/${r}") + .build(), + contentDescription = null, + imageLoader = comicPageViewModel.imageLoader!!, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(12.dp)), + contentScale = ContentScale.Fit, + ) + } + } + } + } + } + } +} \ No newline at end of file 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 040ae83..7621233 100644 --- a/app/src/main/java/com/acitelight/aether/view/ComicScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/ComicScreen.kt @@ -1,11 +1,128 @@ package com.acitelight.aether.view +import androidx.compose.foundation.background +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.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.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Tab +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel +import coil3.compose.AsyncImage +import com.acitelight.aether.model.Video +import com.acitelight.aether.viewModel.VideoScreenViewModel +import androidx.compose.material3.ScrollableTabRow +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.platform.LocalContext +import androidx.navigation.NavHostController +import coil3.request.ImageRequest +import com.acitelight.aether.Global +import com.acitelight.aether.model.Comic import com.acitelight.aether.viewModel.ComicScreenViewModel +import java.nio.charset.Charset @Composable -fun ComicScreen(comicScreenViewModel: ComicScreenViewModel = viewModel()) +fun ComicScreen(navController: NavHostController, comicScreenViewModel: ComicScreenViewModel = viewModel()) { + comicScreenViewModel.SetupClient() + 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) + } + } +} + +@Composable +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() }" + navController.navigate(route) + } + ) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Box(modifier = Modifier.fillMaxSize()){ + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(comic.getPage(0)) + .memoryCacheKey("${comic.id}/${0}") + .diskCacheKey("${comic.id}/${0}") + .build(), + contentDescription = null, + imageLoader = comicScreenViewModel.imageLoader!!, + modifier = Modifier + .fillMaxSize(), + contentScale = ContentScale.Crop, + ) + + + Box( + Modifier + .fillMaxWidth() + .height(24.dp) + .background( brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.45f) + ) + )) + .align(Alignment.BottomCenter)) + { + Text( + modifier = Modifier.align(Alignment.BottomEnd).padding(2.dp), + fontSize = 12.sp, + text = "${comic.comic.list.size} Pages", + fontWeight = FontWeight.Bold, + color = Color.White) + } + } + Text( + text = comic.comic.comic_name, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + maxLines = 2, + modifier = Modifier.padding(8.dp).background(Color.Transparent).heightIn(48.dp) + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt b/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt index 175143c..c5b9512 100644 --- a/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt +++ b/app/src/main/java/com/acitelight/aether/view/VideoScreen.kt @@ -72,7 +72,7 @@ fun VideoScreen(videoScreenViewModel: VideoScreenViewModel = viewModel(), navCon TopRow(videoScreenViewModel); LazyVerticalGrid( - columns = GridCells.Fixed(2), + columns = GridCells.Adaptive(200.dp), contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) diff --git a/app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt new file mode 100644 index 0000000..8ae2f2f --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt @@ -0,0 +1,45 @@ +package com.acitelight.aether.viewModel + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import coil3.ImageLoader +import coil3.network.okhttp.OkHttpNetworkFetcherFactory +import com.acitelight.aether.model.BookMark +import com.acitelight.aether.model.Comic +import com.acitelight.aether.service.ApiClient.createOkHttp +import com.acitelight.aether.service.MediaManager +import kotlinx.coroutines.launch + +class ComicGridViewModel : ViewModel() +{ + var imageLoader: ImageLoader? = null + var comic: Comic? = null + val chapterList = mutableStateListOf() + + @Composable + fun SetupClient() + { + val context = LocalContext.current + imageLoader = ImageLoader.Builder(context) + .components { + add(OkHttpNetworkFetcherFactory(createOkHttp())) + } + .build() + } + + fun Resolve(id: String) + { + if(comic != null) return + viewModelScope.launch { + comic = MediaManager.queryComicInfo(id) + val c = comic!! + for(i in c.comic.bookmarks) + { + chapterList.add(i) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt new file mode 100644 index 0000000..912b7d7 --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/viewModel/ComicPageViewModel.kt @@ -0,0 +1,62 @@ +package com.acitelight.aether.viewModel + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import coil3.ImageLoader +import coil3.network.okhttp.OkHttpNetworkFetcherFactory +import com.acitelight.aether.model.Comic +import com.acitelight.aether.service.ApiClient.createOkHttp +import com.acitelight.aether.service.MediaManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class ComicPageViewModel : ViewModel() +{ + var imageLoader: ImageLoader? = null + var comic: Comic? = null + var pageList = mutableStateListOf() + var title = mutableStateOf("") + var listState: LazyListState? = null + var coroutineScope: CoroutineScope? = null + var showPlane = mutableStateOf(false) + + @Composable + fun SetupClient() + { + val context = LocalContext.current + imageLoader = ImageLoader.Builder(context) + .components { + add(OkHttpNetworkFetcherFactory(createOkHttp())) + } + .build() + listState = rememberLazyListState() + coroutineScope = rememberCoroutineScope() + } + + @Composable + fun Resolve(id: String, page: Int) + { + if(comic != null) return + LaunchedEffect(id, page) { + coroutineScope?.launch { + comic = MediaManager.queryComicInfo(id) + comic?.let { + pageList.addAll(it.comic.list) + title.value = it.comic.comic_name + listState?.scrollToItem(index = page) + } + } + } + } +} \ No newline at end of file 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 f0d09e1..f7af104 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/ComicScreenViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/ComicScreenViewModel.kt @@ -1,24 +1,46 @@ package com.acitelight.aether.viewModel +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import coil3.ImageLoader +import coil3.network.okhttp.OkHttpNetworkFetcherFactory import com.acitelight.aether.model.Comic -import com.acitelight.aether.model.Video +import com.acitelight.aether.model.ComicResponse +import com.acitelight.aether.service.ApiClient.createOkHttp import com.acitelight.aether.service.MediaManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -class ComicScreenViewModel : ViewModel() -{ - private val _comics = MutableStateFlow>(emptyList()) - val comics: StateFlow> = _comics +class ComicScreenViewModel : ViewModel() { - init + var imageLoader: ImageLoader? = null; + + val comics = mutableStateListOf() + + @Composable + fun SetupClient() { - // viewModelScope.launch { - // val l = MediaManager.listComics() - // _comics.value = l.map { MediaManager.queryComicInfo(it) } - // } + val context = LocalContext.current + imageLoader = ImageLoader.Builder(context) + .components { + add(OkHttpNetworkFetcherFactory(createOkHttp())) + } + .build() + } + + init { + viewModelScope.launch { + val l = MediaManager.listComics() + for(i in l) + { + val m = MediaManager.queryComicInfo(i) + if(m != null) + comics.add(m) + } + } } } \ No newline at end of file