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> </deviceKey>
</Target> </Target>
</targetSelectedWithDropDown> </targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-12-21T10:21:31.281798Z" /> <timeTargetWasSelectedWithDropDown value="2021-12-25T06:38:27.847239Z" />
</component> </component>
</project> </project>

View File

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

View File

@@ -50,6 +50,7 @@ fun FloatingSearchBar(
onQueryChange: (String) -> Unit = { }, onQueryChange: (String) -> Unit = { },
navigationIcon: @Composable () -> Unit = { }, navigationIcon: @Composable () -> Unit = { },
actions: @Composable RowScope.() -> Unit = { }, actions: @Composable RowScope.() -> Unit = { },
onSearch: () -> Unit = { },
onTextFieldFocused: () -> Unit = { }, onTextFieldFocused: () -> Unit = { },
onTextFieldUnfocused: () -> Unit = { } onTextFieldUnfocused: () -> Unit = { }
) { ) {
@@ -100,9 +101,7 @@ fun FloatingSearchBar(
cursorBrush = SolidColor(MaterialTheme.colors.secondary), cursorBrush = SolidColor(MaterialTheme.colors.secondary),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onSearch = { onSearch = { onSearch() }
focusManager.clearFocus()
}
), ),
decorationBox = { innerTextField -> decorationBox = { innerTextField ->
Row( 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 package xyz.quaver.pupil.sources.composable
import android.util.Log
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.awaitFirstDown 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.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.consumePositionChange import androidx.compose.ui.input.pointer.consumePositionChange
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFirstOrNull 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.R
import xyz.quaver.pupil.ui.theme.LightBlue300 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 @Composable
fun OverscrollPager( fun OverscrollPager(
@@ -68,8 +69,10 @@ fun OverscrollPager(
var overscroll: Float? by remember { mutableStateOf(null) } var overscroll: Float? by remember { mutableStateOf(null) }
val topCircleRadius by animateFloatAsState(if (overscroll?.let { it >= pageTurnIndicatorHeight } == true) 1000f else 0f) val screenWidth = LocalConfiguration.current.screenWidthDp
val bottomCircleRadius by animateFloatAsState(if (overscroll?.let { it <= -pageTurnIndicatorHeight } == true) 1000f else 0f)
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 prevPageTurnIndicatorOffsetPx = LocalDensity.current.run { prevPageTurnIndicatorOffset.toPx() }
val nextPageTurnIndicatorOffsetPx = LocalDensity.current.run { nextPageTurnIndicatorOffset.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 query by mutableStateOf("")
var loading by mutableStateOf(false) var loading by mutableStateOf(false)
var error by mutableStateOf(false)
//region UI //region UI
var isFabVisible by mutableStateOf(true) var isFabVisible by mutableStateOf(true)
@@ -139,7 +140,7 @@ fun <T> SearchBase(
) )
} }
) { contentPadding -> ) { contentPadding ->
Box(Modifier.padding(contentPadding)) { Box(Modifier.padding(contentPadding).fillMaxSize()) {
OverscrollPager( OverscrollPager(
currentPage = model.currentPage, currentPage = model.currentPage,
prevPageAvailable = model.prevPageAvailable, prevPageAvailable = model.prevPageAvailable,
@@ -191,8 +192,9 @@ fun <T> SearchBase(
} }
}, },
actions = actions, actions = actions,
onSearch = { onSearch(); focusManager.clearFocus() },
onTextFieldFocused = { navigationIconState = NavigationIconState.ARROW }, 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.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size 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.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
@@ -187,21 +190,26 @@ class Hitomi(app: Application) : Source(), DIAware {
}, },
onSearch = { model.search() } onSearch = { model.search() }
) { contentPadding -> ) { contentPadding ->
ListSearchResult(model.searchResults, contentPadding = contentPadding) { LazyVerticalGrid(
DetailedSearchResult( cells = GridCells.Adaptive(minSize = 500.dp),
it, contentPadding = contentPadding
bookmarks = bookmarkSet, ) {
onBookmarkToggle = { items(model.searchResults) {
coroutineScope.launch { DetailedSearchResult(
if (it in bookmarkSet) bookmarkDao.delete(name, it) it,
else bookmarkDao.insert(name, 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 package xyz.quaver.pupil.sources.hitomi
import android.app.Application import android.app.Application
import android.util.LruCache
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue 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.doSearch
import xyz.quaver.pupil.sources.hitomi.lib.getGalleryBlock import xyz.quaver.pupil.sources.hitomi.lib.getGalleryBlock
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<HitomiSearchResult>(app), DIAware { class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<HitomiSearchResult>(app), DIAware {
override val di by closestDI(app) override val di by closestDI(app)
@@ -54,6 +57,8 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<Hitomi
private var cachedSortByPopularity: Boolean? = null private var cachedSortByPopularity: Boolean? = null
private val cache = mutableListOf<Int>() private val cache = mutableListOf<Int>()
private val galleryBlockCache = LruCache<Int, GalleryBlock>(100)
var sortByPopularity by mutableStateOf(false) var sortByPopularity by mutableStateOf(false)
private var searchJob: Job? = null private var searchJob: Job? = null
@@ -66,6 +71,7 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<Hitomi
searchResults.clear() searchResults.clear()
searchBarOffset = 0 searchBarOffset = 0
loading = true loading = true
error = false
searchJob = launch { searchJob = launch {
if (cachedQuery != query || cachedSortByPopularity != sortByPopularity || cache.isEmpty()) { if (cachedQuery != query || cachedSortByPopularity != sortByPopularity || cache.isEmpty()) {
@@ -74,21 +80,41 @@ class HitomiSearchResultViewModel(app: Application) : SearchBaseViewModel<Hitomi
yield() yield()
val result = doSearch(client, query, sortByPopularity) val result = runCatching {
doSearch(client, query, sortByPopularity)
}.onFailure {
error = true
}.getOrNull()
yield() yield()
cache.addAll(result) result?.let { cache.addAll(result) }
cachedQuery = query cachedQuery = query
totalItems = result.size totalItems = result?.size ?: 0
maxPage = ceil(result.size / resultsPerPage.toDouble()).toInt() maxPage =
result?.let { ceil(result.size / resultsPerPage.toDouble()).toInt() }
?: 0
} }
cache.slice((currentPage-1)*resultsPerPage until currentPage*resultsPerPage).forEach { galleryID -> yield()
yield()
loading = false val range = max((currentPage-1)*resultsPerPage, 0) until min(currentPage*resultsPerPage, totalItems)
searchResults.add(transform(getGalleryBlock(client, galleryID)))
} 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 { viewModelScope.launch {

View File

@@ -19,6 +19,7 @@
package xyz.quaver.pupil.sources.hitomi.composable package xyz.quaver.pupil.sources.hitomi.composable
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@@ -107,7 +108,7 @@ fun DetailedSearchResult(
Card( Card(
modifier = Modifier modifier = Modifier
.padding(8.dp, 0.dp) .padding(8.dp)
.fillMaxWidth() .fillMaxWidth()
.clickable { onClick(result) }, .clickable { onClick(result) },
elevation = 4.dp elevation = 4.dp

View File

@@ -48,7 +48,7 @@ suspend fun fetchNozomi(
val startByte = start*4 val startByte = start*4
val endByte = (start+count)*4-1 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 package xyz.quaver.pupil.sources.hitomi.lib
import android.util.Log
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.nio.ByteBuffer import java.nio.ByteBuffer
@@ -36,8 +40,15 @@ const val max_node_size = 464
const val B = 16 const val B = 16
const val compressed_nozomi_prefix = "n" const val compressed_nozomi_prefix = "n"
var tag_index_version: String? = null var _tag_index_version: String? = null
var galleries_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 { fun sha256(data: ByteArray) : ByteArray {
return MessageDigest.getInstance("SHA-256").digest(data) 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) 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> { 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 val (offset, length) = data
if (length > 10000 || length <= 0) if (length > 10000 || length <= 0)
throw Exception("length $length is too long") 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> { 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 val (offset, length) = data
if (length > 100000000 || length <= 0) if (length > 100000000 || length <= 0)
throw Exception("length $length is too long") 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? { suspend fun getNodeAtAddress(client: HttpClient, field: String, address: Long) : Node? {
val url = val url =
when(field) { when(field) {
"galleries" -> "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.index" "galleries" -> "$protocol//$domain/$galleries_index_dir/galleries.${getGalleriesIndexVersion(client)}.index"
"languages" -> "$protocol//$domain/$galleries_index_dir/languages.$galleries_index_version.index" "languages" -> "$protocol//$domain/$galleries_index_dir/languages.${getGalleriesIndexVersion(client)}.index"
"nozomiurl" -> "$protocol//$domain/$galleries_index_dir/nozomiurl.$galleries_index_version.index" "nozomiurl" -> "$protocol//$domain/$galleries_index_dir/nozomiurl.${getGalleriesIndexVersion(client)}.index"
else -> "$protocol//$domain/$index_dir/$field.$tag_index_version.index" else -> "$protocol//$domain/$index_dir/$field.${getTagIndexVersion(client)}.index"
} }
val nodedata = getURLAtRange(client, url, address.until(address+max_node_size)) 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) { suspend fun getURLAtRange(client: HttpClient, url: String, range: LongRange) : ByteArray = withContext(Dispatchers.IO) {
client.get(url) { client.get(url) {
headers { headers {
append("Range", "bytes=${range.first}-${range.last}") set("Range", "bytes=${range.first}-${range.last}")
} }
} }
} }