Fixed a few things...
Can't really write them all down :v
This commit is contained in:
2
.idea/deploymentTargetDropDown.xml
generated
2
.idea/deploymentTargetDropDown.xml
generated
@@ -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>
|
||||
@@ -74,7 +74,6 @@ class Pupil : Application(), DIAware {
|
||||
socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
|
||||
connectTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
|
||||
}
|
||||
install(HttpCache)
|
||||
|
||||
BrowserUserAgent()
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() }
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user