UI update

Added sort by popularity functionality
Added auto update
This commit is contained in:
tom5079
2019-07-07 15:21:56 +09:00
parent dca6ba457b
commit 1eb75acb40
28 changed files with 506 additions and 363 deletions

View File

@@ -1,8 +1,120 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<XML>
<option name="XML_KEEP_LINE_BREAKS" value="false" />
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings> </codeStyleSettings>

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -13,7 +13,7 @@ android {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 20 versionCode 21
versionName "2.12" versionName "2.12"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
@@ -24,6 +24,9 @@ android {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
buildTypes.each {
it.buildConfigField('boolean', 'PRERELEASE', 'true')
}
} }
kotlinOptions { kotlinOptions {
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental' freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":20,"versionName":"2.11.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":21,"versionName":"2.12","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@@ -3,7 +3,9 @@
package="xyz.quaver.pupil"> package="xyz.quaver.pupil">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application <application
android:name=".Pupil" android:name=".Pupil"
@@ -14,6 +16,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".ui.LockActivity"/> <activity android:name=".ui.LockActivity"/>
<activity <activity
android:name=".ui.ReaderActivity" android:name=".ui.ReaderActivity"

View File

@@ -18,10 +18,13 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import android.app.DownloadManager
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat

View File

@@ -1,31 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.types
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import kotlinx.android.parcel.Parcelize
import xyz.quaver.hitomi.Suggestion
@Parcelize
class SelectorSuggestion : SearchSuggestion {
override fun getBody(): String {
return ""
}
}

View File

@@ -20,11 +20,16 @@ package xyz.quaver.pupil.ui
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
import android.view.* import android.view.*
@@ -35,9 +40,9 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@@ -60,11 +65,9 @@ import kotlinx.serialization.list
import kotlinx.serialization.stringify import kotlinx.serialization.stringify
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.* import xyz.quaver.hitomi.*
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.SelectorSuggestion
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.types.Tags
@@ -88,6 +91,11 @@ class MainActivity : AppCompatActivity() {
DOWNLOAD, DOWNLOAD,
FAVORITE FAVORITE
} }
enum class SortMode {
NEWEST,
POPULAR
}
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>() private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
@@ -101,6 +109,7 @@ class MainActivity : AppCompatActivity() {
} }
private var mode = Mode.SEARCH private var mode = Mode.SEARCH
private var sortMode = SortMode.NEWEST
private val REQUEST_SETTINGS = 45162 private val REQUEST_SETTINGS = 45162
private val REQUEST_LOCK = 561 private val REQUEST_LOCK = 561
@@ -156,7 +165,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
else -> super.onBackPressed() else -> super.onBackPressed()
@@ -189,7 +198,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
} }
@@ -203,7 +212,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
} }
@@ -221,7 +230,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread { runOnUiThread {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
} }
@@ -278,14 +287,36 @@ class MainActivity : AppCompatActivity() {
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
val update = val update =
checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch checkUpdate(getString(R.string.release_url)) ?: return@launch
val (url, fileName) = getApkUrl(update) ?: return@launch
fileName ?: return@launch
val dialog = AlertDialog.Builder(this@MainActivity).apply { val dialog = AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.update_title) setTitle(R.string.update_title)
val msg = extractReleaseNote(update, Locale.getDefault().language) val msg = extractReleaseNote(update, Locale.getDefault().language)
setMessage(Markwon.create(context).toMarkdown(msg)) setMessage(Markwon.create(context).toMarkdown(msg))
setPositiveButton(android.R.string.yes) { _, _ -> setPositiveButton(android.R.string.yes) { _, _ ->
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.update)))) val request = DownloadManager.Request(Uri.parse(url)).apply {
setDescription(getString(R.string.update_notification_description))
setTitle(getString(R.string.app_name))
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
}
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val id = manager.enqueue(request)
registerReceiver(object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val install = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(manager.getUriForDownloadedFile(id), manager.getMimeTypeForDownloadedFile(id))
}
startActivity(install)
unregisterReceiver(this)
}
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
} }
setNegativeButton(android.R.string.no) { _, _ ->} setNegativeButton(android.R.string.no) { _, _ ->}
} }
@@ -308,6 +339,13 @@ class MainActivity : AppCompatActivity() {
main_searchview.translationY = p1.toFloat() main_searchview.translationY = p1.toFloat()
main_recyclerview.scrollBy(0, prevP1 - p1) main_recyclerview.scrollBy(0, prevP1 - p1)
with(main_fab) {
if (prevP1 > p1)
hideMenuButton(true)
else if (prevP1 < p1)
showMenuButton(true)
}
prevP1 = p1 prevP1 = p1
} }
) )
@@ -324,7 +362,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0 currentPage = 0
query = "" query = ""
mode = Mode.SEARCH mode = Mode.SEARCH
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_history -> { R.id.main_drawer_history -> {
@@ -333,7 +371,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0 currentPage = 0
query = "" query = ""
mode = Mode.HISTORY mode = Mode.HISTORY
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_downloads -> { R.id.main_drawer_downloads -> {
@@ -342,7 +380,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0 currentPage = 0
query = "" query = ""
mode = Mode.DOWNLOAD mode = Mode.DOWNLOAD
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_favorite -> { R.id.main_drawer_favorite -> {
@@ -351,7 +389,7 @@ class MainActivity : AppCompatActivity() {
currentPage = 0 currentPage = 0
query = "" query = ""
mode = Mode.FAVORITE mode = Mode.FAVORITE
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_help -> { R.id.main_drawer_help -> {
@@ -375,9 +413,67 @@ class MainActivity : AppCompatActivity() {
true true
} }
with(main_fab_jump) {
setImageResource(R.drawable.ic_jump)
setOnClickListener {
val preference = PreferenceManager.getDefaultSharedPreferences(context)
val perPage = preference.getString("per_page", "25")!!.toInt()
val editText = EditText(context)
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()
))
setPositiveButton(android.R.string.ok) { _, _ ->
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
}.show()
}
}
with(main_fab_id) {
setImageResource(R.drawable.numeric)
setOnClickListener {
val editText = EditText(context)
AlertDialog.Builder(context).apply {
setView(editText)
setTitle(R.string.main_open_gallery_by_id)
setPositiveButton(android.R.string.ok) { _, _ ->
CoroutineScope(Dispatchers.Default).launch {
try {
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
val gallery =
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
intent.putExtra("galleryID", gallery.id)
startActivity(intent)
} catch (e: Exception) {
Snackbar.make(main_layout,
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
}
}
}
}.show()
}
}
setupSearchBar() setupSearchBar()
setupRecyclerView() setupRecyclerView()
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
@@ -391,7 +487,7 @@ class MainActivity : AppCompatActivity() {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
} }
@@ -459,7 +555,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread { runOnUiThread {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
} }
@@ -527,7 +623,6 @@ class MainActivity : AppCompatActivity() {
runOnUiThread { runOnUiThread {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query)
loadBlocks() loadBlocks()
} }
@@ -714,55 +809,31 @@ class MainActivity : AppCompatActivity() {
setOnMenuItemClickListener { setOnMenuItemClickListener {
when(it.itemId) { when(it.itemId) {
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS) R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
R.id.main_menu_jump -> { R.id.main_menu_sort_newest -> {
val preference = PreferenceManager.getDefaultSharedPreferences(context) sortMode = SortMode.NEWEST
val perPage = preference.getString("per_page", "25")!!.toInt() it.isChecked = true
val editText = EditText(context)
AlertDialog.Builder(context).apply { runOnUiThread {
setView(editText) currentPage = 0
setTitle(R.string.main_jump_title)
setMessage(getString(
R.string.main_jump_message,
currentPage+1,
ceil(totalItems / perPage.toDouble()).roundToInt()
))
setPositiveButton(android.R.string.ok) { _, _ -> cancelFetch()
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1 clearGalleries()
fetchGalleries(query, sortMode)
runOnUiThread { loadBlocks()
cancelFetch() }
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
}
}.show()
} }
R.id.main_menu_id -> { R.id.main_menu_sort_popular -> {
val editText = EditText(context) sortMode = SortMode.POPULAR
it.isChecked = true
AlertDialog.Builder(context).apply { runOnUiThread {
setView(editText) currentPage = 0
setTitle(R.string.main_open_gallery_by_id)
setPositiveButton(android.R.string.ok) { _, _ -> cancelFetch()
CoroutineScope(Dispatchers.Default).launch { clearGalleries()
try { fetchGalleries(query, sortMode)
val intent = Intent(this@MainActivity, ReaderActivity::class.java) loadBlocks()
val gallery = }
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
intent.putExtra("galleryID", gallery.id)
startActivity(intent)
} catch (e: Exception) {
Snackbar.make(main_layout,
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
}
}
}
}.show()
} }
} }
} }
@@ -770,20 +841,20 @@ class MainActivity : AppCompatActivity() {
setOnQueryChangeListener { _, query -> setOnQueryChangeListener { _, query ->
this@MainActivity.query = query this@MainActivity.query = query
suggestionJob?.cancel()
clearSuggestions() clearSuggestions()
if (query.isEmpty() or query.endsWith(' ')) { if (query.isEmpty() or query.endsWith(' ')) {
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map { swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
TagSuggestion(it.tag, -1, "", it.area ?: "tag") TagSuggestion(it.tag, -1, "", it.area ?: "tag")
} + SelectorSuggestion()) })
return@setOnQueryChangeListener return@setOnQueryChangeListener
} }
val currentQuery = query.split(" ").last().replace('_', ' ') val currentQuery = query.split(" ").last().replace('_', ' ')
suggestionJob?.cancel()
suggestionJob = CoroutineScope(Dispatchers.IO).launch { suggestionJob = CoroutineScope(Dispatchers.IO).launch {
val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }) val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) })
@@ -802,103 +873,72 @@ class MainActivity : AppCompatActivity() {
} }
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ -> setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
if (item is SelectorSuggestion) { item as TagSuggestion
var hasSelector = false
with(suggestionView as LinearLayout) { val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child is ConstraintLayout) {
child.visibility = View.VISIBLE
hasSelector = true
}
else
child.visibility = View.GONE
}
}
if (!hasSelector) { leftIcon.setImageDrawable(
val view = LayoutInflater.from(context) ResourcesCompat.getDrawable(
.inflate(R.layout.item_selector_suggestion, suggestionView, false) resources,
when(item.n) {
"female" -> R.drawable.ic_gender_female
"male" -> R.drawable.ic_gender_male
"language" -> R.drawable.ic_translate
"group" -> R.drawable.ic_account_group
"character" -> R.drawable.ic_account_star
"series" -> R.drawable.ic_book_open
"artist" -> R.drawable.ic_brush
else -> R.drawable.ic_tag
},
null)
)
suggestionView.addView(view) with(suggestionView.findViewById<ImageView>(R.id.right_icon)) {
}
} else if(item is TagSuggestion) {
with(suggestionView as LinearLayout) {
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child is ConstraintLayout) {
child.visibility = View.GONE
}
else
child.visibility = View.VISIBLE
}
}
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
leftIcon.setImageDrawable( if (Tags(json.parse(serializer, favoritesFile.readText())).contains(tag))
ResourcesCompat.getDrawable( setImageResource(R.drawable.ic_star_filled)
resources, else
when(item.n) { setImageResource(R.drawable.ic_star_empty)
"female" -> R.drawable.ic_gender_female
"male" -> R.drawable.ic_gender_male
"language" -> R.drawable.ic_translate
"group" -> R.drawable.ic_account_group
"character" -> R.drawable.ic_account_star
"series" -> R.drawable.ic_book_open
"artist" -> R.drawable.ic_brush
else -> R.drawable.ic_tag
},
null)
)
with(suggestionView.findViewById<ImageView>(R.id.right_icon)) { rotation = 0f
isEnabled = true
if (Tags(json.parse(serializer, favoritesFile.readText())).contains(tag)) setColorFilter(ContextCompat.getColor(context, R.color.material_orange_500))
setImageResource(R.drawable.ic_star_filled)
else isClickable = true
setOnClickListener {
val favorites = Tags(json.parse(serializer, favoritesFile.readText()))
if (favorites.contains(tag)) {
setImageResource(R.drawable.ic_star_empty) setImageResource(R.drawable.ic_star_empty)
favorites.remove(tag)
rotation = 0f
isEnabled = true
setColorFilter(ContextCompat.getColor(context, R.color.material_orange_500))
isClickable = true
setOnClickListener {
val favorites = Tags(json.parse(serializer, favoritesFile.readText()))
if (favorites.contains(tag)) {
setImageResource(R.drawable.ic_star_empty)
favorites.remove(tag)
}
else {
setImageDrawable(AnimatedVectorDrawableCompat.create(context,
R.drawable.avd_star
))
(drawable as Animatable).start()
favorites.add(tag)
}
favoritesFile.writeText(json.stringify(favorites))
} }
else {
setImageDrawable(AnimatedVectorDrawableCompat.create(context,
R.drawable.avd_star
))
(drawable as Animatable).start()
favorites.add(tag)
}
favoritesFile.writeText(json.stringify(favorites))
} }
}
if (item.t == -1) { if (item.t == -1) {
textView.text = item.s textView.text = item.s
} else { } else {
val text = "${item.s}\n ${item.t}" val text = "${item.s}\n ${item.t}"
val len = text.length val len = text.length
val left = item.s.length val left = item.s.length
textView.text = SpannableString(text).apply { textView.text = SpannableString(text).apply {
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE) val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
} }
} }
} }
@@ -924,24 +964,18 @@ class MainActivity : AppCompatActivity() {
if (query.isEmpty() or query.endsWith(' ')) if (query.isEmpty() or query.endsWith(' '))
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map { swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
TagSuggestion(it.tag, -1, "", it.area ?: "tag") TagSuggestion(it.tag, -1, "", it.area ?: "tag")
} + SelectorSuggestion()) })
} }
override fun onFocusCleared() { override fun onFocusCleared() {
suggestionJob?.cancel() suggestionJob?.cancel()
val query = searchInputView.text.toString() runOnUiThread {
cancelFetch()
if (query != this@MainActivity.query) { clearGalleries()
this@MainActivity.query = query currentPage = 0
fetchGalleries(query, sortMode)
runOnUiThread { loadBlocks()
cancelFetch()
clearGalleries()
currentPage = 0
fetchGalleries(query)
loadBlocks()
}
} }
} }
}) })
@@ -970,9 +1004,8 @@ class MainActivity : AppCompatActivity() {
main_progressbar.show() main_progressbar.show()
} }
private fun fetchGalleries(query: String) { private fun fetchGalleries(query: String, sortMode: SortMode) {
val preference = PreferenceManager.getDefaultSharedPreferences(this) val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!! val defaultQuery = preference.getString("default_query", "")!!
galleryIDs = null galleryIDs = null
@@ -985,12 +1018,14 @@ class MainActivity : AppCompatActivity() {
Mode.SEARCH -> { Mode.SEARCH -> {
when { when {
query.isEmpty() and defaultQuery.isEmpty() -> { query.isEmpty() and defaultQuery.isEmpty() -> {
fetchNozomi(start = currentPage*perPage, count = perPage).let { when(sortMode) {
totalItems = it.second SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
it.first else -> getGalleryIDsFromNozomi(null, "index", "all")
}.apply {
totalItems = size
} }
} }
else -> doSearch("$defaultQuery $query").apply { else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).apply {
totalItems = size totalItems = size
} }
} }
@@ -1043,7 +1078,6 @@ class MainActivity : AppCompatActivity() {
private fun loadBlocks() { private fun loadBlocks() {
val preference = PreferenceManager.getDefaultSharedPreferences(this) val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25 val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
loadingJob = CoroutineScope(Dispatchers.IO).launch { loadingJob = CoroutineScope(Dispatchers.IO).launch {
val galleryIDs = galleryIDs?.await() val galleryIDs = galleryIDs?.await()
@@ -1057,12 +1091,7 @@ class MainActivity : AppCompatActivity() {
return@launch return@launch
} }
when { galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks ->
query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
galleryIDs
else ->
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 {

View File

@@ -25,7 +25,6 @@ import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
@@ -40,7 +39,6 @@ import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.io.IOException
import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.ImplicitReflectionSerializer
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
@@ -222,15 +220,8 @@ class ReaderActivity : AppCompatActivity() {
private fun initDownloader() { private fun initDownloader() {
var d: GalleryDownloader? = GalleryDownloader.get(galleryID) var d: GalleryDownloader? = GalleryDownloader.get(galleryID)
if (d == null) { if (d == null)
try { d = GalleryDownloader(this, galleryID)
d = GalleryDownloader(this, galleryID)
} catch (e: IOException) {
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
finish()
return
}
}
downloader = d.apply { downloader = d.apply {
onReaderLoadedHandler = { onReaderLoadedHandler = {
@@ -268,8 +259,7 @@ class ReaderActivity : AppCompatActivity() {
} }
} }
onErrorHandler = { onErrorHandler = {
if (it is IOException) Snackbar.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE).show()
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
downloader.download = false downloader.download = false
} }
onCompleteHandler = { onCompleteHandler = {
@@ -323,6 +313,11 @@ class ReaderActivity : AppCompatActivity() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
if (dy < 0)
this@ReaderActivity.reader_fab.showMenuButton(true)
else if (dy > 0)
this@ReaderActivity.reader_fab.hideMenuButton(true)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager val layoutManager = recyclerView.layoutManager as LinearLayoutManager
if (layoutManager.findFirstVisibleItemPosition() == -1) if (layoutManager.findFirstVisibleItemPosition() == -1)

View File

@@ -102,55 +102,59 @@ class GalleryDownloader(
initNotification() initNotification()
reader = CoroutineScope(Dispatchers.IO).async { reader = CoroutineScope(Dispatchers.IO).async {
download = _notify try {
val json = Json(JsonConfiguration.Stable) download = _notify
val serializer = Reader.serializer() val json = Json(JsonConfiguration.Stable)
val serializer = Reader.serializer()
//Check cache //Check cache
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json") val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json")
if (cache.exists()) { if (cache.exists()) {
val cached = json.parse(serializer, cache.readText()) val cached = json.parse(serializer, cache.readText())
if (cached.readerItems.isNotEmpty()) { if (cached.readerItems.isNotEmpty()) {
useHiyobi = when { useHiyobi = when {
cached.readerItems[0].url.contains("hitomi.la") -> false cached.readerItems[0].url.contains("hitomi.la") -> false
else -> true else -> true
}
onReaderLoadedHandler?.invoke(cached)
return@async cached
} }
onReaderLoadedHandler?.invoke(cached)
return@async cached
} }
}
//Cache doesn't exist. Load from internet //Cache doesn't exist. Load from internet
val reader = when { val reader = when {
useHiyobi -> { useHiyobi -> {
xyz.quaver.hiyobi.getReader(galleryID).let { xyz.quaver.hiyobi.getReader(galleryID).let {
when { when {
it.readerItems.isEmpty() -> { it.readerItems.isEmpty() -> {
useHiyobi = false useHiyobi = false
getReader(galleryID) getReader(galleryID)
}
else -> it
} }
else -> it
} }
} }
else -> {
getReader(galleryID)
}
} }
else -> {
getReader(galleryID) if (reader.readerItems.isNotEmpty()) {
//Save cache
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, reader))
} }
reader
} catch (e: Exception) {
Reader("", listOf())
} }
if (reader.readerItems.isNotEmpty()) {
//Save cache
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, reader))
}
reader
} }
} }
@@ -160,8 +164,10 @@ class GalleryDownloader(
downloadJob = CoroutineScope(Dispatchers.Default).launch { downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader!!.await() val reader = reader!!.await()
if (reader.readerItems.isEmpty()) if (reader.readerItems.isEmpty()) {
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader")) onErrorHandler?.invoke(IOException(getString(R.string.unable_to_connect)))
return@launch
}
val list = ArrayList<String>() val list = ArrayList<String>()

View File

@@ -19,6 +19,7 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import xyz.quaver.pupil.BuildConfig
import java.net.URL import java.net.URL
fun getReleases(url: String) : JsonArray { fun getReleases(url: String) : JsonArray {
@@ -31,26 +32,27 @@ fun getReleases(url: String) : JsonArray {
} }
} }
fun checkUpdate(url: String, currentVersion: String) : JsonObject? { fun checkUpdate(url: String) : JsonObject? {
val releases = getReleases(url) val releases = getReleases(url)
if (releases.isEmpty()) if (releases.isEmpty())
return null return null
val latestVersion = releases[0].jsonObject["tag_name"]?.content return releases.firstOrNull {
if (BuildConfig.PRERELEASE) {
BuildConfig.VERSION_NAME != it.jsonObject["tag_name"]?.content
} else {
it.jsonObject["prerelease"]?.boolean == false &&
BuildConfig.VERSION_NAME != (it.jsonObject["tag_name"]?.content ?: "")
}
}?.jsonObject
}
return when { fun getApkUrl(releases: JsonObject) : Pair<String?, String?>? {
currentVersion.split('-').size == 1 -> { releases["assets"]?.jsonArray?.forEach {
when { if (Regex("Pupil-v(\\d+\\.)+\\d+\\.apk").matches(it.jsonObject["name"]?.content ?: ""))
currentVersion != latestVersion -> releases[0].jsonObject return Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
else -> null
}
}
else -> {
when {
(currentVersion.split('-')[0] == latestVersion) -> releases[0].jsonObject
else -> null
}
}
} }
return null
} }

View File

@@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#fff" <vector android:height="24dp" android:tint="#fff"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#ff000000" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/> <path android:fillColor="#fff" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
</vector> </vector>

View File

@@ -1,8 +0,0 @@
<!-- drawable/numeric.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
</vector>

View File

@@ -0,0 +1,8 @@
<!-- drawable/image_broken_variant.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M21,5V11.59L18,8.58L14,12.59L10,8.59L6,12.59L3,9.58V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5M18,11.42L21,14.43V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V12.42L6,15.41L10,11.41L14,15.41" />
</vector>

View File

@@ -4,5 +4,5 @@
android:width="24dp" android:width="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" /> <path android:fillColor="#fff" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
</vector> </vector>

View File

@@ -0,0 +1,8 @@
<!-- drawable/sort_variant.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#fff" android:pathData="M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z" />
</vector>

View File

@@ -56,6 +56,30 @@
android:scrollbars="vertical" android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<com.github.clans.fab.FloatingActionMenu
android:id="@+id/main_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:menu_colorNormal="@color/colorAccent">
<com.github.clans.fab.FloatingActionButton
android:id="@+id/main_fab_jump"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fab_label="@string/main_jump_title"
app:fab_size="mini"/>
<com.github.clans.fab.FloatingActionButton
android:id="@+id/main_fab_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fab_label="@string/main_open_gallery_by_id"
app:fab_size="mini"/>
</com.github.clans.fab.FloatingActionMenu>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.arlib.floatingsearchview.FloatingSearchView <com.arlib.floatingsearchview.FloatingSearchView

View File

@@ -39,6 +39,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="4dp" android:layout_height="4dp"
android:progressTint="@color/material_green_a700" android:progressTint="@color/material_green_a700"
tools:ignore="UnusedAttribute"
android:visibility="gone"/> android:visibility="gone"/>
</LinearLayout> </LinearLayout>
@@ -55,7 +56,6 @@
android:id="@+id/reader_fab_download" android:id="@+id/reader_fab_download"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_downloading"
app:fab_label="@string/reader_fab_download" app:fab_label="@string/reader_fab_download"
app:fab_size="mini"/> app:fab_size="mini"/>
@@ -63,7 +63,6 @@
android:id="@+id/reader_fab_fullscreen" android:id="@+id/reader_fab_fullscreen"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_fullscreen"
app:fab_label="@string/reader_fab_fullscreen" app:fab_label="@string/reader_fab_fullscreen"
app:fab_size="mini"/> app:fab_size="mini"/>

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_height="1dp"
android:background="@color/dark_gray"
android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<Button
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="favorite" />
<Button
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="history" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,15 +2,19 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/main_menu_jump" <item
android:icon="@drawable/ic_jump" android:id="@+id/main_menu_sort"
android:title="@string/main_jump_title" android:title="@string/main_menu_sort">
app:showAsAction="ifRoom"/> <menu>
<group android:checkableBehavior="single">
<item android:id="@+id/main_menu_id" <item android:id="@+id/main_menu_sort_newest"
android:icon="@drawable/ic_numeric" android:title="@string/main_menu_sort_newest"
android:title="@string/main_open_gallery_by_id" android:checked="true"/>
app:showAsAction="ifRoom"/> <item android:id="@+id/main_menu_sort_popular"
android:title="@string/main_menu_sort_popular"/>
</group>
</menu>
</item>
<item <item
android:id="@+id/main_menu_settings" android:id="@+id/main_menu_settings"

View File

@@ -80,4 +80,7 @@
<string name="settings_lock_none">なし</string> <string name="settings_lock_none">なし</string>
<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_newest">投稿日時順</string>
<string name="main_menu_sort_popular">人気順</string>
</resources> </resources>

View File

@@ -54,7 +54,7 @@
<string name="unable_to_connect">hitomi.la에 연결할 수 없습니다</string> <string name="unable_to_connect">hitomi.la에 연결할 수 없습니다</string>
<string name="main_move">%1$d 페이지로 이동</string> <string name="main_move">%1$d 페이지로 이동</string>
<string name="https_block_alert_title">접속 불가 현상 안내</string> <string name="https_block_alert_title">접속 불가 현상 안내</string>
<string name="https_block_alert">최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다\n이 경우 플레이스토어에서 SNIper앱을 이용하시면 정상이용이 가능합니다.</string> <string name="https_block_alert">최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다 이 경우 플레이스토어에서 Intra앱을 이용하시면 정상이용이 가능합니다.</string>
<string name="main_dialog_export">갤러리 내보내기</string> <string name="main_dialog_export">갤러리 내보내기</string>
<string name="main_export_complete">내보내기 완료</string> <string name="main_export_complete">내보내기 완료</string>
<string name="main_export_open_folder">폴더 열기</string> <string name="main_export_open_folder">폴더 열기</string>
@@ -80,4 +80,7 @@
<string name="settings_lock_none">없음</string> <string name="settings_lock_none">없음</string>
<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_popular">인기순</string>
<string name="main_menu_sort_newest">시간순</string>
</resources> </resources>

View File

@@ -1,7 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" translatable="false" tools:override="true">Pupil</string> <string name="app_name" translatable="false" tools:override="true">Pupil</string>
<string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil-issue/releases</string> <string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil/releases</string>
<string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string> <string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
<string name="home_page" translatable="false">http://bit.ly/2EZDClw</string> <string name="home_page" translatable="false">http://bit.ly/2EZDClw</string>
@@ -45,6 +45,10 @@
<string name="main_drawer_group_contact_email">Email me!</string> <string name="main_drawer_group_contact_email">Email me!</string>
<string name="main_drawer_grouop_contact_kakaotalk">Kakaotalk</string> <string name="main_drawer_grouop_contact_kakaotalk">Kakaotalk</string>
<string name="main_menu_sort">Sort</string>
<string name="main_menu_sort_newest">Newest</string>
<string name="main_menu_sort_popular">Popular</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>
<string name="main_open_gallery_by_id">Open Gallery by ID</string> <string name="main_open_gallery_by_id">Open Gallery by ID</string>

View File

@@ -32,7 +32,10 @@ class ExampleUnitTest {
@Test @Test
fun test() { fun test() {
val current = "0.1"
val latest = "0.2"
print(current < latest)
} }
} }

View File

@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.40' ext.kotlin_version = '1.3.41'
repositories { repositories {
google() google()
jcenter() jcenter()

View File

@@ -16,13 +16,13 @@
package xyz.quaver.hitomi package xyz.quaver.hitomi
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import java.util.* import java.util.*
import java.util.concurrent.Executors
fun doSearch(query: String) : List<Int> {
val time = System.currentTimeMillis()
fun doSearch(query: String, sortByPopularity: Boolean = false) : List<Int> {
val terms = query val terms = query
.trim() .trim()
.replace(Regex("""^\?"""), "") .replace(Regex("""^\?"""), "")
@@ -42,7 +42,20 @@ fun doSearch(query: String) : List<Int> {
positiveTerms.push(term) positiveTerms.push(term)
} }
val positiveResults = positiveTerms.map {
CoroutineScope(Dispatchers.IO).async {
getGalleryIDsForQuery(it)
}
}
val negativeResults = negativeTerms.map {
CoroutineScope(Dispatchers.IO).async {
getGalleryIDsForQuery(it)
}
}
var results = when { var results = when {
sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", "all")
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all") positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all")
else -> getGalleryIDsForQuery(positiveTerms.poll()) else -> getGalleryIDsForQuery(positiveTerms.poll())
} }
@@ -57,25 +70,19 @@ fun doSearch(query: String) : List<Int> {
} }
//positive results //positive results
positiveTerms.map { positiveResults.forEach {
async(Dispatchers.IO) { val result = it.await()
Pair(getGalleryIDsForQuery(it), true)
}
}+negativeTerms.map {
async(Dispatchers.IO) {
Pair(getGalleryIDsForQuery(it), false)
}
}.forEach {
val (result, isPositive) = it.await()
when { filterPositive(result.sorted())
isPositive -> filterPositive(result.sorted()) }
else -> filterNegative(result.sorted())
} //negative results
negativeResults.forEach {
val result = it.await()
filterNegative(result.sorted())
} }
} }
println("PUPIL/SEARCH TIME ${System.currentTimeMillis() - time}ms")
return results return results
} }

View File

@@ -16,6 +16,7 @@
package xyz.quaver.hitomi package xyz.quaver.hitomi
import java.io.ByteArrayOutputStream
import java.net.URL import java.net.URL
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
@@ -179,8 +180,10 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : List
val nozomi = ArrayList<Int>() val nozomi = ArrayList<Int>()
val bytes = inputStream.readBytes()
val arrayBuffer = ByteBuffer val arrayBuffer = ByteBuffer
.wrap(inputStream.readBytes()) .wrap(bytes)
.order(ByteOrder.BIG_ENDIAN) .order(ByteOrder.BIG_ENDIAN)
while (arrayBuffer.hasRemaining()) while (arrayBuffer.hasRemaining())

View File

@@ -19,9 +19,6 @@
package xyz.quaver.hitomi package xyz.quaver.hitomi
import org.junit.Test import org.junit.Test
import java.net.InetAddress
import java.net.UnknownHostException
class UnitTest { class UnitTest {
@Test @Test
@@ -31,9 +28,9 @@ class UnitTest {
@Test @Test
fun test_nozomi() { fun test_nozomi() {
val nozomi = fetchNozomi(start = 0, count = 5) val nozomi = getGalleryIDsFromNozomi(null, "popular", "all")
nozomi.first print(nozomi.size)
} }
@Test @Test
@@ -52,7 +49,7 @@ class UnitTest {
@Test @Test
fun test_doSearch() { fun test_doSearch() {
val r = doSearch("female:loli female:bondage language:korean -male:yaoi -male:guro -female:guro") val r = doSearch("female:loli female:bondage language:korean -male:yaoi -male:guro -female:guro", true)
print(r.size) print(r.size)
} }