diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 144bcc60..538b1cb5 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -12,6 +12,6 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
index 05278bde..80761d6d 100644
--- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt
+++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
@@ -74,7 +74,6 @@ class Pupil : Application(), DIAware {
socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
connectTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
}
- install(HttpCache)
BrowserUserAgent()
}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/FloatingSearchBar.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/FloatingSearchBar.kt
index 57a7f027..2eee3d82 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/composable/FloatingSearchBar.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/FloatingSearchBar.kt
@@ -50,6 +50,7 @@ fun FloatingSearchBar(
onQueryChange: (String) -> Unit = { },
navigationIcon: @Composable () -> Unit = { },
actions: @Composable RowScope.() -> Unit = { },
+ onSearch: () -> Unit = { },
onTextFieldFocused: () -> Unit = { },
onTextFieldUnfocused: () -> Unit = { }
) {
@@ -100,9 +101,7 @@ fun FloatingSearchBar(
cursorBrush = SolidColor(MaterialTheme.colors.secondary),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(
- onSearch = {
- focusManager.clearFocus()
- }
+ onSearch = { onSearch() }
),
decorationBox = { innerTextField ->
Row(
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/ListSearchResult.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/ListSearchResult.kt
deleted file mode 100644
index 2c118053..00000000
--- a/app/src/main/java/xyz/quaver/pupil/sources/composable/ListSearchResult.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Pupil, Hitomi.la viewer for Android
- * Copyright (C) 2021 tom5079
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package xyz.quaver.pupil.sources.composable
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun ListSearchResult(searchResults: List, contentPadding: PaddingValues = PaddingValues(0.dp), content: @Composable (T) -> Unit) {
- LazyColumn(
- Modifier.fillMaxSize(),
- contentPadding = contentPadding,
- verticalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- items(searchResults) { itemInfo ->
- content(itemInfo)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/OverscrollPager.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/OverscrollPager.kt
index 23f5c499..2463d2ee 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/composable/OverscrollPager.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/OverscrollPager.kt
@@ -18,7 +18,6 @@
package xyz.quaver.pupil.sources.composable
-import android.util.Log
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.awaitFirstDown
@@ -41,17 +40,19 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.consumePositionChange
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull
-import com.google.accompanist.insets.LocalWindowInsets
-import com.google.accompanist.insets.rememberInsetsPaddingValues
import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.theme.LightBlue300
-import kotlin.math.*
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.sign
@Composable
fun OverscrollPager(
@@ -68,8 +69,10 @@ fun OverscrollPager(
var overscroll: Float? by remember { mutableStateOf(null) }
- val topCircleRadius by animateFloatAsState(if (overscroll?.let { it >= pageTurnIndicatorHeight } == true) 1000f else 0f)
- val bottomCircleRadius by animateFloatAsState(if (overscroll?.let { it <= -pageTurnIndicatorHeight } == true) 1000f else 0f)
+ val screenWidth = LocalConfiguration.current.screenWidthDp
+
+ val topCircleRadius by animateFloatAsState(if (overscroll?.let { it >= pageTurnIndicatorHeight } == true) screenWidth.toFloat() else 0f)
+ val bottomCircleRadius by animateFloatAsState(if (overscroll?.let { it <= -pageTurnIndicatorHeight } == true) screenWidth.toFloat() else 0f)
val prevPageTurnIndicatorOffsetPx = LocalDensity.current.run { prevPageTurnIndicatorOffset.toPx() }
val nextPageTurnIndicatorOffsetPx = LocalDensity.current.run { nextPageTurnIndicatorOffset.toPx() }
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt b/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt
index 95c5b46e..bb149da6 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/composable/SearchBase.kt
@@ -85,6 +85,7 @@ open class SearchBaseViewModel(app: Application) : AndroidViewModel(app) {
var query by mutableStateOf("")
var loading by mutableStateOf(false)
+ var error by mutableStateOf(false)
//region UI
var isFabVisible by mutableStateOf(true)
@@ -139,7 +140,7 @@ fun SearchBase(
)
}
) { contentPadding ->
- Box(Modifier.padding(contentPadding)) {
+ Box(Modifier.padding(contentPadding).fillMaxSize()) {
OverscrollPager(
currentPage = model.currentPage,
prevPageAvailable = model.prevPageAvailable,
@@ -191,8 +192,9 @@ fun SearchBase(
}
},
actions = actions,
+ onSearch = { onSearch(); focusManager.clearFocus() },
onTextFieldFocused = { navigationIconState = NavigationIconState.ARROW },
- onTextFieldUnfocused = { navigationIconState = NavigationIconState.MENU; onSearch() }
+ onTextFieldUnfocused = { navigationIconState = NavigationIconState.MENU }
)
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
index 6f30a53c..8dbd6b5e 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
@@ -26,6 +26,9 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.GridCells
+import androidx.compose.foundation.lazy.LazyVerticalGrid
+import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
@@ -187,21 +190,26 @@ class Hitomi(app: Application) : Source(), DIAware {
},
onSearch = { model.search() }
) { contentPadding ->
- ListSearchResult(model.searchResults, contentPadding = contentPadding) {
- DetailedSearchResult(
- it,
- bookmarks = bookmarkSet,
- onBookmarkToggle = {
- coroutineScope.launch {
- if (it in bookmarkSet) bookmarkDao.delete(name, it)
- else bookmarkDao.insert(name, it)
+ LazyVerticalGrid(
+ cells = GridCells.Adaptive(minSize = 500.dp),
+ contentPadding = contentPadding
+ ) {
+ items(model.searchResults) {
+ DetailedSearchResult(
+ it,
+ bookmarks = bookmarkSet,
+ onBookmarkToggle = {
+ coroutineScope.launch {
+ if (it in bookmarkSet) bookmarkDao.delete(name, it)
+ else bookmarkDao.insert(name, it)
+ }
}
+ ) { result ->
+ logger.info {
+ result.toString()
+ }
+ navController.navigate("hitomi.la/reader/${result.itemID}")
}
- ) { result ->
- logger.info {
- result.toString()
- }
- navController.navigate("hitomi.la/reader/${result.itemID}")
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/HitomiSearchResultViewModel.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/HitomiSearchResultViewModel.kt
index 0212ad7f..bff10ab0 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/HitomiSearchResultViewModel.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/HitomiSearchResultViewModel.kt
@@ -19,6 +19,7 @@
package xyz.quaver.pupil.sources.hitomi
import android.app.Application
+import android.util.LruCache
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -39,6 +40,8 @@ import xyz.quaver.pupil.sources.hitomi.lib.GalleryBlock
import xyz.quaver.pupil.sources.hitomi.lib.doSearch
import xyz.quaver.pupil.sources.hitomi.lib.getGalleryBlock
import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.math.min
class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel(app), DIAware {
override val di by closestDI(app)
@@ -54,6 +57,8 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel()
+ private val galleryBlockCache = LruCache(100)
+
var sortByPopularity by mutableStateOf(false)
private var searchJob: Job? = null
@@ -66,6 +71,7 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel
- yield()
- loading = false
- searchResults.add(transform(getGalleryBlock(client, galleryID)))
- }
+ yield()
+
+ val range = max((currentPage-1)*resultsPerPage, 0) until min(currentPage*resultsPerPage, totalItems)
+
+ cache.slice(range)
+ .forEach { galleryID ->
+ yield()
+ loading = false
+ kotlin.runCatching {
+ galleryBlockCache.get(galleryID) ?: getGalleryBlock(client, galleryID).also {
+ galleryBlockCache.put(galleryID, it)
+ }
+ }.onFailure {
+ error = true
+ }.getOrNull()?.let {
+ searchResults.add(transform(it))
+ }
+
+ }
}
viewModelScope.launch {
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 64e3a909..f1e6f702 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
@@ -19,6 +19,7 @@
package xyz.quaver.pupil.sources.hitomi.composable
import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
@@ -107,7 +108,7 @@ fun DetailedSearchResult(
Card(
modifier = Modifier
- .padding(8.dp, 0.dp)
+ .padding(8.dp)
.fillMaxWidth()
.clickable { onClick(result) },
elevation = 4.dp
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/galleryblock.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/galleryblock.kt
index e7ccb3cb..bc4e1a9a 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/galleryblock.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/galleryblock.kt
@@ -48,7 +48,7 @@ suspend fun fetchNozomi(
val startByte = start*4
val endByte = (start+count)*4-1
- append("Range", "bytes=$startByte-$endByte")
+ set("Range", "bytes=$startByte-$endByte")
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/search.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/search.kt
index fc5d9ccc..07f836db 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/search.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/lib/search.kt
@@ -18,8 +18,12 @@
package xyz.quaver.pupil.sources.hitomi.lib
+import android.util.Log
import io.ktor.client.*
+import io.ktor.client.call.*
import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.ByteBuffer
@@ -36,8 +40,15 @@ const val max_node_size = 464
const val B = 16
const val compressed_nozomi_prefix = "n"
-var tag_index_version: String? = null
-var galleries_index_version: String? = null
+var _tag_index_version: String? = null
+suspend fun getTagIndexVersion(client: HttpClient): String = _tag_index_version ?: getIndexVersion(client, "tagindex").also {
+ _tag_index_version = it
+}
+
+var _galleries_index_version: String? = null
+suspend fun getGalleriesIndexVersion(client: HttpClient): String = _galleries_index_version ?: getIndexVersion(client, "galleriesindex").also {
+ _galleries_index_version = it
+}
fun sha256(data: ByteArray) : ByteArray {
return MessageDigest.getInstance("SHA-256").digest(data)
@@ -118,7 +129,7 @@ suspend fun getSuggestionsForQuery(client: HttpClient, query: String) : List) : List {
- val url = "$protocol//$domain/$index_dir/$field.$tag_index_version.data"
+ val url = "$protocol//$domain/$index_dir/$field.${getTagIndexVersion(client)}.data"
val (offset, length) = data
if (length > 10000 || length <= 0)
throw Exception("length $length is too long")
@@ -188,7 +199,7 @@ suspend fun getGalleryIDsFromNozomi(client: HttpClient, area: String?, tag: Stri
}
suspend fun getGalleryIDsFromData(client: HttpClient, data: Pair) : Set {
- val url = "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.data"
+ val url = "$protocol//$domain/$galleries_index_dir/galleries.${getGalleriesIndexVersion(client)}.data"
val (offset, length) = data
if (length > 100000000 || length <= 0)
throw Exception("length $length is too long")
@@ -219,10 +230,10 @@ suspend fun getGalleryIDsFromData(client: HttpClient, data: Pair) : S
suspend fun getNodeAtAddress(client: HttpClient, field: String, address: Long) : Node? {
val url =
when(field) {
- "galleries" -> "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.index"
- "languages" -> "$protocol//$domain/$galleries_index_dir/languages.$galleries_index_version.index"
- "nozomiurl" -> "$protocol//$domain/$galleries_index_dir/nozomiurl.$galleries_index_version.index"
- else -> "$protocol//$domain/$index_dir/$field.$tag_index_version.index"
+ "galleries" -> "$protocol//$domain/$galleries_index_dir/galleries.${getGalleriesIndexVersion(client)}.index"
+ "languages" -> "$protocol//$domain/$galleries_index_dir/languages.${getGalleriesIndexVersion(client)}.index"
+ "nozomiurl" -> "$protocol//$domain/$galleries_index_dir/nozomiurl.${getGalleriesIndexVersion(client)}.index"
+ else -> "$protocol//$domain/$index_dir/$field.${getTagIndexVersion(client)}.index"
}
val nodedata = getURLAtRange(client, url, address.until(address+max_node_size))
@@ -233,7 +244,7 @@ suspend fun getNodeAtAddress(client: HttpClient, field: String, address: Long) :
suspend fun getURLAtRange(client: HttpClient, url: String, range: LongRange) : ByteArray = withContext(Dispatchers.IO) {
client.get(url) {
headers {
- append("Range", "bytes=${range.first}-${range.last}")
+ set("Range", "bytes=${range.first}-${range.last}")
}
}
}