[feat] Comic Resume

This commit is contained in:
acite
2025-09-02 19:08:11 +08:00
parent daa66a9ecc
commit 18d021a8e5
11 changed files with 427 additions and 129 deletions

View File

@@ -3,6 +3,7 @@ plugins {
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "1.9.0" kotlin("plugin.serialization") version "1.9.0"
id("kotlin-kapt")
} }
android { android {
@@ -42,6 +43,9 @@ android {
} }
dependencies { dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
kapt("androidx.room:room-compiler:2.7.2")
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
implementation(libs.bcprov.jdk15on) implementation(libs.bcprov.jdk15on)
implementation(libs.converter.gson) implementation(libs.converter.gson)

View File

@@ -47,4 +47,22 @@ class Comic(
return -1 return -1
} }
fun getPageChapterIndex(page: Int): Pair<BookMark, Int>?
{
var p = page
while(p >= 0 && !comic.bookmarks.any{ x -> x.page == comic.list[p] })
{
p--
}
for(i in comic.bookmarks)
{
if(i.page == comic.list[p])
{
return Pair(i, page - comic.list.indexOf(i.page) + 1)
}
}
return null
}
} }

View File

@@ -0,0 +1,13 @@
package com.acitelight.aether.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class ComicRecord(
@PrimaryKey(autoGenerate = false) val id: Int = 0,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "position") val position: Int
)

View File

@@ -0,0 +1,27 @@
package com.acitelight.aether.model
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface ComicRecordDao {
@Query("SELECT * FROM comicrecord")
fun getAll(): Flow<List<ComicRecord>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(rec: ComicRecord)
@Update
suspend fun update(rec: ComicRecord)
@Delete
suspend fun delete(rec: ComicRecord)
@Query("SELECT * FROM comicrecord WHERE id = :id")
suspend fun getById(id: Int): ComicRecord?
}

View File

@@ -0,0 +1,29 @@
package com.acitelight.aether.model
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [ComicRecord::class], version = 1)
abstract class ComicRecordDatabase : RoomDatabase() {
abstract fun userDao(): ComicRecordDao
companion object {
@Volatile
private var INSTANCE: ComicRecordDatabase? = null
fun getDatabase(context: Context): ComicRecordDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
ComicRecordDatabase::class.java,
"comicrecord_database"
).build()
INSTANCE = instance
instance
}
}
}
}

View File

