sort mode
This commit is contained in:
@@ -18,8 +18,8 @@ android {
|
|||||||
applicationId = "xyz.quaver.pupil"
|
applicationId = "xyz.quaver.pupil"
|
||||||
minSdk = 16
|
minSdk = 16
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 69
|
versionCode = 70
|
||||||
versionName = "5.3.15"
|
versionName = "5.3.16"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
~ Pupil, Hitomi.la viewer for Android
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
~ Copyright (C) 2020 tom5079
|
~ Copyright (C) 2020 tom5079
|
||||||
~
|
~
|
||||||
@@ -17,6 +16,4 @@
|
|||||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources></resources>
|
||||||
<string name="app_name" translatable="false" tools:override="true">Pupil-Debug</string>
|
|
||||||
</resources>
|
|
||||||
@@ -18,9 +18,9 @@ package xyz.quaver.pupil.hitomi
|
|||||||
|
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import java.util.*
|
import java.util.LinkedList
|
||||||
|
|
||||||
suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int> = coroutineScope {
|
suspend fun doSearch(query: String, sortMode: SortMode): List<Int> = coroutineScope {
|
||||||
val terms = query
|
val terms = query
|
||||||
.trim()
|
.trim()
|
||||||
.replace(Regex("""^\?"""), "")
|
.replace(Regex("""^\?"""), "")
|
||||||
@@ -34,8 +34,8 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int
|
|||||||
val negativeTerms = LinkedList<String>()
|
val negativeTerms = LinkedList<String>()
|
||||||
|
|
||||||
for (term in terms) {
|
for (term in terms) {
|
||||||
if (term.matches(Regex("^-.+")))
|
if (term.startsWith("-"))
|
||||||
negativeTerms.push(term.replace(Regex("^-"), ""))
|
negativeTerms.push(term.substring(1))
|
||||||
else if (term.isNotBlank())
|
else if (term.isNotBlank())
|
||||||
positiveTerms.push(term)
|
positiveTerms.push(term)
|
||||||
}
|
}
|
||||||
@@ -43,22 +43,25 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int
|
|||||||
val positiveResults = positiveTerms.map {
|
val positiveResults = positiveTerms.map {
|
||||||
async {
|
async {
|
||||||
runCatching {
|
runCatching {
|
||||||
getGalleryIDsForQuery(it)
|
getGalleryIDsForQuery(it, sortMode)
|
||||||
}.getOrElse { emptySet() }
|
}.getOrElse { emptySet() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val negativeResults = negativeTerms.mapIndexed { index, it ->
|
val negativeResults = negativeTerms.map {
|
||||||
async {
|
async {
|
||||||
runCatching {
|
runCatching {
|
||||||
getGalleryIDsForQuery(it)
|
getGalleryIDsForQuery(it, sortMode)
|
||||||
}.getOrElse { emptySet() }
|
}.getOrElse { emptySet() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val results = when {
|
val results = when {
|
||||||
sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", "all")
|
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(
|
||||||
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all")
|
SearchArgs("all", "index", "all"),
|
||||||
|
sortMode
|
||||||
|
)
|
||||||
|
|
||||||
else -> emptySet()
|
else -> emptySet()
|
||||||
}.toMutableSet()
|
}.toMutableSet()
|
||||||
|
|
||||||
@@ -79,9 +82,13 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int
|
|||||||
}
|
}
|
||||||
|
|
||||||
//negative results
|
//negative results
|
||||||
negativeResults.forEachIndexed { index, it ->
|
negativeResults.forEach {
|
||||||
filterNegative(it.await())
|
filterNegative(it.await())
|
||||||
}
|
}
|
||||||
|
|
||||||
results
|
return@coroutineScope if (sortMode != SortMode.RANDOM) {
|
||||||
|
results.toList()
|
||||||
|
} else {
|
||||||
|
results.shuffled()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,54 @@ import java.nio.ByteOrder
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
data class SearchArgs(
|
||||||
|
val area: String?,
|
||||||
|
val tag: String,
|
||||||
|
val language: String,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun fromQuery(query: String): SearchArgs? {
|
||||||
|
if (!query.contains(':')) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val (left, right) = query.split(':')
|
||||||
|
|
||||||
|
return when (left) {
|
||||||
|
"male", "female" -> SearchArgs("tag", query, "all")
|
||||||
|
"language" -> SearchArgs(null, "index", right)
|
||||||
|
else -> SearchArgs(left, right, "all")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SortMode {
|
||||||
|
DATE_ADDED,
|
||||||
|
DATE_PUBLISHED,
|
||||||
|
POPULAR_TODAY,
|
||||||
|
POPULAR_WEEK,
|
||||||
|
POPULAR_MONTH,
|
||||||
|
POPULAR_YEAR,
|
||||||
|
RANDOM;
|
||||||
|
|
||||||
|
val orderBy: String
|
||||||
|
get() = when (this) {
|
||||||
|
DATE_ADDED, DATE_PUBLISHED, RANDOM -> "date"
|
||||||
|
POPULAR_TODAY, POPULAR_WEEK, POPULAR_MONTH, POPULAR_YEAR -> "popular"
|
||||||
|
}
|
||||||
|
|
||||||
|
val orderByKey: String
|
||||||
|
get() = when (this) {
|
||||||
|
DATE_ADDED, RANDOM -> "added"
|
||||||
|
DATE_PUBLISHED -> "published"
|
||||||
|
POPULAR_TODAY -> "today"
|
||||||
|
POPULAR_WEEK -> "week"
|
||||||
|
POPULAR_MONTH -> "month"
|
||||||
|
POPULAR_YEAR -> "year"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//searchlib.js
|
//searchlib.js
|
||||||
const val separator = "-"
|
const val separator = "-"
|
||||||
const val extension = ".html"
|
const val extension = ".html"
|
||||||
@@ -39,51 +87,35 @@ val tag_index_version: String by lazy { getIndexVersion("tagindex") }
|
|||||||
val galleries_index_version: String by lazy { getIndexVersion("galleriesindex") }
|
val galleries_index_version: String by lazy { getIndexVersion("galleriesindex") }
|
||||||
val tagIndexDomain = "tagindex.hitomi.la"
|
val tagIndexDomain = "tagindex.hitomi.la"
|
||||||
|
|
||||||
fun sha256(data: ByteArray) : ByteArray {
|
fun sha256(data: ByteArray): ByteArray {
|
||||||
return MessageDigest.getInstance("SHA-256").digest(data)
|
return MessageDigest.getInstance("SHA-256").digest(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
fun hashTerm(term: String) : UByteArray {
|
fun hashTerm(term: String): UByteArray {
|
||||||
return sha256(term.toByteArray()).toUByteArray().sliceArray(0 until 4)
|
return sha256(term.toByteArray()).toUByteArray().sliceArray(0 until 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sanitize(input: String) : String {
|
fun sanitize(input: String): String {
|
||||||
return input.replace(Regex("[/#]"), "")
|
return input.replace(Regex("[/#]"), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIndexVersion(name: String) =
|
fun getIndexVersion(name: String) =
|
||||||
URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}").readText()
|
URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}").readText()
|
||||||
|
|
||||||
//search.js
|
//search.js
|
||||||
fun getGalleryIDsForQuery(query: String) : Set<Int> {
|
fun getGalleryIDsForQuery(query: String, sortMode: SortMode): Set<Int> {
|
||||||
query.replace("_", " ").let {
|
val sanitizedQuery = query.replace("_", " ")
|
||||||
if (it.indexOf(':') > -1) {
|
|
||||||
val sides = it.split(":")
|
|
||||||
val ns = sides[0]
|
|
||||||
var tag = sides[1]
|
|
||||||
|
|
||||||
var area : String? = ns
|
val args = SearchArgs.fromQuery(sanitizedQuery)
|
||||||
var language = "all"
|
|
||||||
when (ns) {
|
|
||||||
"female", "male" -> {
|
|
||||||
area = "tag"
|
|
||||||
tag = it
|
|
||||||
}
|
|
||||||
"language" -> {
|
|
||||||
area = null
|
|
||||||
language = tag
|
|
||||||
tag = "index"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getGalleryIDsFromNozomi(area, tag, language)
|
return if (args != null) {
|
||||||
}
|
getGalleryIDsFromNozomi(args, sortMode)
|
||||||
|
} else {
|
||||||
val key = hashTerm(it)
|
val key = hashTerm(sanitizedQuery)
|
||||||
val field = "galleries"
|
val field = "galleries"
|
||||||
|
|
||||||
val node = getNodeAtAddress(field, 0) ?: return emptySet()
|
val node = getNodeAtAddress(field, 0)
|
||||||
|
|
||||||
val data = bSearch(field, key, node)
|
val data = bSearch(field, key, node)
|
||||||
|
|
||||||
@@ -95,14 +127,14 @@ fun getGalleryIDsForQuery(query: String) : Set<Int> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun encodeSearchQueryForUrl(s: Char) =
|
fun encodeSearchQueryForUrl(s: Char) =
|
||||||
when(s) {
|
when (s) {
|
||||||
' ' -> "_"
|
' ' -> "_"
|
||||||
'/' -> "slash"
|
'/' -> "slash"
|
||||||
'.' -> "dot"
|
'.' -> "dot"
|
||||||
else -> s.toString()
|
else -> s.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSuggestionsForQuery(query: String) : List<Suggestion> {
|
fun getSuggestionsForQuery(query: String): List<Suggestion> {
|
||||||
query.replace('_', ' ').let {
|
query.replace('_', ' ').let {
|
||||||
var field = "global"
|
var field = "global"
|
||||||
var term = it
|
var term = it
|
||||||
@@ -114,13 +146,16 @@ fun getSuggestionsForQuery(query: String) : List<Suggestion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val chars = term.map(::encodeSearchQueryForUrl)
|
val chars = term.map(::encodeSearchQueryForUrl)
|
||||||
val url = "https://$tagIndexDomain/$field${if (chars.isNotEmpty()) "/${chars.joinToString("/")}" else ""}.json"
|
val url =
|
||||||
|
"https://$tagIndexDomain/$field${if (chars.isNotEmpty()) "/${chars.joinToString("/")}" else ""}.json"
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val suggestions = json.parseToJsonElement(client.newCall(request).execute().body()?.use { body -> body.string() } ?: return emptyList())
|
val suggestions = json.parseToJsonElement(
|
||||||
|
client.newCall(request).execute().body()?.use { body -> body.string() }
|
||||||
|
?: return emptyList())
|
||||||
|
|
||||||
return buildList {
|
return buildList {
|
||||||
suggestions.jsonArray.forEach { suggestionRaw ->
|
suggestions.jsonArray.forEach { suggestionRaw ->
|
||||||
@@ -131,26 +166,34 @@ fun getSuggestionsForQuery(query: String) : List<Suggestion> {
|
|||||||
val ns = suggestion[2].content ?: ""
|
val ns = suggestion[2].content ?: ""
|
||||||
|
|
||||||
val tagname = sanitize(suggestion[0].content ?: return@forEach)
|
val tagname = sanitize(suggestion[0].content ?: return@forEach)
|
||||||
val url = when(ns) {
|
val url = when (ns) {
|
||||||
"female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
|
"female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
|
||||||
"language" -> "/index-$tagname${separator}1$extension"
|
"language" -> "/index-$tagname${separator}1$extension"
|
||||||
else -> "/$ns/$tagname${separator}all${separator}1$extension"
|
else -> "/$ns/$tagname${separator}all${separator}1$extension"
|
||||||
}
|
}
|
||||||
|
|
||||||
add(Suggestion(suggestion[0].content ?: "", suggestion[1].content?.toIntOrNull() ?: 0, url, ns))
|
add(
|
||||||
|
Suggestion(
|
||||||
|
suggestion[0].content ?: "",
|
||||||
|
suggestion[1].content?.toIntOrNull() ?: 0,
|
||||||
|
url,
|
||||||
|
ns
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
fun getSuggestionsFromData(field: String, data: Pair<Long, Int>) : List<Suggestion> {
|
|
||||||
|
fun getSuggestionsFromData(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.$tag_index_version.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")
|
||||||
|
|
||||||
val inbuf = getURLAtRange(url, offset.until(offset+length))
|
val inbuf = getURLAtRange(url, offset.until(offset + length))
|
||||||
|
|
||||||
val suggestions = ArrayList<Suggestion>()
|
val suggestions = ArrayList<Suggestion>()
|
||||||
|
|
||||||
@@ -165,23 +208,25 @@ fun getSuggestionsFromData(field: String, data: Pair<Long, Int>) : List<Suggesti
|
|||||||
for (i in 0.until(numberOfSuggestions)) {
|
for (i in 0.until(numberOfSuggestions)) {
|
||||||
var top = buffer.int
|
var top = buffer.int
|
||||||
|
|
||||||
val ns = inbuf.sliceArray(buffer.position().until(buffer.position()+top)).toString(charset("UTF-8"))
|
val ns = inbuf.sliceArray(buffer.position().until(buffer.position() + top))
|
||||||
buffer.position(buffer.position()+top)
|
.toString(charset("UTF-8"))
|
||||||
|
buffer.position(buffer.position() + top)
|
||||||
|
|
||||||
top = buffer.int
|
top = buffer.int
|
||||||
|
|
||||||
val tag = inbuf.sliceArray(buffer.position().until(buffer.position()+top)).toString(charset("UTF-8"))
|
val tag = inbuf.sliceArray(buffer.position().until(buffer.position() + top))
|
||||||
buffer.position(buffer.position()+top)
|
.toString(charset("UTF-8"))
|
||||||
|
buffer.position(buffer.position() + top)
|
||||||
|
|
||||||
val count = buffer.int
|
val count = buffer.int
|
||||||
|
|
||||||
val tagname = sanitize(tag)
|
val tagname = sanitize(tag)
|
||||||
val u =
|
val u =
|
||||||
when(ns) {
|
when (ns) {
|
||||||
"female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
|
"female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
|
||||||
"language" -> "/index-$tagname${separator}1$extension"
|
"language" -> "/index-$tagname${separator}1$extension"
|
||||||
else -> "/$ns/$tagname${separator}all${separator}1$extension"
|
else -> "/$ns/$tagname${separator}all${separator}1$extension"
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestions.add(Suggestion(tag, count, u, ns))
|
suggestions.add(Suggestion(tag, count, u, ns))
|
||||||
}
|
}
|
||||||
@@ -189,12 +234,17 @@ fun getSuggestionsFromData(field: String, data: Pair<Long, Int>) : List<Suggesti
|
|||||||
return suggestions
|
return suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<Int> {
|
fun nozomiAddressFromArgs(args: SearchArgs, sortMode: SortMode) = when {
|
||||||
val nozomiAddress =
|
sortMode != SortMode.DATE_ADDED && sortMode != SortMode.RANDOM ->
|
||||||
when(area) {
|
if (args.area == "all") "$protocol//$domain/$compressed_nozomi_prefix/${sortMode.orderBy}/${sortMode.orderByKey}-${args.language}$nozomiextension"
|
||||||
null -> "$protocol//$domain/$compressed_nozomi_prefix/$tag-$language$nozomiextension"
|
else "$protocol//$domain/$compressed_nozomi_prefix/${args.area}/${sortMode.orderBy}/${sortMode.orderByKey}/${args.tag}-${args.language}$nozomiextension"
|
||||||
else -> "$protocol//$domain/$compressed_nozomi_prefix/$area/$tag-$language$nozomiextension"
|
|
||||||
}
|
args.area == "all" -> "$protocol//$domain/$compressed_nozomi_prefix/${args.tag}-${args.language}$nozomiextension"
|
||||||
|
else -> "$protocol//$domain/$compressed_nozomi_prefix/${args.area}/${args.tag}-${args.language}$nozomiextension"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGalleryIDsFromNozomi(args: SearchArgs, sortMode: SortMode): Set<Int> {
|
||||||
|
val nozomiAddress = nozomiAddressFromArgs(args, sortMode)
|
||||||
|
|
||||||
val bytes = URL(nozomiAddress).readBytes()
|
val bytes = URL(nozomiAddress).readBytes()
|
||||||
|
|
||||||
@@ -210,13 +260,13 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<
|
|||||||
return nozomi
|
return nozomi
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGalleryIDsFromData(data: Pair<Long, Int>) : Set<Int> {
|
fun getGalleryIDsFromData(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.$galleries_index_version.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")
|
||||||
|
|
||||||
val inbuf = getURLAtRange(url, offset.until(offset+length))
|
val inbuf = getURLAtRange(url, offset.until(offset + length))
|
||||||
|
|
||||||
val galleryIDs = mutableSetOf<Int>()
|
val galleryIDs = mutableSetOf<Int>()
|
||||||
|
|
||||||
@@ -226,7 +276,7 @@ fun getGalleryIDsFromData(data: Pair<Long, Int>) : Set<Int> {
|
|||||||
|
|
||||||
val numberOfGalleryIDs = buffer.int
|
val numberOfGalleryIDs = buffer.int
|
||||||
|
|
||||||
val expectedLength = numberOfGalleryIDs*4+4
|
val expectedLength = numberOfGalleryIDs * 4 + 4
|
||||||
|
|
||||||
if (numberOfGalleryIDs > 10000000 || numberOfGalleryIDs <= 0)
|
if (numberOfGalleryIDs > 10000000 || numberOfGalleryIDs <= 0)
|
||||||
throw Exception("number_of_galleryids $numberOfGalleryIDs is too long")
|
throw Exception("number_of_galleryids $numberOfGalleryIDs is too long")
|
||||||
@@ -239,21 +289,21 @@ fun getGalleryIDsFromData(data: Pair<Long, Int>) : Set<Int> {
|
|||||||
return galleryIDs
|
return galleryIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNodeAtAddress(field: String, address: Long) : Node? {
|
fun getNodeAtAddress(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.$galleries_index_version.index"
|
||||||
"languages" -> "$protocol//$domain/$galleries_index_dir/languages.$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"
|
"nozomiurl" -> "$protocol//$domain/$galleries_index_dir/nozomiurl.$galleries_index_version.index"
|
||||||
else -> "$protocol//$domain/$index_dir/$field.$tag_index_version.index"
|
else -> "$protocol//$domain/$index_dir/$field.$tag_index_version.index"
|
||||||
}
|
}
|
||||||
|
|
||||||
val nodedata = getURLAtRange(url, address.until(address+ max_node_size))
|
val nodedata = getURLAtRange(url, address.until(address + max_node_size))
|
||||||
|
|
||||||
return decodeNode(nodedata)
|
return decodeNode(nodedata)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getURLAtRange(url: String, range: LongRange) : ByteArray {
|
fun getURLAtRange(url: String, range: LongRange): ByteArray {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header("Range", "bytes=${range.first}-${range.last}")
|
.header("Range", "bytes=${range.first}-${range.last}")
|
||||||
@@ -263,9 +313,14 @@ fun getURLAtRange(url: String, range: LongRange) : ByteArray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
data class Node(val keys: List<UByteArray>, val datas: List<Pair<Long, Int>>, val subNodeAddresses: List<Long>)
|
data class Node(
|
||||||
|
val keys: List<UByteArray>,
|
||||||
|
val datas: List<Pair<Long, Int>>,
|
||||||
|
val subNodeAddresses: List<Long>
|
||||||
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
fun decodeNode(data: ByteArray) : Node {
|
fun decodeNode(data: ByteArray): Node {
|
||||||
val buffer = ByteBuffer
|
val buffer = ByteBuffer
|
||||||
.wrap(data)
|
.wrap(data)
|
||||||
.order(ByteOrder.BIG_ENDIAN)
|
.order(ByteOrder.BIG_ENDIAN)
|
||||||
@@ -281,8 +336,8 @@ fun decodeNode(data: ByteArray) : Node {
|
|||||||
if (keySize == 0 || keySize > 32)
|
if (keySize == 0 || keySize > 32)
|
||||||
throw Exception("fatal: !keySize || keySize > 32")
|
throw Exception("fatal: !keySize || keySize > 32")
|
||||||
|
|
||||||
keys.add(uData.sliceArray(buffer.position().until(buffer.position()+keySize)))
|
keys.add(uData.sliceArray(buffer.position().until(buffer.position() + keySize)))
|
||||||
buffer.position(buffer.position()+keySize)
|
buffer.position(buffer.position() + keySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
val numberOfDatas = buffer.int
|
val numberOfDatas = buffer.int
|
||||||
@@ -295,7 +350,7 @@ fun decodeNode(data: ByteArray) : Node {
|
|||||||
datas.add(Pair(offset, length))
|
datas.add(Pair(offset, length))
|
||||||
}
|
}
|
||||||
|
|
||||||
val numberOfSubNodeAddresses = B +1
|
val numberOfSubNodeAddresses = B + 1
|
||||||
val subNodeAddresses = ArrayList<Long>()
|
val subNodeAddresses = ArrayList<Long>()
|
||||||
|
|
||||||
for (i in 0.until(numberOfSubNodeAddresses)) {
|
for (i in 0.until(numberOfSubNodeAddresses)) {
|
||||||
@@ -307,8 +362,8 @@ fun decodeNode(data: ByteArray) : Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? {
|
fun bSearch(field: String, key: UByteArray, node: Node): Pair<Long, Int>? {
|
||||||
fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray) : Int {
|
fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray): Int {
|
||||||
val top = min(dv1.size, dv2.size)
|
val top = min(dv1.size, dv2.size)
|
||||||
|
|
||||||
for (i in 0.until(top)) {
|
for (i in 0.until(top)) {
|
||||||
@@ -321,18 +376,18 @@ fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun locateKey(key: UByteArray, node: Node) : Pair<Boolean, Int> {
|
fun locateKey(key: UByteArray, node: Node): Pair<Boolean, Int> {
|
||||||
for (i in node.keys.indices) {
|
for (i in node.keys.indices) {
|
||||||
val cmpResult = compareArrayBuffers(key, node.keys[i])
|
val cmpResult = compareArrayBuffers(key, node.keys[i])
|
||||||
|
|
||||||
if (cmpResult <= 0)
|
if (cmpResult <= 0)
|
||||||
return Pair(cmpResult==0, i)
|
return Pair(cmpResult == 0, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Pair(false, node.keys.size)
|
return Pair(false, node.keys.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLeaf(node: Node) : Boolean {
|
fun isLeaf(node: Node): Boolean {
|
||||||
for (subnode in node.subNodeAddresses)
|
for (subnode in node.subNodeAddresses)
|
||||||
if (subnode != 0L)
|
if (subnode != 0L)
|
||||||
return false
|
return false
|
||||||
@@ -349,6 +404,6 @@ fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? {
|
|||||||
else if (isLeaf(node))
|
else if (isLeaf(node))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val nextNode = getNodeAtAddress(field, node.subNodeAddresses[where]) ?: return null
|
val nextNode = getNodeAtAddress(field, node.subNodeAddresses[where])
|
||||||
return bSearch(field, key, nextNode)
|
return bSearch(field, key, nextNode)
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,6 @@ import android.widget.EditText
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@@ -42,19 +41,40 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import xyz.quaver.floatingsearchview.FloatingSearchView
|
import xyz.quaver.floatingsearchview.FloatingSearchView
|
||||||
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import xyz.quaver.floatingsearchview.util.view.MenuView
|
import xyz.quaver.floatingsearchview.util.view.MenuView
|
||||||
import xyz.quaver.floatingsearchview.util.view.SearchInputView
|
import xyz.quaver.floatingsearchview.util.view.SearchInputView
|
||||||
import xyz.quaver.pupil.*
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
import xyz.quaver.pupil.databinding.MainActivityBinding
|
import xyz.quaver.pupil.databinding.MainActivityBinding
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
|
import xyz.quaver.pupil.favorites
|
||||||
|
import xyz.quaver.pupil.histories
|
||||||
|
import xyz.quaver.pupil.hitomi.SortMode
|
||||||
import xyz.quaver.pupil.hitomi.doSearch
|
import xyz.quaver.pupil.hitomi.doSearch
|
||||||
import xyz.quaver.pupil.hitomi.getGalleryIDsFromNozomi
|
|
||||||
import xyz.quaver.pupil.hitomi.getSuggestionsForQuery
|
import xyz.quaver.pupil.hitomi.getSuggestionsForQuery
|
||||||
|
import xyz.quaver.pupil.searchHistory
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.types.*
|
import xyz.quaver.pupil.types.FavoriteHistorySwitch
|
||||||
|
import xyz.quaver.pupil.types.LoadingSuggestion
|
||||||
|
import xyz.quaver.pupil.types.NoResultSuggestion
|
||||||
|
import xyz.quaver.pupil.types.Suggestion
|
||||||
|
import xyz.quaver.pupil.types.Tag
|
||||||
|
import xyz.quaver.pupil.types.TagSuggestion
|
||||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
||||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||||
import xyz.quaver.pupil.ui.view.MainView
|
import xyz.quaver.pupil.ui.view.MainView
|
||||||
@@ -73,10 +93,19 @@ import kotlin.math.max
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
val sortModeLookup = mapOf(
|
||||||
|
R.id.main_menu_sort_date_added to SortMode.DATE_ADDED,
|
||||||
|
R.id.main_menu_sort_date_published to SortMode.DATE_PUBLISHED,
|
||||||
|
R.id.main_menu_sort_popular_today to SortMode.POPULAR_TODAY,
|
||||||
|
R.id.main_menu_sort_popular_week to SortMode.POPULAR_WEEK,
|
||||||
|
R.id.main_menu_sort_popular_month to SortMode.POPULAR_MONTH,
|
||||||
|
R.id.main_menu_sort_popular_year to SortMode.POPULAR_YEAR,
|
||||||
|
R.id.main_menu_sort_random to SortMode.RANDOM
|
||||||
|
)
|
||||||
|
|
||||||
class MainActivity :
|
class MainActivity :
|
||||||
BaseActivity(),
|
BaseActivity(),
|
||||||
NavigationView.OnNavigationItemSelectedListener
|
NavigationView.OnNavigationItemSelectedListener {
|
||||||
{
|
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
SEARCH,
|
SEARCH,
|
||||||
@@ -85,25 +114,21 @@ class MainActivity :
|
|||||||
FAVORITE
|
FAVORITE
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SortMode {
|
|
||||||
NEWEST,
|
|
||||||
POPULAR
|
|
||||||
}
|
|
||||||
|
|
||||||
private val galleries = ArrayList<Int>()
|
private val galleries = ArrayList<Int>()
|
||||||
|
|
||||||
private var query = ""
|
private var query = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
with(findViewById<SearchInputView>(R.id.search_bar_text)) {
|
with(findViewById<SearchInputView>(R.id.search_bar_text)) {
|
||||||
if (text.toString() != value)
|
if (text.toString() != value)
|
||||||
setText(query, TextView.BufferType.EDITABLE)
|
setText(query, TextView.BufferType.EDITABLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
private var queryStack = mutableListOf<String>()
|
private var queryStack = mutableListOf<String>()
|
||||||
|
|
||||||
private var mode = Mode.SEARCH
|
private var mode = Mode.SEARCH
|
||||||
private var sortMode = SortMode.NEWEST
|
private var sortMode = SortMode.DATE_ADDED
|
||||||
|
|
||||||
private var galleryIDs: Deferred<List<Int>>? = null
|
private var galleryIDs: Deferred<List<Int>>? = null
|
||||||
private var totalItems = 0
|
private var totalItems = 0
|
||||||
@@ -112,11 +137,12 @@ class MainActivity :
|
|||||||
|
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
|
|
||||||
private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
private val requestNotificationPermssionLauncher =
|
||||||
if (!isGranted) {
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
showNotificationPermissionExplanationDialog(this)
|
if (!isGranted) {
|
||||||
|
showNotificationPermissionExplanationDialog(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -127,9 +153,17 @@ class MainActivity :
|
|||||||
intent.dataString?.let { url ->
|
intent.dataString?.let { url ->
|
||||||
restore(url,
|
restore(url,
|
||||||
onFailure = {
|
onFailure = {
|
||||||
Snackbar.make(binding.contents.recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(
|
||||||
|
binding.contents.recyclerview,
|
||||||
|
R.string.settings_backup_failed,
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
}, onSuccess = {
|
}, onSuccess = {
|
||||||
Snackbar.make(binding.contents.recyclerview, getString(R.string.settings_restore_success, it), Snackbar.LENGTH_LONG).show()
|
Snackbar.make(
|
||||||
|
binding.contents.recyclerview,
|
||||||
|
getString(R.string.settings_restore_success, it),
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -138,17 +172,24 @@ class MainActivity :
|
|||||||
requestNotificationPermission(this, requestNotificationPermssionLauncher, false) {}
|
requestNotificationPermission(this, requestNotificationPermssionLauncher, false) {}
|
||||||
|
|
||||||
if (Preferences["download_folder", ""].isEmpty())
|
if (Preferences["download_folder", ""].isEmpty())
|
||||||
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
DownloadLocationDialogFragment().show(
|
||||||
|
supportFragmentManager,
|
||||||
|
"Download Location Dialog"
|
||||||
|
)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Preferences["download_folder_ignore_warning", false] &&
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Preferences["download_folder_ignore_warning", false] &&
|
||||||
ContextCompat.getExternalFilesDirs(this, null).filterNotNull().map { Uri.fromFile(it).toString() }
|
ContextCompat.getExternalFilesDirs(this, null).filterNotNull()
|
||||||
|
.map { Uri.fromFile(it).toString() }
|
||||||
.contains(Preferences["download_folder", ""])
|
.contains(Preferences["download_folder", ""])
|
||||||
) {
|
) {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(R.string.unaccessible_download_folder)
|
.setMessage(R.string.unaccessible_download_folder)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
DownloadLocationDialogFragment().show(
|
||||||
|
supportFragmentManager,
|
||||||
|
"Download Location Dialog"
|
||||||
|
)
|
||||||
}.setNegativeButton(R.string.ignore) { _, _ ->
|
}.setNegativeButton(R.string.ignore) { _, _ ->
|
||||||
Preferences["download_folder_ignore_warning"] = true
|
Preferences["download_folder_ignore_warning"] = true
|
||||||
}.show()
|
}.show()
|
||||||
@@ -163,10 +204,12 @@ class MainActivity :
|
|||||||
checkUpdate(this)
|
checkUpdate(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
when {
|
when {
|
||||||
binding.drawer.isDrawerOpen(GravityCompat.START) -> binding.drawer.closeDrawer(GravityCompat.START)
|
binding.drawer.isDrawerOpen(GravityCompat.START) -> binding.drawer.closeDrawer(
|
||||||
|
GravityCompat.START
|
||||||
|
)
|
||||||
|
|
||||||
queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread {
|
queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread {
|
||||||
query = queryStack.last()
|
query = queryStack.last()
|
||||||
|
|
||||||
@@ -175,6 +218,7 @@ class MainActivity :
|
|||||||
fetchGalleries(query, sortMode)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onBackPressed()
|
else -> super.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +233,7 @@ class MainActivity :
|
|||||||
val perPage = Preferences["per_page", "25"].toInt()
|
val perPage = Preferences["per_page", "25"].toInt()
|
||||||
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
|
||||||
return when(keyCode) {
|
return when (keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
if (currentPage > 0) {
|
if (currentPage > 0) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -204,6 +248,7 @@ class MainActivity :
|
|||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
if (currentPage < maxPage) {
|
if (currentPage < maxPage) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -218,20 +263,22 @@ class MainActivity :
|
|||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onKeyDown(keyCode, event)
|
else -> super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
binding.contents.recyclerview.addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
binding.contents.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
// -height of the search view < translationY < 0
|
// -height of the search view < translationY < 0
|
||||||
binding.contents.searchview.translationY =
|
binding.contents.searchview.translationY =
|
||||||
min(
|
min(
|
||||||
max(
|
max(
|
||||||
binding.contents.searchview.translationY - dy,
|
binding.contents.searchview.translationY - dy,
|
||||||
-binding.contents.searchview.binding.querySection.root.height.toFloat()
|
-binding.contents.searchview.binding.querySection.root.height.toFloat()
|
||||||
), 0F)
|
), 0F
|
||||||
|
)
|
||||||
|
|
||||||
if (dy > 0)
|
if (dy > 0)
|
||||||
binding.contents.fab.hideMenuButton(true)
|
binding.contents.fab.hideMenuButton(true)
|
||||||
@@ -240,7 +287,12 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Linkify.addLinks(binding.contents.noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) })
|
Linkify.addLinks(
|
||||||
|
binding.contents.noresult,
|
||||||
|
Pattern.compile(getString(R.string.https_text)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{ _, _ -> getString(R.string.https) })
|
||||||
|
|
||||||
//NavigationView
|
//NavigationView
|
||||||
binding.navView.setNavigationItemSelectedListener(this)
|
binding.navView.setNavigationItemSelectedListener(this)
|
||||||
@@ -261,14 +313,17 @@ class MainActivity :
|
|||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setView(editText)
|
setView(editText)
|
||||||
setTitle(R.string.main_jump_title)
|
setTitle(R.string.main_jump_title)
|
||||||
setMessage(getString(
|
setMessage(
|
||||||
R.string.main_jump_message,
|
getString(
|
||||||
currentPage+1,
|
R.string.main_jump_message,
|
||||||
ceil(totalItems / perPage.toDouble()).roundToInt()
|
currentPage + 1,
|
||||||
))
|
ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
|
currentPage =
|
||||||
|
(editText.text.toString().toIntOrNull() ?: return@setPositiveButton) - 1
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
@@ -322,7 +377,8 @@ class MainActivity :
|
|||||||
setTitle(R.string.main_open_gallery_by_id)
|
setTitle(R.string.main_open_gallery_by_id)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
val galleryID =
|
||||||
|
editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
||||||
|
|
||||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
@@ -344,7 +400,7 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
with(binding.contents.view) {
|
with(binding.contents.view) {
|
||||||
setOnPageTurnListener(object: MainView.OnPageTurnListener {
|
setOnPageTurnListener(object : MainView.OnPageTurnListener {
|
||||||
override fun onPrev(page: Int) {
|
override fun onPrev(page: Int) {
|
||||||
currentPage--
|
currentPage--
|
||||||
|
|
||||||
@@ -409,10 +465,11 @@ class MainActivity :
|
|||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
requestNotificationPermssionLauncher
|
requestNotificationPermssionLauncher
|
||||||
) {
|
) {
|
||||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
if (DownloadManager.getInstance(context)
|
||||||
|
.isDownloading(galleryID)
|
||||||
|
) { //download in progress
|
||||||
DownloadService.cancel(this@MainActivity, galleryID)
|
DownloadService.cancel(this@MainActivity, galleryID)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||||
DownloadService.download(this@MainActivity, galleryID)
|
DownloadService.download(this@MainActivity, galleryID)
|
||||||
}
|
}
|
||||||
@@ -485,6 +542,7 @@ class MainActivity :
|
|||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
} + FavoriteHistorySwitch(getString(R.string.search_show_histories))
|
} + FavoriteHistorySwitch(getString(R.string.search_show_histories))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
searchHistory.map {
|
searchHistory.map {
|
||||||
Suggestion(it)
|
Suggestion(it)
|
||||||
@@ -492,7 +550,7 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
}.reversed()
|
}.reversed()
|
||||||
|
|
||||||
private var suggestionJob : Job? = null
|
private var suggestionJob: Job? = null
|
||||||
private fun setupSearchBar() {
|
private fun setupSearchBar() {
|
||||||
with(binding.contents.searchview) {
|
with(binding.contents.searchview) {
|
||||||
val scrollSuggestionToTop = {
|
val scrollSuggestionToTop = {
|
||||||
@@ -500,7 +558,10 @@ class MainActivity :
|
|||||||
MainScope().launch {
|
MainScope().launch {
|
||||||
withTimeout(1000) {
|
withTimeout(1000) {
|
||||||
val layoutManager = layoutManager as LinearLayoutManager
|
val layoutManager = layoutManager as LinearLayoutManager
|
||||||
while (layoutManager.findLastVisibleItemPosition() != adapter?.itemCount?.minus(1)) {
|
while (layoutManager.findLastVisibleItemPosition() != adapter?.itemCount?.minus(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
) {
|
||||||
layoutManager.scrollToPosition(adapter?.itemCount?.minus(1) ?: 0)
|
layoutManager.scrollToPosition(adapter?.itemCount?.minus(1) ?: 0)
|
||||||
delay(100)
|
delay(100)
|
||||||
}
|
}
|
||||||
@@ -509,7 +570,7 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
|
onMenuStatusChangeListener = object : FloatingSearchView.OnMenuStatusChangeListener {
|
||||||
override fun onMenuOpened() {
|
override fun onMenuOpened() {
|
||||||
(this@MainActivity.binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
(this@MainActivity.binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
||||||
}
|
}
|
||||||
@@ -561,7 +622,8 @@ class MainActivity :
|
|||||||
|
|
||||||
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val suggestions = kotlin.runCatching {
|
val suggestions = kotlin.runCatching {
|
||||||
getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }.toMutableList()
|
getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }
|
||||||
|
.toMutableList()
|
||||||
}.getOrElse { mutableListOf() }
|
}.getOrElse { mutableListOf() }
|
||||||
|
|
||||||
suggestions.filter {
|
suggestions.filter {
|
||||||
@@ -573,12 +635,16 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
swapSuggestions(if (suggestions.isNotEmpty()) suggestions else listOf(NoResultSuggestion(getText(R.string.main_no_result).toString())))
|
swapSuggestions(
|
||||||
|
if (suggestions.isNotEmpty()) suggestions else listOf(
|
||||||
|
NoResultSuggestion(getText(R.string.main_no_result).toString())
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener {
|
onFocusChangeListener = object : FloatingSearchView.OnFocusChangeListener {
|
||||||
override fun onFocus() {
|
override fun onFocus() {
|
||||||
if (query.isEmpty() or query.endsWith(' ')) {
|
if (query.isEmpty() or query.endsWith(' ')) {
|
||||||
swapSuggestions(defaultSuggestions)
|
swapSuggestions(defaultSuggestions)
|
||||||
@@ -604,8 +670,14 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onActionMenuItemSelected(item: MenuItem?) {
|
fun onActionMenuItemSelected(item: MenuItem?) {
|
||||||
when(item?.itemId) {
|
when (item?.itemId) {
|
||||||
R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
|
R.id.main_menu_settings -> startActivity(
|
||||||
|
Intent(
|
||||||
|
this@MainActivity,
|
||||||
|
SettingsActivity::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
R.id.main_menu_thin -> {
|
R.id.main_menu_thin -> {
|
||||||
val thin = !item.isChecked
|
val thin = !item.isChecked
|
||||||
|
|
||||||
@@ -620,21 +692,15 @@ class MainActivity :
|
|||||||
adapter = adapter // Force to redraw
|
adapter = adapter // Force to redraw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.main_menu_sort_newest -> {
|
|
||||||
sortMode = SortMode.NEWEST
|
|
||||||
item.isChecked = true
|
|
||||||
|
|
||||||
runOnUiThread {
|
R.id.main_menu_sort_date_added,
|
||||||
currentPage = 0
|
R.id.main_menu_sort_date_published,
|
||||||
|
R.id.main_menu_sort_popular_today,
|
||||||
cancelFetch()
|
R.id.main_menu_sort_popular_week,
|
||||||
clearGalleries()
|
R.id.main_menu_sort_popular_month,
|
||||||
fetchGalleries(query, sortMode)
|
R.id.main_menu_sort_popular_year,
|
||||||
loadBlocks()
|
R.id.main_menu_sort_random -> {
|
||||||
}
|
sortMode = sortModeLookup[item.itemId]!!
|
||||||
}
|
|
||||||
R.id.main_menu_sort_popular -> {
|
|
||||||
sortMode = SortMode.POPULAR
|
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -653,7 +719,7 @@ class MainActivity :
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
binding.drawer.closeDrawers()
|
binding.drawer.closeDrawers()
|
||||||
|
|
||||||
when(item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.main_drawer_home -> {
|
R.id.main_drawer_home -> {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
@@ -664,6 +730,7 @@ class MainActivity :
|
|||||||
fetchGalleries(query, sortMode)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_history -> {
|
R.id.main_drawer_history -> {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
@@ -674,6 +741,7 @@ class MainActivity :
|
|||||||
fetchGalleries(query, sortMode)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_downloads -> {
|
R.id.main_drawer_downloads -> {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
@@ -684,6 +752,7 @@ class MainActivity :
|
|||||||
fetchGalleries(query, sortMode)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_favorite -> {
|
R.id.main_drawer_favorite -> {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
@@ -694,20 +763,35 @@ class MainActivity :
|
|||||||
fetchGalleries(query, sortMode)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_help -> {
|
R.id.main_drawer_help -> {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_github -> {
|
R.id.main_drawer_github -> {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_homepage -> {
|
R.id.main_drawer_homepage -> {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(getString(R.string.home_page))
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_email -> {
|
R.id.main_drawer_email -> {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.main_drawer_kakaotalk -> {
|
R.id.main_drawer_kakaotalk -> {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(getString(R.string.discord))
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -745,17 +829,18 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.isNotEmpty() && mode != Mode.SEARCH) {
|
if (query.isNotEmpty() && mode != Mode.SEARCH) {
|
||||||
Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply {
|
Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT)
|
||||||
setAction(android.R.string.ok) {
|
.apply {
|
||||||
cancelFetch()
|
setAction(android.R.string.ok) {
|
||||||
clearGalleries()
|
cancelFetch()
|
||||||
currentPage = 0
|
clearGalleries()
|
||||||
mode = Mode.SEARCH
|
currentPage = 0
|
||||||
queryStack.clear()
|
mode = Mode.SEARCH
|
||||||
fetchGalleries(query, sortMode)
|
queryStack.clear()
|
||||||
loadBlocks()
|
fetchGalleries(query, sortMode)
|
||||||
}
|
loadBlocks()
|
||||||
}.show()
|
}
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryIDs = null
|
galleryIDs = null
|
||||||
@@ -764,22 +849,16 @@ class MainActivity :
|
|||||||
return
|
return
|
||||||
|
|
||||||
galleryIDs = CoroutineScope(Dispatchers.IO).async {
|
galleryIDs = CoroutineScope(Dispatchers.IO).async {
|
||||||
when(mode) {
|
when (mode) {
|
||||||
Mode.SEARCH -> {
|
Mode.SEARCH -> {
|
||||||
when {
|
doSearch(
|
||||||
query.isEmpty() and defaultQuery.isEmpty() -> {
|
"$defaultQuery $query",
|
||||||
when(sortMode) {
|
sortMode
|
||||||
SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
|
).also {
|
||||||
else -> getGalleryIDsFromNozomi(null, "index", "all")
|
totalItems = it.size
|
||||||
}.also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mode.HISTORY -> {
|
Mode.HISTORY -> {
|
||||||
when {
|
when {
|
||||||
query.isEmpty() -> {
|
query.isEmpty() -> {
|
||||||
@@ -787,36 +866,42 @@ class MainActivity :
|
|||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val result = doSearch(query).sorted()
|
val result = doSearch(query, SortMode.DATE_ADDED).sorted()
|
||||||
histories.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
histories.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mode.DOWNLOAD -> {
|
Mode.DOWNLOAD -> {
|
||||||
val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
|
val downloads =
|
||||||
|
DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
query.isEmpty() -> downloads.reversed().also {
|
query.isEmpty() -> downloads.reversed().also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val result = doSearch(query).sorted()
|
val result = doSearch(query, SortMode.DATE_ADDED).sorted()
|
||||||
downloads.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
downloads.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mode.FAVORITE -> {
|
Mode.FAVORITE -> {
|
||||||
when {
|
when {
|
||||||
query.isEmpty() -> favorites.reversed().also {
|
query.isEmpty() -> favorites.reversed().also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val result = doSearch(query).sorted()
|
val result = doSearch(query, SortMode.DATE_ADDED).sorted()
|
||||||
favorites.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
favorites.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
@@ -849,10 +934,18 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
binding.contents.view.setCurrentPage(currentPage + 1, galleryIDs.size > (currentPage+1)*perPage)
|
binding.contents.view.setCurrentPage(
|
||||||
|
currentPage + 1,
|
||||||
|
galleryIDs.size > (currentPage + 1) * perPage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks ->
|
galleryIDs.slice(
|
||||||
|
currentPage * perPage until min(
|
||||||
|
currentPage * perPage + perPage,
|
||||||
|
galleryIDs.size
|
||||||
|
)
|
||||||
|
).chunked(5).let { chunks ->
|
||||||
for (chunk in chunks)
|
for (chunk in chunks)
|
||||||
chunk.map { galleryID ->
|
chunk.map { galleryID ->
|
||||||
async {
|
async {
|
||||||
|
|||||||
@@ -26,11 +26,21 @@
|
|||||||
app:showAsAction="ifRoom">
|
app:showAsAction="ifRoom">
|
||||||
<menu>
|
<menu>
|
||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
<item android:id="@+id/main_menu_sort_newest"
|
<item android:id="@+id/main_menu_sort_date_added"
|
||||||
android:title="@string/main_menu_sort_newest"
|
android:title="@string/main_menu_sort_date_added"
|
||||||
android:checked="true"/>
|
android:checked="true"/>
|
||||||
<item android:id="@+id/main_menu_sort_popular"
|
<item android:id="@+id/main_menu_sort_date_published"
|
||||||
android:title="@string/main_menu_sort_popular"/>
|
android:title="@string/main_menu_sort_date_published"/>
|
||||||
|
<item android:id="@+id/main_menu_sort_popular_today"
|
||||||
|
android:title="@string/main_menu_sort_popular_today"/>
|
||||||
|
<item android:id="@+id/main_menu_sort_popular_week"
|
||||||
|
android:title="@string/main_menu_sort_popular_week"/>
|
||||||
|
<item android:id="@+id/main_menu_sort_popular_month"
|
||||||
|
android:title="@string/main_menu_sort_popular_month"/>
|
||||||
|
<item android:id="@+id/main_menu_sort_popular_year"
|
||||||
|
android:title="@string/main_menu_sort_popular_year"/>
|
||||||
|
<item android:id="@+id/main_menu_sort_random"
|
||||||
|
android:title="@string/main_menu_sort_random"/>
|
||||||
</group>
|
</group>
|
||||||
</menu>
|
</menu>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -1,197 +1,170 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="warning">注意</string>
|
<string name="galleryblock_language">言語: %1$s</string>
|
||||||
<string name="error">エラー</string>
|
|
||||||
<string name="ignore">無視</string>
|
|
||||||
<string name="unlimited">制限なし</string>
|
|
||||||
|
|
||||||
<string name="copied_to_clipboard">クリップボードにコピーしました</string>
|
|
||||||
|
|
||||||
<string name="channel_download">ダウンロード</string>
|
|
||||||
<string name="channel_download_description">ダウンロードの進行を通知</string>
|
|
||||||
<string name="channel_downloader">ダウンローダ</string>
|
|
||||||
<string name="channel_downloader_description">ダウンローダの状態を表示</string>
|
|
||||||
<string name="channel_update">アップデート</string>
|
|
||||||
<string name="channel_update_description">アップデートの進行状況を表示</string>
|
|
||||||
<string name="channel_transfer">転送</string>
|
|
||||||
<string name="channel_transfer_description">他の機器へのデータ転送の進行状況を表示</string>
|
|
||||||
|
|
||||||
<string name="unable_to_connect">hitomi.laに接続できません</string>
|
|
||||||
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再インストールしてください。</string>
|
|
||||||
<string name="main_no_result">結果なし</string>
|
|
||||||
<string name="unaccessible_download_folder">アンドロイド11以上では、現在のダウンロードフォルダに外部アプリからアクセスできません。ダウンロードフォルダを変更しますか?</string>
|
|
||||||
<string name="notification_denied">通知を無効にすると、バックグラウンドでのダウンロードとアプリのアップデート機能が使用不可になります。</string>
|
|
||||||
|
|
||||||
<string name="main_drawer_home">トップ</string>
|
|
||||||
<string name="main_drawer_history">履歴</string>
|
|
||||||
<string name="main_drawer_downloads">ダウンロード</string>
|
|
||||||
<string name="main_drawer_favorite">ブックマーク</string>
|
|
||||||
<string name="main_drawer_group_contact_title">お問い合わせ先</string>
|
|
||||||
<string name="main_drawer_group_contact_help">ヘルプ</string>
|
|
||||||
<string name="main_drawer_group_contact_homepage">ホームページ</string>
|
|
||||||
<string name="main_drawer_group_contact_github">Github</string>
|
|
||||||
<string name="main_drawer_group_contact_email">メールを送る</string>
|
|
||||||
<string name="main_drawer_grouop_contact_discord">ディスコード</string>
|
|
||||||
|
|
||||||
<string name="main_menu_thin">簡単モード</string>
|
|
||||||
|
|
||||||
<string name="main_menu_sort">並び替え</string>
|
|
||||||
<string name="main_menu_sort_newest">新しい順</string>
|
|
||||||
<string name="main_menu_sort_popular">人気順</string>
|
|
||||||
|
|
||||||
<string name="main_jump_title">ページ移動</string>
|
|
||||||
<string name="main_jump_message">現ページ番号: %1$d\nページ数: %2$d</string>
|
|
||||||
<string name="main_open_gallery_by_id">IDで作品を開く</string>
|
|
||||||
<string name="reader_failed_to_find_gallery">エラーが発生しました</string>
|
|
||||||
<string name="main_fab_random">ランダムに作品を開く</string>
|
|
||||||
<string name="main_fab_cancel">すべてのダウンロードをキャンセル</string>
|
|
||||||
|
|
||||||
<string name="main_move_to_page">%1$dページへ移動</string>
|
|
||||||
|
|
||||||
<string name="main_download">ダウンロード</string>
|
|
||||||
<string name="main_delete">削除</string>
|
|
||||||
|
|
||||||
<string name="update_title">最新版あり</string>
|
|
||||||
<string name="update_download_completed">ダウンロードが完了しました</string>
|
|
||||||
<string name="update_download_completed_description">ここをクリックして更新</string>
|
|
||||||
<string name="update_notification_description">最新版をダウンロード中…</string>
|
|
||||||
<string name="update_release_note"># 更新履歴(v%1$s)\n%2$s</string>
|
|
||||||
|
|
||||||
<string name="search_hint">作品を検索</string>
|
|
||||||
<string name="search_all">すべての作品を対象に検索</string>
|
|
||||||
<string name="search_show_histories">履歴を見る</string>
|
|
||||||
<string name="search_show_tags">お気に入りのタグを見る</string>
|
|
||||||
|
|
||||||
<string name="gallery_details">作品情報</string>
|
|
||||||
<string name="gallery_thumbnails">サムネイル</string>
|
|
||||||
<string name="gallery_related">おすすめ</string>
|
|
||||||
<string name="gallery_artists">作者</string>
|
|
||||||
<string name="gallery_groups">グループ</string>
|
|
||||||
<string name="gallery_language">言語</string>
|
|
||||||
<string name="gallery_series">シリーズ</string>
|
|
||||||
<string name="gallery_characters">キャラクター</string>
|
|
||||||
<string name="gallery_tags">タグ</string>
|
|
||||||
|
|
||||||
<string name="galleryblock_series">シリーズ: %1$s</string>
|
<string name="galleryblock_series">シリーズ: %1$s</string>
|
||||||
<string name="galleryblock_type">タイプ: %1$s</string>
|
<string name="galleryblock_type">タイプ: %1$s</string>
|
||||||
<string name="galleryblock_language">言語: %1$s</string>
|
<string name="main_no_result">結果なし</string>
|
||||||
|
<string name="search_hint">ギャラリー検索</string>
|
||||||
<!-- READER -->
|
<string name="settings_clear_cache">キャッシュクリア</string>
|
||||||
<string name="reader_loading">読込中</string>
|
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
|
||||||
<string name="reader_go_to_page">移動</string>
|
|
||||||
<string name="reader_fab_fullscreen">全画面</string>
|
|
||||||
<string name="reader_fab_retry">再試行</string>
|
|
||||||
<string name="reader_fab_auto">まばたき検知スクロール</string>
|
|
||||||
<string name="reader_fab_auto_cancel">まばたき検知を中止</string>
|
|
||||||
<string name="reader_fab_download">バックグラウンドでダウンロード</string>
|
|
||||||
<string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</string>
|
|
||||||
<string name="reader_notification_text">ダウンロード中…</string>
|
|
||||||
<string name="reader_notification_complete">ダウンロード完了</string>
|
|
||||||
|
|
||||||
<string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string>
|
|
||||||
<string name="no_camera">この機器には前面カメラが装着されていません</string>
|
|
||||||
|
|
||||||
<string name="downloader_running">ダウンローダー起動中</string>
|
|
||||||
|
|
||||||
<string name="settings_title">設定</string>
|
|
||||||
|
|
||||||
<string name="settings_app_version_title">バージョン(クリックで更新確認)</string>
|
|
||||||
<string name="settings_app_version_description">v%s</string>
|
|
||||||
<string name="settings_beta">ベータ版チャンネルでアップデート</string>
|
|
||||||
|
|
||||||
<string name="settings_search_title">検索設定</string>
|
|
||||||
<string name="settings_galleries_per_page">一度に読み込む作品数</string>
|
|
||||||
<string name="settings_default_query">検索語句の初期値</string>
|
|
||||||
|
|
||||||
<string name="settings_storage">保存領域</string>
|
|
||||||
|
|
||||||
<string name="settings_manage_storage">保存領域の管理</string>
|
|
||||||
<string name="settings_storage_usage">%s使用中</string>
|
<string name="settings_storage_usage">%s使用中</string>
|
||||||
<string name="settings_storage_usage_loading">保存領域の使用量を算出中…</string>
|
<string name="settings_storage_usage_loading">ストレージ使用量読み込み中…</string>
|
||||||
<string name="settings_clear_cache">キャッシュを削除</string>
|
<string name="settings_default_query">デフォルトキーワード</string>
|
||||||
<string name="settings_clear_cache_alert_message">キャッシュを削除すると画像の読込に時間がかかります。実行しますか?</string>
|
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
|
||||||
<string name="settings_recover_downloads">ダウンロードデータベースを再構築</string>
|
<string name="settings_search_title">検索設定</string>
|
||||||
<string name="settings_clear_downloads">ダウンロード済みを削除</string>
|
<string name="settings_title">設定</string>
|
||||||
<string name="settings_clear_downloads_alert_message">ダウンロードした作品をすべて削除します。\n実行しますか?</string>
|
<string name="update_notification_description">アップデートダウンロード中</string>
|
||||||
|
<string name="update_title">新しいアップデートがあります</string>
|
||||||
|
<string name="warning">注意</string>
|
||||||
|
<string name="settings_miscellaneous_title">その他</string>
|
||||||
|
<string name="settings_mirror_title">ミラーサーバー</string>
|
||||||
<string name="settings_clear_history">履歴を削除</string>
|
<string name="settings_clear_history">履歴を削除</string>
|
||||||
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
|
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
|
||||||
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
||||||
|
<string name="main_drawer_history">履歴</string>
|
||||||
<string name="settings_download_folder_name">フォルダ名パターン</string>
|
<string name="notification_denied">通知を無効にするとバックグラウンドダウンロード及びアプリのアップデート機能が使用不可になります。</string>
|
||||||
<string name="settings_invalid_download_folder_name">フォルダ名に使用できない文字が含まれています</string>
|
<string name="main_drawer_home">トップ</string>
|
||||||
<string name="settings_download_folder_name_message">変数 %s は対応する値に置換されます\n\n%s</string>
|
<string name="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
|
||||||
<string name="settings_download_folder">ダウンロード場所</string>
|
|
||||||
<string name="settings_download_folder_removable">取り外し可能メディア</string>
|
|
||||||
<string name="settings_download_folder_internal">内部の保存領域</string>
|
|
||||||
<string name="settings_download_folder_available">%s 使用可能</string>
|
|
||||||
<string name="settings_download_folder_custom">手動で設定</string>
|
|
||||||
<string name="settings_download_folder_not_writable">このフォルダにアクセスできません。他のフォルダを選択してください。</string>
|
|
||||||
<string name="settings_cache_limit">キャッシュサイズ制限</string>
|
|
||||||
<string name="settings_nomedia_title">画像を隠す</string>
|
|
||||||
<string name="settings_low_quality">低解像度の画像</string>
|
|
||||||
<string name="settings_low_quality_summary">読込速度とデータ使用料を改善するため低解像度の画像を読み込む</string>
|
|
||||||
<string name="settings_transfer_data">他の機器にデータを転送</string>
|
|
||||||
|
|
||||||
<string name="settings_app_lock">アプリをロック</string>
|
|
||||||
<string name="settings_app_lock_type">アップをロックする方法</string>
|
|
||||||
|
|
||||||
<string name="settings_networking">ネットワーク</string>
|
|
||||||
<string name="settings_mirror_summary">ミラーサーバから画像を読み込む</string>
|
|
||||||
<string name="settings_proxy_title">プロクシ</string>
|
|
||||||
<string name="settings_max_concurrent_download">並列ダウンロード</string>
|
|
||||||
|
|
||||||
<string name="settings_miscellaneous_title">その他</string>
|
|
||||||
<string name="settings_tag_translation">タグの言語</string>
|
|
||||||
<string name="settings_tag_translation_message">Githubにて翻訳に参加できます</string>
|
|
||||||
<string name="settings_rtl">綴じ方向を左にする</string>
|
|
||||||
<string name="settings_security_mode_title">セキュリティーモード</string>
|
<string name="settings_security_mode_title">セキュリティーモード</string>
|
||||||
<string name="settings_security_mode_summary">アプリ履歴でアプリの画面を表示しない</string>
|
<string name="settings_security_mode_summary">アプリ履歴でアプリの画面を表示しない</string>
|
||||||
<string name="settings_dark_mode_title">ダークモード</string>
|
<string name="reader_go_to_page">移動</string>
|
||||||
<string name="settings_dark_mode_summary">夜にシコりたい方々へ</string>
|
<string name="default_query_dialog_language_selector_none">非選択</string>
|
||||||
<string name="settings_import_old_galleries">旧ギャラリーインポート</string>
|
|
||||||
<string name="settings_user_id">ユーザーID</string>
|
|
||||||
<string name="settings_oss">オープンソースライセンス</string>
|
|
||||||
|
|
||||||
<string name="settings_manage_favorites">ブックマーク管理</string>
|
|
||||||
<string name="settings_backup_title">ブックマークをバックアップ</string>
|
|
||||||
<string name="settings_backup_failed">エラーが発生しました</string>
|
|
||||||
<string name="settings_backup_share">バックアップ共有</string>
|
|
||||||
<string name="settings_backup_file_created">バックアップファイルを作成しました</string>
|
|
||||||
<string name="settings_restore_title">ブックマーク復元</string>
|
|
||||||
<string name="settings_restore_failed">復元に失敗しました</string>
|
|
||||||
<string name="settings_restore_success">%1$d項目を復元しました</string>
|
|
||||||
|
|
||||||
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string>
|
|
||||||
<string name="settings_lock_enabled">有効</string>
|
|
||||||
<string name="settings_lock_none">なし</string>
|
|
||||||
<string name="settings_lock_pattern">パターン</string>
|
|
||||||
<string name="settings_lock_password">パスワード</string>
|
|
||||||
<string name="settings_lock_biometrics">生体認証</string>
|
|
||||||
<string name="settings_lock_fingerprint">指紋</string>
|
|
||||||
<string name="settings_lock_fingerprint_without_lock">予備のロックが設定されていないと指紋ロックは使用できません</string>
|
|
||||||
<string name="settings_lock_fingerprint_prompt">Pupil 指紋ロック™</string>
|
|
||||||
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
|
||||||
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
|
||||||
|
|
||||||
<string name="default_query_dialog_title">検索語句の初期値を設定</string>
|
|
||||||
<string name="default_query_dialog_language">"言語: "</string>
|
|
||||||
<string name="default_query_dialog_filter_BL">BLフィルター</string>
|
<string name="default_query_dialog_filter_BL">BLフィルター</string>
|
||||||
<string name="default_query_dialog_filter_guro">グロフィルター</string>
|
<string name="default_query_dialog_filter_guro">グロフィルター</string>
|
||||||
<string name="default_query_dialog_filter_loli">登場人物を全て18歳以上にする</string>
|
<string name="default_query_dialog_language">"言語: "</string>
|
||||||
<string name="default_query_dialog_language_selector_none">非選択</string>
|
<string name="default_query_dialog_title">デフォルトキーワード設定</string>
|
||||||
<string name="settings_mirror_title">ミラーサーバー</string>
|
<string name="main_drawer_group_contact_title">お問い合わせ先</string>
|
||||||
|
<string name="main_drawer_group_contact_homepage">ホームページ</string>
|
||||||
<string name="proxy_dialog_type">プロクシの種類</string>
|
<string name="main_drawer_group_contact_help">ヘルプ</string>
|
||||||
<string name="proxy_dialog_addr_hint">サーバーアドレス</string>
|
<string name="main_drawer_group_contact_github">Github</string>
|
||||||
<string name="proxy_dialog_port_hint">ポート番号</string>
|
<string name="main_drawer_group_contact_email">メールを送る</string>
|
||||||
|
<string name="reader_fab_fullscreen">フルスクリーン</string>
|
||||||
|
<string name="channel_download">ダウンロード</string>
|
||||||
|
<string name="channel_download_description">ダウンロードの進行を通知</string>
|
||||||
|
<string name="reader_fab_download">バックグラウンドダウンロード</string>
|
||||||
|
<string name="reader_notification_text">ダウンロード中…</string>
|
||||||
|
<string name="reader_notification_complete">ダウンロード完了</string>
|
||||||
|
<string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</string>
|
||||||
|
<string name="main_drawer_downloads">ダウンロード</string>
|
||||||
|
<string name="main_menu_sort_random">ランダム</string>
|
||||||
|
<string name="main_jump_title">ページ移動</string>
|
||||||
|
<string name="main_jump_message">現ページ番号: %1$d\nページ数: %2$d</string>
|
||||||
|
<string name="channel_transfer">転送</string>
|
||||||
|
<string name="unable_to_connect">hitomi.laに接続できません</string>
|
||||||
|
<string name="main_move_to_page">%1$dページへ移動</string>
|
||||||
|
<string name="settings_clear_downloads">ダウンロード削除</string>
|
||||||
|
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか?</string>
|
||||||
|
<string name="settings_mirror_summary">ミラーサーバからイメージをロード</string>
|
||||||
|
<string name="main_drawer_favorite">ブックマーク</string>
|
||||||
|
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
|
||||||
|
<string name="reader_failed_to_find_gallery">エラーが発生しました</string>
|
||||||
|
<string name="settings_storage">ストレージ</string>
|
||||||
|
<string name="main_drawer_grouop_contact_discord">ディスコード</string>
|
||||||
|
<string name="settings_app_lock">アプリロック</string>
|
||||||
|
<string name="settings_app_lock_type">アップロックの種類</string>
|
||||||
|
<string name="settings_app_version_title">バージョン(アップデート確認)</string>
|
||||||
|
<string name="settings_lock_biometrics">生体認識</string>
|
||||||
|
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string>
|
||||||
|
<string name="settings_lock_enabled">有効</string>
|
||||||
|
<string name="settings_lock_fingerprint">指紋</string>
|
||||||
|
<string name="settings_lock_password">パスワード</string>
|
||||||
|
<string name="settings_lock_pattern">パターン</string>
|
||||||
|
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
||||||
|
<string name="settings_lock_none">なし</string>
|
||||||
|
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
||||||
|
<string name="reader_loading">ロード中</string>
|
||||||
|
<string name="main_menu_sort">ソート</string>
|
||||||
|
<string name="ignore">無視</string>
|
||||||
|
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string>
|
||||||
|
<string name="settings_dark_mode_title">ダークモード</string>
|
||||||
|
<string name="settings_dark_mode_summary">夜にシコりたい方々へ</string>
|
||||||
|
<string name="gallery_details">ギャラリー情報</string>
|
||||||
|
<string name="gallery_artists">アーティスト</string>
|
||||||
|
<string name="gallery_characters">キャラクター</string>
|
||||||
|
<string name="gallery_groups">グループ</string>
|
||||||
|
<string name="gallery_language">言語</string>
|
||||||
|
<string name="gallery_series">シリーズ</string>
|
||||||
|
<string name="gallery_tags">タグ</string>
|
||||||
|
<string name="gallery_thumbnails">サムネイル</string>
|
||||||
|
<string name="gallery_related">おすすめ</string>
|
||||||
|
<string name="settings_nomedia_title">イメージを隠す</string>
|
||||||
|
<string name="main_delete">削除</string>
|
||||||
|
<string name="main_download">ダウンロード</string>
|
||||||
|
<string name="settings_backup_title">ブックマークバックアップ</string>
|
||||||
|
<string name="settings_restore_title">ブックマーク復元</string>
|
||||||
|
<string name="settings_backup_file_created">バックアップファイルを作成しました</string>
|
||||||
|
<string name="settings_restore_failed">復元に失敗しました</string>
|
||||||
|
<string name="settings_restore_success">%1$d項目を復元しました</string>
|
||||||
|
<string name="settings_download_folder">ダウンロード場所</string>
|
||||||
|
<string name="settings_download_folder_internal">内部ストレージ</string>
|
||||||
|
<string name="settings_download_folder_removable">外部SDカード</string>
|
||||||
|
<string name="settings_download_folder_available">%s 使用可能</string>
|
||||||
|
<string name="update_download_completed">ダウンロードが完了しました</string>
|
||||||
|
<string name="update_download_completed_description">ここをクリックしてアップデートを行えます</string>
|
||||||
|
<string name="settings_beta">ベータチャンネルでアップデートを受信</string>
|
||||||
|
<string name="settings_app_version_description">v%s</string>
|
||||||
|
<string name="settings_low_quality">低解像度イメージ</string>
|
||||||
|
<string name="settings_low_quality_summary">ロード速度とデータ使用料を改善するため低解像度イメージをロード</string>
|
||||||
|
<string name="settings_download_folder_custom">手動で設定</string>
|
||||||
|
<string name="settings_download_folder_not_writable">このフォルダにアクセスできません。他のフォルダを選択してください。</string>
|
||||||
|
<string name="settings_proxy_title">プロクシ</string>
|
||||||
<string name="proxy_dialog_username_hint">ID</string>
|
<string name="proxy_dialog_username_hint">ID</string>
|
||||||
|
<string name="proxy_dialog_type">プロクシタイプ</string>
|
||||||
|
<string name="proxy_dialog_port_hint">ポート</string>
|
||||||
<string name="proxy_dialog_password_hint">パスワード</string>
|
<string name="proxy_dialog_password_hint">パスワード</string>
|
||||||
<string name="proxy_dialog_error">エラー</string>
|
<string name="proxy_dialog_error">エラー</string>
|
||||||
|
<string name="proxy_dialog_addr_hint">サーバーアドレス</string>
|
||||||
<string name="proxy_dialog_server">サーバー</string>
|
<string name="proxy_dialog_server">サーバー</string>
|
||||||
|
<string name="main_menu_thin">簡単モード</string>
|
||||||
|
<string name="main_fab_cancel">すべてのダウンロードキャンセル</string>
|
||||||
|
<string name="channel_update">アップデート</string>
|
||||||
|
<string name="channel_update_description">アップデートの進行状態を表示</string>
|
||||||
|
<string name="settings_import_old_galleries">旧ギャラリーインポート</string>
|
||||||
<string name="import_old_galleries_folder_not_readable">フォルダを読めません</string>
|
<string name="import_old_galleries_folder_not_readable">フォルダを読めません</string>
|
||||||
<string name="import_old_galleries_notification">旧ギャラリーインポート中…</string>
|
<string name="import_old_galleries_notification">旧ギャラリーインポート中…</string>
|
||||||
<string name="import_old_galleries_notification_done">インポート完了</string>
|
<string name="import_old_galleries_notification_done">インポート完了</string>
|
||||||
|
<string name="main_fab_random">ランダムギャラリーを開く</string>
|
||||||
|
<string name="settings_lock_fingerprint_without_lock">予備のロックが設定されていないと指紋ロックは使用できません</string>
|
||||||
|
<string name="settings_lock_fingerprint_prompt">Pupil指紋ロック™</string>
|
||||||
<string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string>
|
<string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string>
|
||||||
|
<string name="default_query_dialog_filter_loli">登場人物を全て18歳以上にする</string>
|
||||||
|
<string name="settings_user_id">ユーザーID</string>
|
||||||
|
<string name="copied_to_clipboard">クリップボードにコピーしました</string>
|
||||||
|
<string name="reader_fab_retry">リトライ</string>
|
||||||
|
<string name="reader_fab_auto">まばたき検知スクロール</string>
|
||||||
|
<string name="search_all">全てのギャラリーを対象に検索</string>
|
||||||
|
<string name="settings_rtl">綴じ方向を左にする</string>
|
||||||
|
<string name="settings_manage_favorites">ブックマーク管理</string>
|
||||||
|
<string name="settings_backup_failed">エラーが発生しました</string>
|
||||||
|
<string name="settings_backup_share">バックアップ共有</string>
|
||||||
|
<string name="channel_downloader">ダウンローダ</string>
|
||||||
|
<string name="channel_downloader_description">ダウンローダの状態を表示</string>
|
||||||
|
<string name="downloader_running">ダウンローダー起動中</string>
|
||||||
|
<string name="settings_download_folder_name">フォルダ名パターン</string>
|
||||||
|
<string name="settings_invalid_download_folder_name">フォルダ名に使用できない文字が含まれています</string>
|
||||||
|
<string name="settings_download_folder_name_message">%sに含まれている文字列を対応する変数に置換します\n\n%s</string>
|
||||||
|
<string name="settings_manage_storage">ストレージ管理</string>
|
||||||
|
<string name="settings_oss">オープンソースライセンス</string>
|
||||||
|
<string name="search_show_tags">お気に入りのタグを見る</string>
|
||||||
|
<string name="search_show_histories">履歴を見る</string>
|
||||||
|
<string name="reader_fab_auto_cancel">まばたき検知を中止</string>
|
||||||
|
<string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string>
|
||||||
|
<string name="no_camera">この機器には前面カメラが装着されていません</string>
|
||||||
|
<string name="error">エラー</string>
|
||||||
|
<string name="settings_cache_limit">キャッシュサイズ制限</string>
|
||||||
|
<string name="unlimited">制限なし</string>
|
||||||
|
<string name="settings_tag_translation">タグ言語</string>
|
||||||
|
<string name="settings_tag_translation_message">Githubにて翻訳に参加できます</string>
|
||||||
|
<string name="settings_max_concurrent_download">並列ダウンロード</string>
|
||||||
|
<string name="unaccessible_download_folder">アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか?</string>
|
||||||
|
<string name="settings_networking">ネットワーク</string>
|
||||||
|
<string name="settings_recover_downloads">ダウンロードデータベースを再構築</string>
|
||||||
|
<string name="settings_transfer_data">他の機器にデータを転送</string>
|
||||||
|
<string name="channel_transfer_description">他の機器へのデータ転送の進捗度を表示</string>
|
||||||
|
<string name="main_menu_sort_date_added">新しい順</string>
|
||||||
|
<string name="main_menu_sort_date_published">新しい順(発売日)</string>
|
||||||
|
<string name="main_menu_sort_popular_today">人気順(今日)</string>
|
||||||
|
<string name="main_menu_sort_popular_week">人気順(週間)</string>
|
||||||
|
<string name="main_menu_sort_popular_month">人気順(月間)</string>
|
||||||
|
<string name="main_menu_sort_popular_year">人気順(年間)</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
<string name="reader_notification_complete">다운로드 완료</string>
|
<string name="reader_notification_complete">다운로드 완료</string>
|
||||||
<string name="reader_fab_download_cancel">백그라운드 다운로드 취소</string>
|
<string name="reader_fab_download_cancel">백그라운드 다운로드 취소</string>
|
||||||
<string name="main_drawer_downloads">다운로드</string>
|
<string name="main_drawer_downloads">다운로드</string>
|
||||||
|
<string name="main_menu_sort_random">무작위</string>
|
||||||
<string name="main_jump_title">페이지 이동</string>
|
<string name="main_jump_title">페이지 이동</string>
|
||||||
<string name="main_jump_message">현재 페이지: %1$d\n페이지 수: %2$d</string>
|
<string name="main_jump_message">현재 페이지: %1$d\n페이지 수: %2$d</string>
|
||||||
<string name="channel_transfer">전송</string>
|
<string name="channel_transfer">전송</string>
|
||||||
@@ -71,8 +72,6 @@
|
|||||||
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
||||||
<string name="reader_loading">로딩중</string>
|
<string name="reader_loading">로딩중</string>
|
||||||
<string name="main_menu_sort">정렬</string>
|
<string name="main_menu_sort">정렬</string>
|
||||||
<string name="main_menu_sort_popular">인기순</string>
|
|
||||||
<string name="main_menu_sort_newest">시간순</string>
|
|
||||||
<string name="ignore">무시</string>
|
<string name="ignore">무시</string>
|
||||||
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
|
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
|
||||||
<string name="settings_dark_mode_title">다크 모드</string>
|
<string name="settings_dark_mode_title">다크 모드</string>
|
||||||
@@ -162,4 +161,10 @@
|
|||||||
<string name="settings_recover_downloads">다운로드 데이터베이스 복구</string>
|
<string name="settings_recover_downloads">다운로드 데이터베이스 복구</string>
|
||||||
<string name="settings_transfer_data">다른 기기에 데이터 전송</string>
|
<string name="settings_transfer_data">다른 기기에 데이터 전송</string>
|
||||||
<string name="channel_transfer_description">다른 기기에 데이터 전송 시 상태 표시</string>
|
<string name="channel_transfer_description">다른 기기에 데이터 전송 시 상태 표시</string>
|
||||||
|
<string name="main_menu_sort_date_added">추가일</string>
|
||||||
|
<string name="main_menu_sort_date_published">발매일</string>
|
||||||
|
<string name="main_menu_sort_popular_today">인기순 (오늘)</string>
|
||||||
|
<string name="main_menu_sort_popular_week">인기순 (이번 주)</string>
|
||||||
|
<string name="main_menu_sort_popular_month">인기순 (이번 달)</string>
|
||||||
|
<string name="main_menu_sort_popular_year">인기순 (이번 해)</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -70,8 +70,13 @@
|
|||||||
<string name="main_menu_thin">Thin Mode</string>
|
<string name="main_menu_thin">Thin Mode</string>
|
||||||
|
|
||||||
<string name="main_menu_sort">Sort</string>
|
<string name="main_menu_sort">Sort</string>
|
||||||
<string name="main_menu_sort_newest">Newest</string>
|
<string name="main_menu_sort_date_added">Date Added</string>
|
||||||
<string name="main_menu_sort_popular">Popular</string>
|
<string name="main_menu_sort_date_published">Date Published</string>
|
||||||
|
<string name="main_menu_sort_popular_today">Popular: Today</string>
|
||||||
|
<string name="main_menu_sort_popular_week">Popular: Week</string>
|
||||||
|
<string name="main_menu_sort_popular_month">Popular: Month</string>
|
||||||
|
<string name="main_menu_sort_popular_year">Popular: Year</string>
|
||||||
|
<string name="main_menu_sort_random">Random</string>
|
||||||
|
|
||||||
<string name="main_jump_title">Jump to page</string>
|
<string name="main_jump_title">Jump to page</string>
|
||||||
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>
|
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user