From c3c5761ffa8b97891991fa431a87f42db7ea5fa7 Mon Sep 17 00:00:00 2001 From: tom5079 Date: Sun, 26 Dec 2021 21:19:51 +0900 Subject: [PATCH] [Manatoki] Implemented next episode button in reader [Hitomi] Adjusted Search result padding --- .../pupil/sources/composable/ReaderBase.kt | 15 +- .../hitomi/composable/SearchResultEntry.kt | 2 +- .../sources/manatoki/composable/Reader.kt | 139 ++++++++++-------- .../xyz/quaver/pupil/sources/manatoki/util.kt | 26 +++- 4 files changed, 112 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt index efa0c08b..fc681c16 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/ReaderBase.kt @@ -520,9 +520,16 @@ fun ReaderItem( } val modifier = when { - imageSize == null -> Modifier.weight(1f).height(heightDp) - readerOptions.padding -> Modifier.fillMaxHeight().widthIn(0.dp, widthDp/images.size).aspectRatio(imageSize.width/imageSize.height) - readerOptions.orientation.isVertical -> Modifier.weight(1f).aspectRatio(imageSize.width/imageSize.height) + imageSize == null -> Modifier + .weight(1f) + .height(heightDp) + readerOptions.padding -> Modifier + .fillMaxHeight() + .widthIn(0.dp, widthDp / images.size) + .aspectRatio(imageSize.width / imageSize.height) + readerOptions.orientation.isVertical -> Modifier + .weight(1f) + .aspectRatio(imageSize.width / imageSize.height) else -> Modifier.aspectRatio(imageSize.width/imageSize.height) } @@ -627,6 +634,7 @@ fun LazyListScope.ReaderLazyListContent( fun ReaderBase( modifier: Modifier = Modifier, model: ReaderBaseViewModel, + listState: LazyListState = rememberLazyListState(), onScroll: (direction: Float) -> Unit = { } ) { val context = LocalContext.current @@ -687,7 +695,6 @@ fun ReaderBase( } ) { var listSize: Size? by remember { mutableStateOf(null) } - val listState = rememberLazyListState() val nestedScrollConnection = remember { object: NestedScrollConnection { override suspend fun onPreFling(available: Velocity): Velocity { diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/composable/SearchResultEntry.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/composable/SearchResultEntry.kt index 840bd94c..3fd2a4f8 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/composable/SearchResultEntry.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/composable/SearchResultEntry.kt @@ -109,7 +109,7 @@ fun DetailedSearchResult( Card( modifier = Modifier - .padding(8.dp) + .padding(8.dp, 4.dp) .fillMaxWidth() .clickable { onClick(result) }, elevation = 4.dp diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Reader.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Reader.kt index 0865e21a..e24640f6 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Reader.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/composable/Reader.kt @@ -18,9 +18,7 @@ package xyz.quaver.pupil.sources.manatoki.composable -import android.util.Log import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.ExperimentalFoundationApi @@ -30,15 +28,11 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyListItemInfo import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.List -import androidx.compose.material.icons.filled.NavigateBefore -import androidx.compose.material.icons.filled.Star -import androidx.compose.material.icons.filled.StarOutline +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.saveable.rememberSaveable @@ -115,7 +109,8 @@ fun Reader(navController: NavController) { ).calculateBottomPadding().toPx() } - val listState = rememberLazyListState() + val bottomSheetListState = rememberLazyListState() + val readerListState = rememberLazyListState() var scrollDirection by remember { mutableStateOf(0f) } @@ -140,17 +135,10 @@ fun Reader(navController: NavController) { mangaListingListSize = it }, rippleInteractionSource = mangaListingRippleInteractionSource, - listState = listState + listState = bottomSheetListState ) { - coroutineScope.launch { - client.getItem( - it, - onReader = { - navController.navigate("manatoki.net/reader/${it.itemID}") { - popUpTo("manatoki.net/") - } - } - ) + navController.navigate("manatoki.net/reader/$it") { + popUpTo("manatoki.net/") } } } @@ -205,70 +193,96 @@ fun Reader(navController: NavController) { ) }, floatingActionButton = { - val scale by animateFloatAsState(if (model.fullscreen || scrollDirection < 0f) 0f else 1f) + val showNextButton by derivedStateOf { + (readerInfo?.nextItemID?.isNotEmpty() == true) && with (readerListState.layoutInfo) { + visibleItemsInfo.lastOrNull()?.index == totalItemsCount-1 + } + } + val scale by animateFloatAsState(if (!showNextButton && (model.fullscreen || scrollDirection < 0f)) 0f else 1f) if (scale > 0f) FloatingActionButton( - modifier = Modifier.navigationBarsPadding().scale(scale), + modifier = Modifier + .navigationBarsPadding() + .scale(scale), onClick = { readerInfo?.let { - coroutineScope.launch { - sheetState.show() - } + if (showNextButton) { + navController.navigate("manatoki.net/reader/${it.nextItemID}") { + popUpTo("manatoki.net/") + } + } else { + coroutineScope.launch { + sheetState.show() + } - coroutineScope.launch { - if (mangaListing?.itemID != it.listingItemID) - client.getItem(it.listingItemID, onListing = { - mangaListing = it + coroutineScope.launch { + if (mangaListing?.itemID != it.listingItemID) + client.getItem(it.listingItemID, onListing = { + mangaListing = it - mangaListingRippleInteractionSource.addAll( - List(max(it.entries.size - mangaListingRippleInteractionSource.size, 0)) { - MutableInteractionSource() - } - ) - - coroutineScope.launch { - while (listState.layoutInfo.totalItemsCount != it.entries.size) { - delay(100) - } - - val targetIndex = it.entries.indexOfFirst { it.itemID == itemID } - - listState.scrollToItem(targetIndex) - - mangaListingListSize?.let { sheetSize -> - val targetItem = listState.layoutInfo.visibleItemsInfo.first { - it.key == itemID - } - - if (targetItem.offset == 0) { - listState.animateScrollBy( - -(sheetSize.height - navigationBarsPadding - targetItem.size) + mangaListingRippleInteractionSource.addAll( + List( + max( + it.entries.size - mangaListingRippleInteractionSource.size, + 0 ) + ) { + MutableInteractionSource() + } + ) + + coroutineScope.launch { + while (bottomSheetListState.layoutInfo.totalItemsCount != it.entries.size) { + delay(100) } - delay(200) + val targetIndex = + it.entries.indexOfFirst { it.itemID == itemID } - with (mangaListingRippleInteractionSource[targetIndex]) { - val interaction = PressInteraction.Press( - Offset(sheetSize.width/2, targetItem.size/2f) - ) + bottomSheetListState.scrollToItem(targetIndex) + + mangaListingListSize?.let { sheetSize -> + val targetItem = + bottomSheetListState.layoutInfo.visibleItemsInfo.first { + it.key == itemID + } + + if (targetItem.offset == 0) { + bottomSheetListState.animateScrollBy( + -(sheetSize.height - navigationBarsPadding - targetItem.size) + ) + } + + delay(200) + + with(mangaListingRippleInteractionSource[targetIndex]) { + val interaction = + PressInteraction.Press( + Offset( + sheetSize.width / 2, + targetItem.size / 2f + ) + ) - emit(interaction) - emit(PressInteraction.Release(interaction)) + emit(interaction) + emit( + PressInteraction.Release( + interaction + ) + ) + } } } - } - }) + }) + } } - } } ) { Icon( - Icons.Default.List, - + if (showNextButton) Icons.Default.NavigateNext else Icons.Default.List, contentDescription = null ) } @@ -277,6 +291,7 @@ fun Reader(navController: NavController) { ReaderBase( Modifier.padding(contentPadding), model = model, + listState = readerListState, onScroll = { scrollDirection = it } ) } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt index d537ce37..2079907f 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt @@ -85,7 +85,9 @@ data class ReaderInfo( val itemID: String, val title: String, val urls: List, - val listingItemID: String + val listingItemID: String, + val prevItemID: String, + val nextItemID: String ): Parcelable @ExperimentalMaterialApi @@ -142,7 +144,7 @@ suspend fun HttpClient.getItem( }.toString() val urls = Jsoup.parse(htmlData) - .select("img[^data-]:not([style])") + .select("img[^data-]:not([style]):not([src*=loading])").also { Log.d("PUPILD", it.size.toString()) } .map { it.attributes() .first { it.key.startsWith("data-") } @@ -154,11 +156,29 @@ suspend fun HttpClient.getItem( val listingItemID = doc.select("a:contains(전체목록)").first()!!.attr("href") .takeLastWhile { it != '/' } + val prevItemID = doc.getElementById("goPrevBtn")!!.attr("href") + .let { + if (it.contains('?')) + it.dropLastWhile { it != '?' }.drop(1) + else it + } + .takeLastWhile { it != '/' } + + val nextItemID = doc.getElementById("goNextBtn")!!.attr("href") + .let { + if (it.contains('?')) + it.dropLastWhile { it != '?' }.drop(1) + else it + } + .takeLastWhile { it != '/' } + val readerInfo = ReaderInfo( itemID, title, urls, - listingItemID + listingItemID, + prevItemID, + nextItemID ) synchronized(cache) {