From 514e99d7db461201d4d1525f91df055972105a69 Mon Sep 17 00:00:00 2001 From: acite <1498045907@qq.com> Date: Fri, 5 Sep 2025 12:57:50 +0800 Subject: [PATCH] [feat] Comic Bookmark --- .../java/com/acitelight/aether/model/Comic.kt | 5 +- .../acitelight/aether/service/ApiInterface.kt | 3 + .../acitelight/aether/service/MediaManager.kt | 12 +++ .../com/acitelight/aether/view/BookmarkPop.kt | 60 ++++++++++++ .../acitelight/aether/view/ComicGridView.kt | 19 +--- .../acitelight/aether/view/ComicPageView.kt | 95 ++++++++++++++----- .../aether/viewModel/ComicGridViewModel.kt | 17 ++-- 7 files changed, 161 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/com/acitelight/aether/view/BookmarkPop.kt 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 51dc9d9..a6819d7 100644 --- a/app/src/main/java/com/acitelight/aether/model/Comic.kt +++ b/app/src/main/java/com/acitelight/aether/model/Comic.kt @@ -48,13 +48,14 @@ class Comic( return -1 } - fun getPageChapterIndex(page: Int): Pair? + fun getPageChapterIndex(page: Int): Pair { var p = page while(p >= 0 && !comic.bookmarks.any{ x -> x.page == comic.list[p] }) { p-- } + if(p < 0) return Pair(BookMark(name="null", page=comic.list[0]), page + 1) for(i in comic.bookmarks) { if(i.page == comic.list[p]) @@ -63,6 +64,6 @@ class Comic( } } - return null + return Pair(BookMark(name="null", page=comic.list[0]), page + 1) } } \ 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 5f94cc9..8dbe9db 100644 --- a/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt +++ b/app/src/main/java/com/acitelight/aether/service/ApiInterface.kt @@ -1,5 +1,6 @@ package com.acitelight.aether.service +import com.acitelight.aether.model.BookMark import com.acitelight.aether.model.ChallengeResponse import com.acitelight.aether.model.ComicResponse import com.acitelight.aether.model.VideoResponse @@ -32,6 +33,8 @@ interface ApiInterface { @GET("api/image/{id}") suspend fun queryComicInfo(@Path("id") id: String, @Query("token") token: String): ComicResponse + @POST("api/image/{id}/bookmark") + suspend fun postBookmark(@Path("id") id: String, @Query("token") token: String, @Body bookmark: BookMark) @GET("api/user/{user}") suspend fun getChallenge( 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 e4f8703..9a7cccc 100644 --- a/app/src/main/java/com/acitelight/aether/service/MediaManager.kt +++ b/app/src/main/java/com/acitelight/aether/service/MediaManager.kt @@ -1,5 +1,6 @@ package com.acitelight.aether.service +import com.acitelight.aether.model.BookMark import com.acitelight.aether.model.Comic import com.acitelight.aether.model.ComicResponse import com.acitelight.aether.model.Video @@ -69,4 +70,15 @@ object MediaManager return null } } + + suspend fun postBookmark(id: String, bookMark: BookMark): Boolean + { + try{ + val j = ApiClient.api!!.postBookmark(id, token, bookMark) + return true + }catch (e: Exception) + { + return false + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/view/BookmarkPop.kt b/app/src/main/java/com/acitelight/aether/view/BookmarkPop.kt new file mode 100644 index 0000000..749e8a0 --- /dev/null +++ b/app/src/main/java/com/acitelight/aether/view/BookmarkPop.kt @@ -0,0 +1,60 @@ +package com.acitelight.aether.view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.DialogProperties + +@Composable +fun BookmarkPop( + onDismiss: () -> Unit, + onConfirm: (String) -> Unit +) +{ + var inputValue by remember { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismiss, + title = { + Text("Bookmark", style = MaterialTheme.typography.headlineMedium) + }, + text = { + Column { + OutlinedTextField( + value = inputValue, + onValueChange = { inputValue = it }, + label = { Text("Bookmark") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + } + }, + confirmButton = { + TextButton( + onClick = { onConfirm(inputValue) }, + enabled = inputValue.isNotBlank() + ) { + Text("Confirm") + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text("Cancel") + } + }, + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt b/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt index 427e722..f89f227 100644 --- a/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt +++ b/app/src/main/java/com/acitelight/aether/view/ComicGridView.kt @@ -2,12 +2,9 @@ package com.acitelight.aether.view 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.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 @@ -22,36 +19,28 @@ import androidx.compose.material3.Card import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember 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.ToggleFullScreen import com.acitelight.aether.model.BookMark import com.acitelight.aether.model.Comic -import com.acitelight.aether.model.ComicRecordDatabase 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()) + comicGridViewModel.resolve(comicId.hexToString()) comicGridViewModel.updateProcess(comicId.hexToString()){} ToggleFullScreen(false) @@ -104,9 +93,9 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi comicGridViewModel.updateProcess(comicId.hexToString()) { if(record != null) { - val k = comic!!.getPageChapterIndex(record!!.position)!! + val k = comic!!.getPageChapterIndex(record!!.position) val route = "comic_page_route/${"${comic!!.id}".toHex()}/${ - comic!!.getPageIndex(k.first.page) + record!!.position }" navController.navigate(route) }else @@ -121,7 +110,7 @@ fun ComicGridView(comicId: String, navController: NavHostController, comicGridVi Row(Modifier.fillMaxWidth().align(Alignment.Center).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(k.first.page)}", diff --git a/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt index 47e3b3d..35d4b63 100644 --- a/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt +++ b/app/src/main/java/com/acitelight/aether/view/ComicPageView.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -23,11 +24,17 @@ 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.material.icons.Icons +import androidx.compose.material.icons.filled.Bookmarks +import androidx.compose.material.icons.filled.Key import androidx.compose.material3.Card +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -46,7 +53,9 @@ 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.ComicRecord +import com.acitelight.aether.service.MediaManager import com.acitelight.aether.viewModel.ComicPageViewModel import kotlinx.coroutines.launch @@ -59,6 +68,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll val title by comicPageViewModel.title val pagerState = rememberPagerState(initialPage = page.toInt(), pageCount = { comicPageViewModel.pageList.size }) var showPlane by comicPageViewModel.showPlane + var showBookMarkPop by remember { mutableStateOf(false) } comicPageViewModel.UpdateProcess(pagerState.currentPage) @@ -101,7 +111,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll ){ Box() { - Box(modifier = Modifier.height(240.dp).align(Alignment.TopCenter).fillMaxWidth().background( + Box(modifier = Modifier.height(160.dp).align(Alignment.TopCenter).fillMaxWidth().background( brush = Brush.verticalGradient( colors = listOf( Color.Black.copy(alpha = 0.75f), @@ -134,29 +144,52 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll 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) - ) + Row { + 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) + ) + } + + Spacer(Modifier.weight(1f)) + + Row(modifier = Modifier + .padding(top = 6.dp).padding(horizontal = 12.dp) + .height(42.dp).width(42.dp) + .background(Color(0x90FFFFFF), shape = RoundedCornerShape(12.dp))) + { + Box (Modifier.clickable { + showBookMarkPop = true + }){ + Icon( + Icons.Filled.Bookmarks, + tint = Color.Black, + modifier = Modifier + .fillMaxSize() + .padding(8.dp), + contentDescription = "Bookmark") + } + } } } } @@ -214,7 +247,7 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll .align(Alignment.Center), contentScale = ContentScale.Fit, ) - val k = it.getPageChapterIndex(r)!! + val k = it.getPageChapterIndex(r) Box(Modifier .align(Alignment.TopEnd) .padding(6.dp) @@ -248,4 +281,18 @@ fun ComicPageView(comicId: String, page: String, navController: NavHostControll } } } + + if(showBookMarkPop) + { + BookmarkPop({ + showBookMarkPop = false + }, { + s -> + showBookMarkPop = false + comicPageViewModel.coroutineScope?.launch { + MediaManager.postBookmark(comicId.hexToString(), BookMark(name = s, page = comicPageViewModel.pageList[pagerState.currentPage])) + comicPageViewModel.comic.value = MediaManager.queryComicInfo(comicId.hexToString()) + } + }); + } } \ No newline at end of file diff --git a/app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt index 7b4a68e..eff9617 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/ComicGridViewModel.kt @@ -15,7 +15,6 @@ import com.acitelight.aether.model.ComicRecord import com.acitelight.aether.model.ComicRecordDatabase import com.acitelight.aether.service.ApiClient.createOkHttp import com.acitelight.aether.service.MediaManager -import com.acitelight.aether.view.hexToString import kotlinx.coroutines.launch class ComicGridViewModel : ViewModel() @@ -44,16 +43,16 @@ class ComicGridViewModel : ViewModel() } } - fun Resolve(id: String) + fun resolve(id: String) { - if(comic.value != null) return viewModelScope.launch { - comic.value = MediaManager.queryComicInfo(id) - val c = comic.value!! - for(i in c.comic.bookmarks) - { - chapterList.add(i) - } + if(comic.value == null) { + comic.value = MediaManager.queryComicInfo(id) + val c = comic.value!! + for (i in c.comic.bookmarks) { + chapterList.add(i) + } + }else comic.value = MediaManager.queryComicInfo(id) } }