@@ -1,6 +1,7 @@
package com.acitelight.aether.view package com.acitelight.aether.view
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -20,7 +21,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -37,28 +40,113 @@ import androidx.navigation.NavHostController
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import coil3.request.ImageRequest import coil3.request.ImageRequest
import com.acitelight.aether.Global import com.acitelight.aether.Global
import com.acitelight.aether.ToggleFullScreen
import com.acitelight.aether.model.BookMark import com.acitelight.aether.model.BookMark
import com.acitelight.aether.model.Comic import com.acitelight.aether.model.Comic
import com.acitelight.aether.model.ComicRecordDatabase
import com.acitelight.aether.viewModel.ComicGridViewModel import com.acitelight.aether.viewModel.ComicGridViewModel
import java.util.EnumSet.range import java.util.EnumSet.range
import java.util.stream.IntStream.range import java.util.stream.IntStream.range
@Composable @Composable
fun ComicGridView(comicId: String, navController: NavHostController, comicGridViewModel: ComicGridViewModel = viewModel()) fun ComicGridView(comicId: String, navController: NavHostController, comicGridViewModel: ComicGridViewModel = viewModel()) {
{
comicGridViewModel.SetupClient() comicGridViewModel.SetupClient()
comicGridViewModel.Resolve(comicId.hexToString()) comicGridViewModel.Resolve(comicId.hexToString())
LazyColumn(modifier = Modifier.fillMaxWidth()) comicGridViewModel.updateProcess(comicId.hexToString()){}
{ ToggleFullScreen(false)
items(comicGridViewModel.chapterList)
{ val context = LocalContext.current
c -> val comic by comicGridViewModel.comic
ChapterCard(comicGridViewModel.comic!!, navController, c, comicGridViewModel) val record by comicGridViewModel.record
if (comic != null) {
Column {
Box(
Modifier
.padding(horizontal = 16.dp).padding(top = 36.dp)
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp))
)
{
Column {
Text(
text = comic!!.comic.comic_name,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1,
modifier = Modifier.padding(4.dp)
)
Text(
text = comic!!.comic.author,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1,
modifier = Modifier.padding(4.dp).fillMaxWidth()
)
}
}
LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f).padding(top = 6.dp).clip(RoundedCornerShape(6.dp)))
{
items(comicGridViewModel.chapterList)
{ c ->
ChapterCard(comic!!, navController, c, comicGridViewModel)
}
}
Box(
Modifier
.padding(horizontal = 16.dp).padding(top = 6.dp).padding(bottom = 20.dp).heightIn(min = 42.dp)
.background(Color.White.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp))
.clickable{
comicGridViewModel.updateProcess(comicId.hexToString())
{
if(record != null) {
val k = comic!!.getPageChapterIndex(record!!.position)!!
val route = "comic_page_route/${"${comic!!.id}".toHex()}/${
comic!!.getPageIndex(k.first.page)
}"
navController.navigate(route)
}else
{
val route = "comic_page_route/${"${comic!!.id}".toHex()}/${0}"
navController.navigate(route)
}
}
}
)
{
Row(Modifier.fillMaxWidth().align(Alignment.Center).padding(horizontal = 8.dp)) {
if(record != null)
{
val k = comic!!.getPageChapterIndex(record!!.position)!!
Text(
text = "Last Read Position: ${k.first.name} ${k.second}/${comic!!.getChapterLength(k.first.page)}",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1,
modifier = Modifier.padding(4.dp).weight(1f)
)
}else{
Text(
text = "Read from scratch",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1,
modifier = Modifier.padding(4.dp).weight(1f)
)
}
}
}
} }
} }
} }
@Composable @Composable
fun ChapterCard(comic: Comic, navController: NavHostController, chapter: BookMark, comicGridViewModel: ComicGridViewModel = viewModel()) fun ChapterCard(comic: Comic, navController: NavHostController, chapter: BookMark, comicGridViewModel: ComicGridViewModel = viewModel())
{ {
@@ -70,7 +158,7 @@ fun ChapterCard(comic: Comic, navController: NavHostController, chapter: BookMar
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(16.dp), .padding(horizontal = 16.dp).padding(vertical = 6.dp),
onClick = { onClick = {
val route = "comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(chapter.page)}" val route = "comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(chapter.page)}"
navController.navigate(route) navController.navigate(route)
@@ -78,10 +166,10 @@ fun ChapterCard(comic: Comic, navController: NavHostController, chapter: BookMar
) { ) {
Column(Modifier.fillMaxWidth()) Column(Modifier.fillMaxWidth())
{ {
Row(Modifier.padding(12.dp)) Row(Modifier.padding(6.dp))
{ {
Box(Modifier Box(Modifier
.height(260.dp) .height(170.dp)
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(Color(0x44FFFFFF))) .background(Color(0x44FFFFFF)))
{ {
@@ -117,7 +205,7 @@ fun ChapterCard(comic: Comic, navController: NavHostController, chapter: BookMar
} }
val r = comic.comic.list.subList(iv, iv + comic.getChapterLength(c.page)) val r = comic.comic.list.subList(iv, iv + comic.getChapterLength(c.page))
LazyRow(modifier = Modifier.fillMaxWidth().padding(8.dp)) { LazyRow(modifier = Modifier.fillMaxWidth().padding(6.dp)) {
items(r) items(r)
{ {
r -> r ->
@@ -126,7 +214,7 @@ fun ChapterCard(comic: Comic, navController: NavHostController, chapter: BookMar
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.height(200.dp) .height(140.dp)
.padding(horizontal = 6.dp), .padding(horizontal = 6.dp),
onClick = { onClick = {
val route = "comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(r)}" val route = "comic_page_route/${"${comic.id}".toHex()}/${comic.getPageIndex(r)}"

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -25,6 +26,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -44,6 +46,7 @@ import androidx.navigation.NavHostController
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import coil3.request.ImageRequest import coil3.request.ImageRequest
import com.acitelight.aether.ToggleFullScreen import com.acitelight.aether.ToggleFullScreen
import com.acitelight.aether.model.ComicRecord
import com.acitelight.aether.viewModel.ComicPageViewModel import com.acitelight.aether.viewModel.ComicPageViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -57,122 +60,188 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll
val pagerState = rememberPagerState(initialPage = page.toInt(), pageCount = { comicPageViewModel.pageList.size }) val pagerState = rememberPagerState(initialPage = page.toInt(), pageCount = { comicPageViewModel.pageList.size })
var showPlane by comicPageViewModel.showPlane var showPlane by comicPageViewModel.showPlane
Box() comicPageViewModel.UpdateProcess(pagerState.currentPage)
{
HorizontalPager( val comic by comicPageViewModel.comic
state = pagerState, comic?.let {
modifier = Modifier.fillMaxSize().align(Alignment.Center).background(Color.Black).clickable(){ Box()
showPlane = !showPlane {
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize().align(Alignment.Center).background(Color.Black).clickable{
showPlane = !showPlane
if(showPlane)
{
comicPageViewModel.coroutineScope?.launch {
comicPageViewModel.listState?.scrollToItem(index = pagerState.currentPage)
}
}
}
) {
page ->
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(it.getPage(page))
.memoryCacheKey("${it.id}/${page}")
.diskCacheKey("${it.id}/${page}")
.build(),
contentDescription = null,
imageLoader = comicPageViewModel.imageLoader!!,
modifier = Modifier.padding(8.dp).fillMaxSize(),
contentScale = ContentScale.Fit,
)
} }
) {
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( androidx.compose.animation.AnimatedVisibility(
visible = showPlane, visible = showPlane,
enter = slideInVertically(initialOffsetY = { fullHeight -> -fullHeight }), enter = slideInVertically(initialOffsetY = { fullHeight -> -fullHeight }),
exit = slideOutVertically(targetOffsetY = { fullHeight -> -fullHeight }), exit = slideOutVertically(targetOffsetY = { fullHeight -> -fullHeight }),
modifier = Modifier 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) .align(Alignment.TopCenter)
.background(Color(0x90FFFFFF), shape = RoundedCornerShape(12.dp))) ){
Box()
{ {
Text( Box(modifier = Modifier.height(240.dp).align(Alignment.TopCenter).fillMaxWidth().background(
text = title, brush = Brush.verticalGradient(
fontSize = 20.sp, colors = listOf(
fontWeight = FontWeight.Bold, Color.Black.copy(alpha = 0.75f),
color = Color.Black, Color.Transparent,
maxLines = 1, ))))
modifier = Modifier.padding(8.dp).padding(horizontal = 10.dp).weight(1f).align(Alignment.CenterVertically)
)
Text( Column(Modifier.align(Alignment.TopCenter).fillMaxWidth())
text = "${pagerState.currentPage + 1}/${pagerState.pageCount}", {
fontSize = 24.sp, Row(modifier = Modifier
fontWeight = FontWeight.Bold, .fillMaxWidth()
color = Color.Black, .padding(top = 18.dp).padding(horizontal = 12.dp)
maxLines = 1, .height(42.dp)
modifier = Modifier.padding(8.dp).widthIn(120.dp).align(Alignment.CenterVertically) .background(Color(0x90FFFFFF), shape = RoundedCornerShape(12.dp)))
) {
Text(
text = title,
fontSize = 16.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 = 18.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1,
modifier = Modifier.padding(8.dp).widthIn(min = 60.dp).align(Alignment.CenterVertically)
)
}
Row(modifier = Modifier
.padding(top = 6.dp).padding(horizontal = 12.dp)
.height(42.dp)
.background(Color(0x90FFFFFF), shape = RoundedCornerShape(12.dp)))
{
val k = it.getPageChapterIndex(pagerState.currentPage)!!
Text(
text = k.first.name,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1,
modifier = Modifier.padding(8.dp).padding(horizontal = 10.dp).align(Alignment.CenterVertically)
)
Text(
text = "${k.second}/${it.getChapterLength(k.first.page)}",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
maxLines = 1,
modifier = Modifier.padding(8.dp).widthIn(min = 60.dp).align(Alignment.CenterVertically)
)
}
}
} }
} }
}
androidx.compose.animation.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
visible = showPlane, visible = showPlane,
enter = slideInVertically(initialOffsetY = { fullHeight -> fullHeight }), enter = slideInVertically(initialOffsetY = { fullHeight -> fullHeight }),
exit = slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }), exit = slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }),
modifier = Modifier 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) .align(Alignment.BottomCenter)
.background(Color(0x90999999), shape = RoundedCornerShape(12.dp))) )
{ {
items(comicPageViewModel.pageList.size) 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)))
{ {
r -> items(comicPageViewModel.pageList.size)
Card( {
shape = RoundedCornerShape(12.dp), r ->
modifier = Modifier Card(
.fillMaxWidth() shape = RoundedCornerShape(12.dp),
.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 modifier = Modifier
.fillMaxSize() .fillMaxWidth()
.clip(RoundedCornerShape(12.dp)), .wrapContentHeight()
contentScale = ContentScale.Fit, .padding(horizontal = 6.dp).padding(vertical = 6.dp),
) onClick = {
pagerState.requestScrollToPage(page = r)
}
){
Box(Modifier.fillMaxSize())
{
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(it.getPage(r))
.memoryCacheKey("${it.id}/${r}")
.diskCacheKey("${it.id}/${r}")
.build(),
contentDescription = null,
imageLoader = comicPageViewModel.imageLoader!!,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(12.dp))
.align(Alignment.Center),
contentScale = ContentScale.Fit,
)
val k = it.getPageChapterIndex(r)!!
Box(Modifier
.align(Alignment.TopEnd)
.padding(6.dp)
.background(Color.Black.copy(alpha = 0.65f), shape = RoundedCornerShape(12.dp)))
{
Row{
Text(
text = k.first.name,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
maxLines = 1,
modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically)
)
Text(
text = "${k.second}/${it.getChapterLength(k.first.page)}",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
maxLines = 1,
modifier = Modifier.padding(4.dp).fillMaxWidth().align(Alignment.CenterVertically)
)
}
}
}
}
} }
} }
} }

View File

@@ -72,7 +72,7 @@ fun VideoScreen(videoScreenViewModel: VideoScreenViewModel = viewModel(), navCon
TopRow(videoScreenViewModel); TopRow(videoScreenViewModel);
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Adaptive(200.dp), columns = GridCells.Adaptive(160.dp),
contentPadding = PaddingValues(8.dp), contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)

View File

@@ -2,6 +2,8 @@ package com.acitelight.aether.viewModel
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -9,15 +11,20 @@ import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.model.BookMark import com.acitelight.aether.model.BookMark
import com.acitelight.aether.model.Comic import com.acitelight.aether.model.Comic
import com.acitelight.aether.model.ComicRecord
import com.acitelight.aether.model.ComicRecordDatabase
import com.acitelight.aether.service.ApiClient.createOkHttp import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.MediaManager import com.acitelight.aether.service.MediaManager
import com.acitelight.aether.view.hexToString
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ComicGridViewModel : ViewModel() class ComicGridViewModel : ViewModel()
{ {
var imageLoader: ImageLoader? = null var imageLoader: ImageLoader? = null
var comic: Comic? = null var comic = mutableStateOf<Comic?>(null)
val chapterList = mutableStateListOf<BookMark>() val chapterList = mutableStateListOf<BookMark>()
var db: ComicRecordDatabase? = null
var record = mutableStateOf<ComicRecord?>(null)
@Composable @Composable
fun SetupClient() fun SetupClient()
@@ -28,18 +35,33 @@ class ComicGridViewModel : ViewModel()
add(OkHttpNetworkFetcherFactory(createOkHttp())) add(OkHttpNetworkFetcherFactory(createOkHttp()))
} }
.build() .build()
db = remember {
try{
ComicRecordDatabase.getDatabase(context)
}catch (e: Exception) {
print(e.message)
} as ComicRecordDatabase?
}
} }
fun Resolve(id: String) fun Resolve(id: String)
{ {
if(comic != null) return if(comic.value != null) return
viewModelScope.launch { viewModelScope.launch {
comic = MediaManager.queryComicInfo(id) comic.value = MediaManager.queryComicInfo(id)
val c = comic!! val c = comic.value!!
for(i in c.comic.bookmarks) for(i in c.comic.bookmarks)
{ {
chapterList.add(i) chapterList.add(i)
} }
} }
} }
fun updateProcess(id: String, callback: () -> Unit)
{
viewModelScope.launch {
record.value = db?.userDao()?.getById(id.toInt())
callback()
}
}
} }

