Fixed a few things...

Can't really write them all down :v
This commit is contained in:
tom5079
2021-12-25 23:33:13 +09:00
parent fcbe107fe7
commit b708437a16
11 changed files with 95 additions and 87 deletions

View File

@@ -12,6 +12,6 @@
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-12-21T10:21:31.281798Z" />
<timeTargetWasSelectedWithDropDown value="2021-12-25T06:38:27.847239Z" />
</component>
</project>

View File

@@ -74,7 +74,6 @@ class Pupil : Application(), DIAware {
socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
connectTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
}
install(HttpCache)
BrowserUserAgent()
}

View File

@@ -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(

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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 <T> ListSearchResult(searchResults: List<T>, contentPadding: PaddingValues = PaddingValues(0.dp), content: @Composable (T) -> Unit) {
LazyColumn(
Modifier.fillMaxSize(),
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(searchResults) { itemInfo ->
content(itemInfo)
}
}
}

View File

@@ -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() }

View File

@@ -85,6 +85,7 @@ open class SearchBaseViewModel<T>(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 <T> SearchBase(
)
}
) { contentPadding ->
Box(Modifier.padding(contentPadding)) {
Box(Modifier.padding(contentPadding).fillMaxSize()) {
OverscrollPager(
currentPage = model.currentPage,
prevPageAvailable = model.prevPageAvailable,
@@ -191,8 +192,9 @@ fun <T> SearchBase(
}
},
actions = actions,
onSearch = { onSearch(); focusManager.clearFocus() },
onTextFieldFocused = { navigationIconState = NavigationIconState.ARROW },
onTextFieldUnfocused = { navigationIconState = NavigationIconState.MENU; onSearch() }
onTextFieldUnfocused = { navigationIconState = NavigationIconState.MENU }
)
}
}

View File

@@ -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}")
}
}
}

View File

@@ -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<HitomiSearchResult>(app), DIAware {
override val di by closestDI(app)
@@ -54,6 +57,8 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<Hitomi
private var cachedSortByPopularity: Boolean? = null
private val cache = mutableListOf<Int>()
private val galleryBlockCache = LruCache<Int, GalleryBlock>(100)
var sortByPopularity by mutableStateOf(false)
private var searchJob: Job? = null
@@ -66,6 +71,7 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<Hitomi
searchResults.clear()
searchBarOffset = 0
loading = true
error = false
searchJob = launch {
if (cachedQuery != query || cachedSortByPopularity != sortByPopularity || cache.isEmpty()) {
@@ -74,21 +80,41 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<Hitomi
yield()
val result = doSearch(client, query, sortByPopularity)
val result = runCatching {
doSearch(client, query, sortByPopularity)
}.onFailure {
error = true
}.getOrNull()
yield()
cache.addAll(result)
result?.let { cache.addAll(result) }
cachedQuery = query
totalItems = result.size
maxPage = ceil(result.size / resultsPerPage.toDouble()).toInt()
totalItems = result?.size ?: 0
maxPage =
result?.let { ceil(result.size / resultsPerPage.toDouble()).toInt() }
?: 0
}
cache.slice((currentPage-1)*resultsPerPage until currentPage*resultsPerPage).forEach { galleryID ->
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 {

View File

@@ -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

View File

@@ -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")
}
}
}

View File

@@ -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<Sug
data class Suggestion(val s: String, val t: Int, val u: String, val n: String)
suspend fun getSuggestionsFromData(client: HttpClient, field: String, data: Pair<Long, Int>) : List<Suggestion> {
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<Long, Int>) : Set<Int> {
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<Long, Int>) : 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}")
}
}
}