diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 081062b5..d8f754df 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,8 +18,8 @@ android {
applicationId = "xyz.quaver.pupil"
minSdk = 16
targetSdk = 35
- versionCode = 69
- versionName = "5.3.15"
+ versionCode = 70
+ versionName = "5.3.16"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
index 944c7613..d74437b0 100644
--- a/app/src/debug/res/values/strings.xml
+++ b/app/src/debug/res/values/strings.xml
@@ -1,5 +1,4 @@
-
-
-
- Pupil-Debug
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/hitomi/results.kt b/app/src/main/java/xyz/quaver/pupil/hitomi/results.kt
index 23f78778..6d06dbed 100644
--- a/app/src/main/java/xyz/quaver/pupil/hitomi/results.kt
+++ b/app/src/main/java/xyz/quaver/pupil/hitomi/results.kt
@@ -18,9 +18,9 @@ package xyz.quaver.pupil.hitomi
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
-import java.util.*
+import java.util.LinkedList
-suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set = coroutineScope {
+suspend fun doSearch(query: String, sortMode: SortMode): List = coroutineScope {
val terms = query
.trim()
.replace(Regex("""^\?"""), "")
@@ -34,8 +34,8 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set()
for (term in terms) {
- if (term.matches(Regex("^-.+")))
- negativeTerms.push(term.replace(Regex("^-"), ""))
+ if (term.startsWith("-"))
+ negativeTerms.push(term.substring(1))
else if (term.isNotBlank())
positiveTerms.push(term)
}
@@ -43,22 +43,25 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set
+ val negativeResults = negativeTerms.map {
async {
runCatching {
- getGalleryIDsForQuery(it)
+ getGalleryIDsForQuery(it, sortMode)
}.getOrElse { emptySet() }
}
}
val results = when {
- sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", "all")
- positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all")
+ positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(
+ SearchArgs("all", "index", "all"),
+ sortMode
+ )
+
else -> emptySet()
}.toMutableSet()
@@ -79,9 +82,13 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set
+ negativeResults.forEach {
filterNegative(it.await())
}
- results
+ return@coroutineScope if (sortMode != SortMode.RANDOM) {
+ results.toList()
+ } else {
+ results.shuffled()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/hitomi/search.kt b/app/src/main/java/xyz/quaver/pupil/hitomi/search.kt
index db8e1914..421890f5 100644
--- a/app/src/main/java/xyz/quaver/pupil/hitomi/search.kt
+++ b/app/src/main/java/xyz/quaver/pupil/hitomi/search.kt
@@ -26,6 +26,54 @@ import java.nio.ByteOrder
import java.security.MessageDigest
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
const val separator = "-"
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 tagIndexDomain = "tagindex.hitomi.la"
-fun sha256(data: ByteArray) : ByteArray {
+fun sha256(data: ByteArray): ByteArray {
return MessageDigest.getInstance("SHA-256").digest(data)
}
@OptIn(ExperimentalUnsignedTypes::class)
-fun hashTerm(term: String) : UByteArray {
+fun hashTerm(term: String): UByteArray {
return sha256(term.toByteArray()).toUByteArray().sliceArray(0 until 4)
}
-fun sanitize(input: String) : String {
+fun sanitize(input: String): String {
return input.replace(Regex("[/#]"), "")
}
fun getIndexVersion(name: String) =
- URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}").readText()
+ URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}").readText()
//search.js
-fun getGalleryIDsForQuery(query: String) : Set {
- query.replace("_", " ").let {
- if (it.indexOf(':') > -1) {
- val sides = it.split(":")
- val ns = sides[0]
- var tag = sides[1]
+fun getGalleryIDsForQuery(query: String, sortMode: SortMode): Set {
+ val sanitizedQuery = query.replace("_", " ")
- var area : String? = ns
- var language = "all"
- when (ns) {
- "female", "male" -> {
- area = "tag"
- tag = it
- }
- "language" -> {
- area = null
- language = tag
- tag = "index"
- }
- }
+ val args = SearchArgs.fromQuery(sanitizedQuery)
- return getGalleryIDsFromNozomi(area, tag, language)
- }
-
- val key = hashTerm(it)
+ return if (args != null) {
+ getGalleryIDsFromNozomi(args, sortMode)
+ } else {
+ val key = hashTerm(sanitizedQuery)
val field = "galleries"
- val node = getNodeAtAddress(field, 0) ?: return emptySet()
+ val node = getNodeAtAddress(field, 0)
val data = bSearch(field, key, node)
@@ -95,14 +127,14 @@ fun getGalleryIDsForQuery(query: String) : Set {
}
fun encodeSearchQueryForUrl(s: Char) =
- when(s) {
+ when (s) {
' ' -> "_"
'/' -> "slash"
'.' -> "dot"
else -> s.toString()
}
-fun getSuggestionsForQuery(query: String) : List {
+fun getSuggestionsForQuery(query: String): List {
query.replace('_', ' ').let {
var field = "global"
var term = it
@@ -114,13 +146,16 @@ fun getSuggestionsForQuery(query: String) : List {
}
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()
.url(url)
.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 {
suggestions.jsonArray.forEach { suggestionRaw ->
@@ -131,26 +166,34 @@ fun getSuggestionsForQuery(query: String) : List {
val ns = suggestion[2].content ?: ""
val tagname = sanitize(suggestion[0].content ?: return@forEach)
- val url = when(ns) {
+ val url = when (ns) {
"female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
"language" -> "/index-$tagname${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)
-fun getSuggestionsFromData(field: String, data: Pair) : List {
+
+fun getSuggestionsFromData(field: String, data: Pair): List {
val url = "$protocol//$domain/$index_dir/$field.$tag_index_version.data"
val (offset, length) = data
if (length > 10000 || length <= 0)
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()
@@ -165,23 +208,25 @@ fun getSuggestionsFromData(field: String, data: Pair) : List "/tag/$ns:$tagname${separator}1$extension"
- "language" -> "/index-$tagname${separator}1$extension"
- else -> "/$ns/$tagname${separator}all${separator}1$extension"
- }
+ when (ns) {
+ "female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
+ "language" -> "/index-$tagname${separator}1$extension"
+ else -> "/$ns/$tagname${separator}all${separator}1$extension"
+ }
suggestions.add(Suggestion(tag, count, u, ns))
}
@@ -189,12 +234,17 @@ fun getSuggestionsFromData(field: String, data: Pair) : List {
- val nozomiAddress =
- when(area) {
- null -> "$protocol//$domain/$compressed_nozomi_prefix/$tag-$language$nozomiextension"
- else -> "$protocol//$domain/$compressed_nozomi_prefix/$area/$tag-$language$nozomiextension"
- }
+fun nozomiAddressFromArgs(args: SearchArgs, sortMode: SortMode) = when {
+ sortMode != SortMode.DATE_ADDED && sortMode != SortMode.RANDOM ->
+ if (args.area == "all") "$protocol//$domain/$compressed_nozomi_prefix/${sortMode.orderBy}/${sortMode.orderByKey}-${args.language}$nozomiextension"
+ else "$protocol//$domain/$compressed_nozomi_prefix/${args.area}/${sortMode.orderBy}/${sortMode.orderByKey}/${args.tag}-${args.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 {
+ val nozomiAddress = nozomiAddressFromArgs(args, sortMode)
val bytes = URL(nozomiAddress).readBytes()
@@ -210,13 +260,13 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<
return nozomi
}
-fun getGalleryIDsFromData(data: Pair) : Set {
+fun getGalleryIDsFromData(data: Pair): Set {
val url = "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.data"
val (offset, length) = data
if (length > 100000000 || length <= 0)
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()
@@ -226,7 +276,7 @@ fun getGalleryIDsFromData(data: Pair) : Set {
val numberOfGalleryIDs = buffer.int
- val expectedLength = numberOfGalleryIDs*4+4
+ val expectedLength = numberOfGalleryIDs * 4 + 4
if (numberOfGalleryIDs > 10000000 || numberOfGalleryIDs <= 0)
throw Exception("number_of_galleryids $numberOfGalleryIDs is too long")
@@ -239,33 +289,38 @@ fun getGalleryIDsFromData(data: Pair) : Set {
return galleryIDs
}
-fun getNodeAtAddress(field: String, address: Long) : Node? {
+fun getNodeAtAddress(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"
- }
+ 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"
+ }
- val nodedata = getURLAtRange(url, address.until(address+ max_node_size))
+ val nodedata = getURLAtRange(url, address.until(address + max_node_size))
return decodeNode(nodedata)
}
-fun getURLAtRange(url: String, range: LongRange) : ByteArray {
+fun getURLAtRange(url: String, range: LongRange): ByteArray {
val request = Request.Builder()
.url(url)
.header("Range", "bytes=${range.first}-${range.last}")
.build()
-
+
return client.newCall(request).execute().body()?.use { it.bytes() } ?: byteArrayOf()
}
@OptIn(ExperimentalUnsignedTypes::class)
-data class Node(val keys: List, val datas: List>, val subNodeAddresses: List)
+data class Node(
+ val keys: List,
+ val datas: List>,
+ val subNodeAddresses: List
+)
+
@OptIn(ExperimentalUnsignedTypes::class)
-fun decodeNode(data: ByteArray) : Node {
+fun decodeNode(data: ByteArray): Node {
val buffer = ByteBuffer
.wrap(data)
.order(ByteOrder.BIG_ENDIAN)
@@ -281,8 +336,8 @@ fun decodeNode(data: ByteArray) : Node {
if (keySize == 0 || keySize > 32)
throw Exception("fatal: !keySize || keySize > 32")
- keys.add(uData.sliceArray(buffer.position().until(buffer.position()+keySize)))
- buffer.position(buffer.position()+keySize)
+ keys.add(uData.sliceArray(buffer.position().until(buffer.position() + keySize)))
+ buffer.position(buffer.position() + keySize)
}
val numberOfDatas = buffer.int
@@ -295,7 +350,7 @@ fun decodeNode(data: ByteArray) : Node {
datas.add(Pair(offset, length))
}
- val numberOfSubNodeAddresses = B +1
+ val numberOfSubNodeAddresses = B + 1
val subNodeAddresses = ArrayList()
for (i in 0.until(numberOfSubNodeAddresses)) {
@@ -307,8 +362,8 @@ fun decodeNode(data: ByteArray) : Node {
}
@OptIn(ExperimentalUnsignedTypes::class)
-fun bSearch(field: String, key: UByteArray, node: Node) : Pair? {
- fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray) : Int {
+fun bSearch(field: String, key: UByteArray, node: Node): Pair? {
+ fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray): Int {
val top = min(dv1.size, dv2.size)
for (i in 0.until(top)) {
@@ -321,18 +376,18 @@ fun bSearch(field: String, key: UByteArray, node: Node) : Pair? {
return 0
}
- fun locateKey(key: UByteArray, node: Node) : Pair {
+ fun locateKey(key: UByteArray, node: Node): Pair {
for (i in node.keys.indices) {
val cmpResult = compareArrayBuffers(key, node.keys[i])
if (cmpResult <= 0)
- return Pair(cmpResult==0, i)
+ return Pair(cmpResult == 0, i)
}
return Pair(false, node.keys.size)
}
- fun isLeaf(node: Node) : Boolean {
+ fun isLeaf(node: Node): Boolean {
for (subnode in node.subNodeAddresses)
if (subnode != 0L)
return false
@@ -349,6 +404,6 @@ fun bSearch(field: String, key: UByteArray, node: Node) : Pair? {
else if (isLeaf(node))
return null
- val nextNode = getNodeAtAddress(field, node.subNodeAddresses[where]) ?: return null
+ val nextNode = getNodeAtAddress(field, node.subNodeAddresses[where])
return bSearch(field, key, nextNode)
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
index c924ce8e..26496b90 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
@@ -33,7 +33,6 @@ import android.widget.EditText
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
-import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
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.snackbar.Snackbar
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.suggestions.model.SearchSuggestion
import xyz.quaver.floatingsearchview.util.view.MenuView
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.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.getGalleryIDsFromNozomi
import xyz.quaver.pupil.hitomi.getSuggestionsForQuery
+import xyz.quaver.pupil.searchHistory
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.GalleryDialog
import xyz.quaver.pupil.ui.view.MainView
@@ -73,10 +93,19 @@ import kotlin.math.max
import kotlin.math.min
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 :
BaseActivity(),
- NavigationView.OnNavigationItemSelectedListener
-{
+ NavigationView.OnNavigationItemSelectedListener {
enum class Mode {
SEARCH,
@@ -85,25 +114,21 @@ class MainActivity :
FAVORITE
}
- enum class SortMode {
- NEWEST,
- POPULAR
- }
private val galleries = ArrayList()
private var query = ""
- set(value) {
- field = value
- with(findViewById(R.id.search_bar_text)) {
- if (text.toString() != value)
- setText(query, TextView.BufferType.EDITABLE)
+ set(value) {
+ field = value
+ with(findViewById(R.id.search_bar_text)) {
+ if (text.toString() != value)
+ setText(query, TextView.BufferType.EDITABLE)
+ }
}
- }
private var queryStack = mutableListOf()
private var mode = Mode.SEARCH
- private var sortMode = SortMode.NEWEST
+ private var sortMode = SortMode.DATE_ADDED
private var galleryIDs: Deferred>? = null
private var totalItems = 0
@@ -112,11 +137,12 @@ class MainActivity :
private lateinit var binding: MainActivityBinding
- private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
- if (!isGranted) {
- showNotificationPermissionExplanationDialog(this)
+ private val requestNotificationPermssionLauncher =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
+ if (!isGranted) {
+ showNotificationPermissionExplanationDialog(this)
+ }
}
- }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -127,9 +153,17 @@ class MainActivity :
intent.dataString?.let { url ->
restore(url,
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 = {
- 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) {}
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] &&
- ContextCompat.getExternalFilesDirs(this, null).filterNotNull().map { Uri.fromFile(it).toString() }
+ ContextCompat.getExternalFilesDirs(this, null).filterNotNull()
+ .map { Uri.fromFile(it).toString() }
.contains(Preferences["download_folder", ""])
) {
AlertDialog.Builder(this)
.setTitle(R.string.warning)
.setMessage(R.string.unaccessible_download_folder)
.setPositiveButton(android.R.string.ok) { _, _ ->
- DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
+ DownloadLocationDialogFragment().show(
+ supportFragmentManager,
+ "Download Location Dialog"
+ )
}.setNegativeButton(R.string.ignore) { _, _ ->
Preferences["download_folder_ignore_warning"] = true
}.show()
@@ -163,10 +204,12 @@ class MainActivity :
checkUpdate(this)
}
- @OptIn(ExperimentalStdlibApi::class)
override fun onBackPressed() {
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 {
query = queryStack.last()
@@ -175,6 +218,7 @@ class MainActivity :
fetchGalleries(query, sortMode)
loadBlocks()
}
+
else -> super.onBackPressed()
}
}
@@ -189,7 +233,7 @@ class MainActivity :
val perPage = Preferences["per_page", "25"].toInt()
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
- return when(keyCode) {
+ return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
if (currentPage > 0) {
runOnUiThread {
@@ -204,6 +248,7 @@ class MainActivity :
true
}
+
KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (currentPage < maxPage) {
runOnUiThread {
@@ -218,20 +263,22 @@ class MainActivity :
true
}
+
else -> super.onKeyDown(keyCode, event)
}
}
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) {
// -height of the search view < translationY < 0
binding.contents.searchview.translationY =
min(
max(
- binding.contents.searchview.translationY - dy,
- -binding.contents.searchview.binding.querySection.root.height.toFloat()
- ), 0F)
+ binding.contents.searchview.translationY - dy,
+ -binding.contents.searchview.binding.querySection.root.height.toFloat()
+ ), 0F
+ )
if (dy > 0)
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
binding.navView.setNavigationItemSelectedListener(this)
@@ -261,14 +313,17 @@ class MainActivity :
AlertDialog.Builder(context).apply {
setView(editText)
setTitle(R.string.main_jump_title)
- setMessage(getString(
- R.string.main_jump_message,
- currentPage+1,
- ceil(totalItems / perPage.toDouble()).roundToInt()
- ))
+ setMessage(
+ getString(
+ R.string.main_jump_message,
+ currentPage + 1,
+ ceil(totalItems / perPage.toDouble()).roundToInt()
+ )
+ )
setPositiveButton(android.R.string.ok) { _, _ ->
- currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
+ currentPage =
+ (editText.text.toString().toIntOrNull() ?: return@setPositiveButton) - 1
runOnUiThread {
cancelFetch()
@@ -322,7 +377,8 @@ class MainActivity :
setTitle(R.string.main_open_gallery_by_id)
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 {
onChipClickedHandler.add {
@@ -344,7 +400,7 @@ class MainActivity :
}
with(binding.contents.view) {
- setOnPageTurnListener(object: MainView.OnPageTurnListener {
+ setOnPageTurnListener(object : MainView.OnPageTurnListener {
override fun onPrev(page: Int) {
currentPage--
@@ -409,10 +465,11 @@ class MainActivity :
this@MainActivity,
requestNotificationPermssionLauncher
) {
- if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
+ if (DownloadManager.getInstance(context)
+ .isDownloading(galleryID)
+ ) { //download in progress
DownloadService.cancel(this@MainActivity, galleryID)
- }
- else {
+ } else {
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
DownloadService.download(this@MainActivity, galleryID)
}
@@ -485,6 +542,7 @@ class MainActivity :
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
} + FavoriteHistorySwitch(getString(R.string.search_show_histories))
}
+
else -> {
searchHistory.map {
Suggestion(it)
@@ -492,7 +550,7 @@ class MainActivity :
}
}.reversed()
- private var suggestionJob : Job? = null
+ private var suggestionJob: Job? = null
private fun setupSearchBar() {
with(binding.contents.searchview) {
val scrollSuggestionToTop = {
@@ -500,7 +558,10 @@ class MainActivity :
MainScope().launch {
withTimeout(1000) {
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)
delay(100)
}
@@ -509,7 +570,7 @@ class MainActivity :
}
}
- onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
+ onMenuStatusChangeListener = object : FloatingSearchView.OnMenuStatusChangeListener {
override fun onMenuOpened() {
(this@MainActivity.binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
}
@@ -561,7 +622,8 @@ class MainActivity :
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
val suggestions = kotlin.runCatching {
- getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }.toMutableList()
+ getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }
+ .toMutableList()
}.getOrElse { mutableListOf() }
suggestions.filter {
@@ -573,12 +635,16 @@ class MainActivity :
}
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() {
if (query.isEmpty() or query.endsWith(' ')) {
swapSuggestions(defaultSuggestions)
@@ -604,8 +670,14 @@ class MainActivity :
}
fun onActionMenuItemSelected(item: MenuItem?) {
- when(item?.itemId) {
- R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
+ when (item?.itemId) {
+ R.id.main_menu_settings -> startActivity(
+ Intent(
+ this@MainActivity,
+ SettingsActivity::class.java
+ )
+ )
+
R.id.main_menu_thin -> {
val thin = !item.isChecked
@@ -620,21 +692,15 @@ class MainActivity :
adapter = adapter // Force to redraw
}
}
- R.id.main_menu_sort_newest -> {
- sortMode = SortMode.NEWEST
- item.isChecked = true
- runOnUiThread {
- currentPage = 0
-
- cancelFetch()
- clearGalleries()
- fetchGalleries(query, sortMode)
- loadBlocks()
- }
- }
- R.id.main_menu_sort_popular -> {
- sortMode = SortMode.POPULAR
+ R.id.main_menu_sort_date_added,
+ R.id.main_menu_sort_date_published,
+ R.id.main_menu_sort_popular_today,
+ R.id.main_menu_sort_popular_week,
+ R.id.main_menu_sort_popular_month,
+ R.id.main_menu_sort_popular_year,
+ R.id.main_menu_sort_random -> {
+ sortMode = sortModeLookup[item.itemId]!!
item.isChecked = true
runOnUiThread {
@@ -653,7 +719,7 @@ class MainActivity :
runOnUiThread {
binding.drawer.closeDrawers()
- when(item.itemId) {
+ when (item.itemId) {
R.id.main_drawer_home -> {
cancelFetch()
clearGalleries()
@@ -664,6 +730,7 @@ class MainActivity :
fetchGalleries(query, sortMode)
loadBlocks()
}
+
R.id.main_drawer_history -> {
cancelFetch()
clearGalleries()
@@ -674,6 +741,7 @@ class MainActivity :
fetchGalleries(query, sortMode)
loadBlocks()
}
+
R.id.main_drawer_downloads -> {
cancelFetch()
clearGalleries()
@@ -684,6 +752,7 @@ class MainActivity :
fetchGalleries(query, sortMode)
loadBlocks()
}
+
R.id.main_drawer_favorite -> {
cancelFetch()
clearGalleries()
@@ -694,20 +763,35 @@ class MainActivity :
fetchGalleries(query, sortMode)
loadBlocks()
}
+
R.id.main_drawer_help -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
}
+
R.id.main_drawer_github -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
}
+
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 -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
}
+
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) {
- Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply {
- setAction(android.R.string.ok) {
- cancelFetch()
- clearGalleries()
- currentPage = 0
- mode = Mode.SEARCH
- queryStack.clear()
- fetchGalleries(query, sortMode)
- loadBlocks()
- }
- }.show()
+ Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT)
+ .apply {
+ setAction(android.R.string.ok) {
+ cancelFetch()
+ clearGalleries()
+ currentPage = 0
+ mode = Mode.SEARCH
+ queryStack.clear()
+ fetchGalleries(query, sortMode)
+ loadBlocks()
+ }
+ }.show()
}
galleryIDs = null
@@ -764,22 +849,16 @@ class MainActivity :
return
galleryIDs = CoroutineScope(Dispatchers.IO).async {
- when(mode) {
+ when (mode) {
Mode.SEARCH -> {
- when {
- query.isEmpty() and defaultQuery.isEmpty() -> {
- when(sortMode) {
- SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
- else -> getGalleryIDsFromNozomi(null, "index", "all")
- }.also {
- totalItems = it.size
- }
- }
- else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
- totalItems = it.size
- }
+ doSearch(
+ "$defaultQuery $query",
+ sortMode
+ ).also {
+ totalItems = it.size
}
}
+
Mode.HISTORY -> {
when {
query.isEmpty() -> {
@@ -787,36 +866,42 @@ class MainActivity :
totalItems = it.size
}
}
+
else -> {
- val result = doSearch(query).sorted()
+ val result = doSearch(query, SortMode.DATE_ADDED).sorted()
histories.reversed().filter { result.binarySearch(it) >= 0 }.also {
totalItems = it.size
}
}
}
}
+
Mode.DOWNLOAD -> {
- val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
+ val downloads =
+ DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
when {
query.isEmpty() -> downloads.reversed().also {
totalItems = it.size
}
+
else -> {
- val result = doSearch(query).sorted()
+ val result = doSearch(query, SortMode.DATE_ADDED).sorted()
downloads.reversed().filter { result.binarySearch(it) >= 0 }.also {
totalItems = it.size
}
}
}
}
+
Mode.FAVORITE -> {
when {
query.isEmpty() -> favorites.reversed().also {
totalItems = it.size
}
+
else -> {
- val result = doSearch(query).sorted()
+ val result = doSearch(query, SortMode.DATE_ADDED).sorted()
favorites.reversed().filter { result.binarySearch(it) >= 0 }.also {
totalItems = it.size
}
@@ -849,10 +934,18 @@ class MainActivity :
}
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)
chunk.map { galleryID ->
async {
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index b9fd3fa8..399601eb 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -26,11 +26,21 @@
app:showAsAction="ifRoom">
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index a713ac56..3548c82f 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -1,197 +1,170 @@
- 注意
- エラー
- 無視
- 制限なし
-
- クリップボードにコピーしました
-
- ダウンロード
- ダウンロードの進行を通知
- ダウンローダ
- ダウンローダの状態を表示
- アップデート
- アップデートの進行状況を表示
- 転送
- 他の機器へのデータ転送の進行状況を表示
-
- hitomi.laに接続できません
- ロックファイルが破損されています。Pupilを再インストールしてください。
- 結果なし
- アンドロイド11以上では、現在のダウンロードフォルダに外部アプリからアクセスできません。ダウンロードフォルダを変更しますか?
- 通知を無効にすると、バックグラウンドでのダウンロードとアプリのアップデート機能が使用不可になります。
-
- トップ
- 履歴
- ダウンロード
- ブックマーク
- お問い合わせ先
- ヘルプ
- ホームページ
- Github
- メールを送る
- ディスコード
-
- 簡単モード
-
- 並び替え
- 新しい順
- 人気順
-
- ページ移動
- 現ページ番号: %1$d\nページ数: %2$d
- IDで作品を開く
- エラーが発生しました
- ランダムに作品を開く
- すべてのダウンロードをキャンセル
-
- %1$dページへ移動
-
- ダウンロード
- 削除
-
- 最新版あり
- ダウンロードが完了しました
- ここをクリックして更新
- 最新版をダウンロード中…
- # 更新履歴(v%1$s)\n%2$s
-
- 作品を検索
- すべての作品を対象に検索
- 履歴を見る
- お気に入りのタグを見る
-
- 作品情報
- サムネイル
- おすすめ
- 作者
- グループ
- 言語
- シリーズ
- キャラクター
- タグ
-
+ 言語: %1$s
シリーズ: %1$s
タイプ: %1$s
- 言語: %1$s
-
-
- 読込中
- 移動
- 全画面
- 再試行
- まばたき検知スクロール
- まばたき検知を中止
- バックグラウンドでダウンロード
- バックグラウンドダウンロード中止
- ダウンロード中…
- ダウンロード完了
-
- カメラ権限が拒否されているため、まばたき検知使用できません
- この機器には前面カメラが装着されていません
-
- ダウンローダー起動中
-
- 設定
-
- バージョン(クリックで更新確認)
- v%s
- ベータ版チャンネルでアップデート
-
- 検索設定
- 一度に読み込む作品数
- 検索語句の初期値
-
- 保存領域
-
- 保存領域の管理
+ 結果なし
+ ギャラリー検索
+ キャッシュクリア
+ キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?
%s使用中
- 保存領域の使用量を算出中…
- キャッシュを削除
- キャッシュを削除すると画像の読込に時間がかかります。実行しますか?
- ダウンロードデータベースを再構築
- ダウンロード済みを削除
- ダウンロードした作品をすべて削除します。\n実行しますか?
+ ストレージ使用量読み込み中…
+ デフォルトキーワード
+ 一回にロードするギャラリー数
+ 検索設定
+ 設定
+ アップデートダウンロード中
+ 新しいアップデートがあります
+ 注意
+ その他
+ ミラーサーバー
履歴を削除
履歴を削除しますか?
履歴数: %1$d
-
- フォルダ名パターン
- フォルダ名に使用できない文字が含まれています
- 変数 %s は対応する値に置換されます\n\n%s
- ダウンロード場所
- 取り外し可能メディア
- 内部の保存領域
- %s 使用可能
- 手動で設定
- このフォルダにアクセスできません。他のフォルダを選択してください。
- キャッシュサイズ制限
- 画像を隠す
- 低解像度の画像
- 読込速度とデータ使用料を改善するため低解像度の画像を読み込む
- 他の機器にデータを転送
-
- アプリをロック
- アップをロックする方法
-
- ネットワーク
- ミラーサーバから画像を読み込む
- プロクシ
- 並列ダウンロード
-
- その他
- タグの言語
- Githubにて翻訳に参加できます
- 綴じ方向を左にする
+ 履歴
+ 通知を無効にするとバックグラウンドダウンロード及びアプリのアップデート機能が使用不可になります。
+ トップ
+ # リリースノート(v%1$s)\n%2$s
セキュリティーモード
アプリ履歴でアプリの画面を表示しない
- ダークモード
- 夜にシコりたい方々へ
- 旧ギャラリーインポート
- ユーザーID
- オープンソースライセンス
-
- ブックマーク管理
- ブックマークをバックアップ
- エラーが発生しました
- バックアップ共有
- バックアップファイルを作成しました
- ブックマーク復元
- 復元に失敗しました
- %1$d項目を復元しました
-
- ロック確認のためもう一回入力してください。
- 有効
- なし
- パターン
- パスワード
- 生体認証
- 指紋
- 予備のロックが設定されていないと指紋ロックは使用できません
- Pupil 指紋ロック™
- ロックを無効にしますか?
- ロックが一致しません。やり直してください。
-
- 検索語句の初期値を設定
- "言語: "
+ 移動
+ 非選択
BLフィルター
グロフィルター
- 登場人物を全て18歳以上にする
- 非選択
- ミラーサーバー
-
- プロクシの種類
- サーバーアドレス
- ポート番号
+ "言語: "
+ デフォルトキーワード設定
+ お問い合わせ先
+ ホームページ
+ ヘルプ
+ Github
+ メールを送る
+ フルスクリーン
+ ダウンロード
+ ダウンロードの進行を通知
+ バックグラウンドダウンロード
+ ダウンロード中…
+ ダウンロード完了
+ バックグラウンドダウンロード中止
+ ダウンロード
+ ランダム
+ ページ移動
+ 現ページ番号: %1$d\nページ数: %2$d
+ 転送
+ hitomi.laに接続できません
+ %1$dページへ移動
+ ダウンロード削除
+ ダウンロードしたギャラリーを全て削除します。\n実行しますか?
+ ミラーサーバからイメージをロード
+ ブックマーク
+ ギャラリー番号で見る
+ エラーが発生しました
+ ストレージ
+ ディスコード
+ アプリロック
+ アップロックの種類
+ バージョン(アップデート確認)
+ 生体認識
+ ロック確認のためもう一回入力してください。
+ 有効
+ 指紋
+ パスワード
+ パターン
+ ロックが一致しません。やり直してください。
+ なし
+ ロックを無効にしますか?
+ ロード中
+ ソート
+ 無視
+ ロックファイルが破損されています。Pupilを再再インストールしてください。
+ ダークモード
+ 夜にシコりたい方々へ
+ ギャラリー情報
+ アーティスト
+ キャラクター
+ グループ
+ 言語
+ シリーズ
+ タグ
+ サムネイル
+ おすすめ
+ イメージを隠す
+ 削除
+ ダウンロード
+ ブックマークバックアップ
+ ブックマーク復元
+ バックアップファイルを作成しました
+ 復元に失敗しました
+ %1$d項目を復元しました
+ ダウンロード場所
+ 内部ストレージ
+ 外部SDカード
+ %s 使用可能
+ ダウンロードが完了しました
+ ここをクリックしてアップデートを行えます
+ ベータチャンネルでアップデートを受信
+ v%s
+ 低解像度イメージ
+ ロード速度とデータ使用料を改善するため低解像度イメージをロード
+ 手動で設定
+ このフォルダにアクセスできません。他のフォルダを選択してください。
+ プロクシ
ID
+ プロクシタイプ
+ ポート
パスワード
エラー
+ サーバーアドレス
サーバー
-
+ 簡単モード
+ すべてのダウンロードキャンセル
+ アップデート
+ アップデートの進行状態を表示
+ 旧ギャラリーインポート
フォルダを読めません
旧ギャラリーインポート中…
インポート完了
+ ランダムギャラリーを開く
+ 予備のロックが設定されていないと指紋ロックは使用できません
+ Pupil指紋ロック™
こうかはばつぐんだ!
-
-
+ 登場人物を全て18歳以上にする
+ ユーザーID
+ クリップボードにコピーしました
+ リトライ
+ まばたき検知スクロール
+ 全てのギャラリーを対象に検索
+ 綴じ方向を左にする
+ ブックマーク管理
+ エラーが発生しました
+ バックアップ共有
+ ダウンローダ
+ ダウンローダの状態を表示
+ ダウンローダー起動中
+ フォルダ名パターン
+ フォルダ名に使用できない文字が含まれています
+ %sに含まれている文字列を対応する変数に置換します\n\n%s
+ ストレージ管理
+ オープンソースライセンス
+ お気に入りのタグを見る
+ 履歴を見る
+ まばたき検知を中止
+ カメラ権限が拒否されているため、まばたき検知使用できません
+ この機器には前面カメラが装着されていません
+ エラー
+ キャッシュサイズ制限
+ 制限なし
+ タグ言語
+ Githubにて翻訳に参加できます
+ 並列ダウンロード
+ アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか?
+ ネットワーク
+ ダウンロードデータベースを再構築
+ 他の機器にデータを転送
+ 他の機器へのデータ転送の進捗度を表示
+ 新しい順
+ 新しい順(発売日)
+ 人気順(今日)
+ 人気順(週間)
+ 人気順(月間)
+ 人気順(年間)
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 4edcfafc..b6589d2a 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -45,6 +45,7 @@
다운로드 완료
백그라운드 다운로드 취소
다운로드
+ 무작위
페이지 이동
현재 페이지: %1$d\n페이지 수: %2$d
전송
@@ -71,8 +72,6 @@
잠금을 해제할까요?
로딩중
정렬
- 인기순
- 시간순
무시
잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.
다크 모드
@@ -162,4 +161,10 @@
다운로드 데이터베이스 복구
다른 기기에 데이터 전송
다른 기기에 데이터 전송 시 상태 표시
+ 추가일
+ 발매일
+ 인기순 (오늘)
+ 인기순 (이번 주)
+ 인기순 (이번 달)
+ 인기순 (이번 해)
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 87164ae8..a9e0399d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -70,8 +70,13 @@
Thin Mode
Sort
- Newest
- Popular
+ Date Added
+ Date Published
+ Popular: Today
+ Popular: Week
+ Popular: Month
+ Popular: Year
+ Random
Jump to page
Current page: %1$d\nMaximum page: %2$d