View File

@@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -16,20 +17,24 @@ import androidx.lifecycle.viewModelScope
import coil3.ImageLoader import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.model.Comic import com.acitelight.aether.model.Comic
import com.acitelight.aether.model.ComicRecord
import com.acitelight.aether.model.ComicRecordDatabase
import com.acitelight.aether.service.ApiClient.createOkHttp import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.MediaManager import com.acitelight.aether.service.MediaManager
import com.acitelight.aether.view.hexToString
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ComicPageViewModel : ViewModel() class ComicPageViewModel : ViewModel()
{ {
var imageLoader: ImageLoader? = null var imageLoader: ImageLoader? = null
var comic: Comic? = null var comic = mutableStateOf<Comic?>(null)
var pageList = mutableStateListOf<String>() var pageList = mutableStateListOf<String>()
var title = mutableStateOf<String>("") var title = mutableStateOf<String>("")
var listState: LazyListState? = null var listState: LazyListState? = null
var coroutineScope: CoroutineScope? = null var coroutineScope: CoroutineScope? = null
var showPlane = mutableStateOf(false) var showPlane = mutableStateOf(true)
var db: ComicRecordDatabase? = null
@Composable @Composable
fun SetupClient() fun SetupClient()
@@ -42,21 +47,38 @@ class ComicPageViewModel : ViewModel()
.build() .build()
listState = rememberLazyListState() listState = rememberLazyListState()
coroutineScope = rememberCoroutineScope() coroutineScope = rememberCoroutineScope()
db = remember {
try{
ComicRecordDatabase.getDatabase(context)
}catch (e: Exception) {
print(e.message)
} as ComicRecordDatabase?
}
} }
@Composable @Composable
fun Resolve(id: String, page: Int) fun Resolve(id: String, page: Int)
{ {
if(comic != null) return if(comic.value != null) return
LaunchedEffect(id, page) { LaunchedEffect(id, page) {
coroutineScope?.launch { coroutineScope?.launch {
comic = MediaManager.queryComicInfo(id) comic.value = MediaManager.queryComicInfo(id)
comic?.let { comic.value?.let {
pageList.addAll(it.comic.list) pageList.addAll(it.comic.list)
title.value = it.comic.comic_name title.value = it.comic.comic_name
listState?.scrollToItem(index = page) listState?.scrollToItem(index = page)
UpdateProcess(page)
} }
} }
} }
} }
fun UpdateProcess(page: Int)
{
if(comic.value == null) return
coroutineScope?.launch {
db?.userDao()?.insert(ComicRecord(id = comic.value!!.id.toInt(), name = comic.value!!.comic.comic_name, position = page))
}
}
} }

View File

@@ -25,6 +25,9 @@ okhttp = "5.1.0"
retrofit = "3.0.0" retrofit = "3.0.0"
retrofit2KotlinxSerializationConverter = "1.0.0" retrofit2KotlinxSerializationConverter = "1.0.0"
media3DatasourceOkhttp = "1.8.0" media3DatasourceOkhttp = "1.8.0"
roomCompiler = "2.7.2"
roomKtx = "2.7.2"
roomRuntime = "2.7.2"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -34,6 +37,9 @@ androidx-media3-common = { module = "androidx.media3:media3-common", version.ref
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" } androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" }
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Ui" } androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Ui" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bcprovJdk15on" } bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk15on", version.ref = "bcprovJdk15on" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilNetworkOkhttp" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilNetworkOkhttp" }