[feat] Comic Reader
This commit is contained in:
@@ -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")
|
||||
}
|
||||
@@ -1,8 +1,50 @@
|
||||
package com.acitelight.aether.model
|
||||
|
||||
data class Comic(
|
||||
val comic_name: String,
|
||||
val page_count: Int,
|
||||
val bookmarks: List<BookMark>,
|
||||
val pages: List<String>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.acitelight.aether.model
|
||||
|
||||
data class ComicResponse(
|
||||
val comic_name: String,
|
||||
val page_count: Int,
|
||||
val bookmarks: List<BookMark>,
|
||||
val list: List<String>,
|
||||
val author: String
|
||||
)
|
||||
@@ -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<String>
|
||||
@GET("api/image/meta")
|
||||
suspend fun queryComicInfo(@Query("collection") collection: String): Comic
|
||||
@GET("api/image")
|
||||
suspend fun getComics(@Query("token") token: String): List<String>
|
||||
@GET("api/image/{id}")
|
||||
suspend fun queryComicInfo(@Path("id") id: String, @Query("token") token: String): ComicResponse
|
||||
|
||||
|
||||
@GET("api/user/{user}")
|
||||
|
||||
@@ -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<String>
|
||||
{
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
154
app/src/main/java/com/acitelight/aether/view/ComicGridView.kt
Normal file
154
app/src/main/java/com/acitelight/aether/view/ComicGridView.kt
Normal file
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
182
app/src/main/java/com/acitelight/aether/view/ComicPageView.kt
Normal file
182
app/src/main/java/com/acitelight/aether/view/ComicPageView.kt
Normal file
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<BookMark>()
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String>()
|
||||
var title = mutableStateOf<String>("")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<List<Comic>>(emptyList())
|
||||
val comics: StateFlow<List<Comic>> = _comics
|
||||
class ComicScreenViewModel : ViewModel() {
|
||||
|
||||
init
|
||||
var imageLoader: ImageLoader? = null;
|
||||
|
||||
val comics = mutableStateListOf<Comic>()
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user