1
.idea/vcs.xml
generated
@@ -2,6 +2,5 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/gh-pages" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -7,10 +7,10 @@ android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
applicationId "xyz.quaver.pupil"
|
||||
minSdkVersion 15
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 2
|
||||
versionName "1.1"
|
||||
versionCode 3
|
||||
versionName "1.2"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
@@ -19,9 +19,14 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def markwonVersion = "3.0.1"
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
|
||||
@@ -34,6 +39,7 @@ dependencies {
|
||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
|
||||
@@ -11,7 +11,9 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import xyz.quaver.hiyobi.getReader
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
@@ -38,16 +40,7 @@ class ExampleInstrumentedTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExperimentalUnsignedTypes
|
||||
fun test_doSearch() {
|
||||
Log.d("TEST", "Starting...")
|
||||
|
||||
runBlocking {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
Log.d("TEST", "This is started! wow")
|
||||
}.join()
|
||||
}
|
||||
|
||||
Log.d("TEST", "Finished! ...Really?")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.android.synthetic.main.activity_gallery.*
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.Reader
|
||||
@@ -32,9 +34,20 @@ class GalleryActivity : AppCompatActivity() {
|
||||
|
||||
setContentView(R.layout.activity_gallery)
|
||||
|
||||
supportActionBar?.title = intent.getStringExtra("GALLERY_TITLE")
|
||||
|
||||
galleryID = intent.getIntExtra("GALLERY_ID", 0)
|
||||
CoroutineScope(Dispatchers.Unconfined).launch {
|
||||
reader = async(Dispatchers.IO) {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryActivity)
|
||||
if (preference.getBoolean("use_hiyobi", false)) {
|
||||
try {
|
||||
xyz.quaver.hiyobi.getReader(galleryID)
|
||||
Log.d("Pupil", "Using Hiyobi.me")
|
||||
} catch (e: Exception) {
|
||||
getReader(galleryID)
|
||||
}
|
||||
}
|
||||
getReader(galleryID)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +56,18 @@ class GalleryActivity : AppCompatActivity() {
|
||||
loadImages()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val preferences = android.preference.PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
if (preferences.getBoolean("security_mode", false))
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE)
|
||||
else
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
loadJob?.cancel()
|
||||
@@ -78,17 +103,15 @@ class GalleryActivity : AppCompatActivity() {
|
||||
val reader = reader.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
supportActionBar?.title = reader.title
|
||||
|
||||
with(gallery_progressbar) {
|
||||
max = reader.images.size
|
||||
max = reader.size
|
||||
progress = 0
|
||||
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
reader.images.chunked(8).forEach { chunked ->
|
||||
reader.chunked(8).forEach { chunked ->
|
||||
chunked.map {
|
||||
async(Dispatchers.IO) {
|
||||
val url = if (it.second?.haswebp == 1) webpUrlFromUrl(it.first) else it.first
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.preference.PreferenceManager
|
||||
@@ -18,12 +19,15 @@ import android.text.style.AlignmentSpan
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.arlib.floatingsearchview.FloatingSearchView
|
||||
@@ -31,36 +35,43 @@ import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.activity_main_content.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.content
|
||||
import ru.noties.markwon.Markwon
|
||||
import xyz.quaver.hitomi.*
|
||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||
import xyz.quaver.pupil.types.TagSuggestion
|
||||
import xyz.quaver.pupil.util.Histories
|
||||
import xyz.quaver.pupil.util.SetLineOverlap
|
||||
import xyz.quaver.pupil.util.checkUpdate
|
||||
import xyz.quaver.pupil.util.getApkUrl
|
||||
import java.io.File
|
||||
import java.lang.StringBuilder
|
||||
import java.util.*
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val PERMISSION_REQUEST_CODE = 4585
|
||||
private val permissionRequestCode = 4585
|
||||
private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>()
|
||||
|
||||
private var isLoading = false
|
||||
private var query = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
private var galleryIDs: Deferred<List<Int>>? = null
|
||||
private var loadingJob: Job? = null
|
||||
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Histories.default = Histories(File(cacheDir, "histories.json"))
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
checkPermission()
|
||||
|
||||
update()
|
||||
checkUpdate()
|
||||
|
||||
main_appbar_layout.addOnOffsetChangedListener(
|
||||
AppBarLayout.OnOffsetChangedListener { _, p1 ->
|
||||
@@ -73,16 +84,60 @@ class MainActivity : AppCompatActivity() {
|
||||
setProgressViewOffset(false, 0, resources.getDimensionPixelSize(R.dimen.progress_view_offset))
|
||||
|
||||
setOnRefreshListener {
|
||||
runBlocking {
|
||||
cleanJob?.join()
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query)
|
||||
loadBlocks()
|
||||
}
|
||||
fetchGalleries(query, true)
|
||||
}
|
||||
}
|
||||
|
||||
main_nav_view.setNavigationItemSelectedListener {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
main_drawer_layout.closeDrawers()
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
when(it.itemId) {
|
||||
R.id.main_drawer_home -> {
|
||||
query = query.replace("HISTORY", "")
|
||||
fetchGalleries(query)
|
||||
}
|
||||
R.id.main_drawer_history -> {
|
||||
query += "HISTORY"
|
||||
fetchGalleries(query)
|
||||
}
|
||||
}
|
||||
loadBlocks()
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
setupRecyclerView()
|
||||
setupSearchBar()
|
||||
fetchGalleries(query)
|
||||
loadBlocks()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (main_drawer_layout.isDrawerOpen(GravityCompat.START))
|
||||
main_drawer_layout.closeDrawer(GravityCompat.START)
|
||||
else
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
if (preferences.getBoolean("security_mode", false))
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE)
|
||||
else
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
private fun checkPermission() {
|
||||
@@ -98,11 +153,54 @@ class MainActivity : AppCompatActivity() {
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
else
|
||||
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
|
||||
ActivityCompat.requestPermissions(this, permissions, permissionRequestCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun update() {
|
||||
private fun checkUpdate() {
|
||||
|
||||
fun extractReleaseNote(update: JsonObject, locale: String) : String {
|
||||
val markdown = update["body"]!!.content
|
||||
|
||||
val target = when(locale) {
|
||||
"ko" -> "한국어"
|
||||
"ja" -> "日本語"
|
||||
else -> "English"
|
||||
}
|
||||
|
||||
val releaseNote = Regex("^# Release Note.+$")
|
||||
val language = Regex("^## $target$")
|
||||
val end = Regex("^#.+$")
|
||||
|
||||
var releaseNoteFlag = false
|
||||
var languageFlag = false
|
||||
|
||||
val result = StringBuilder()
|
||||
|
||||
for(line in markdown.split('\n')) {
|
||||
if (releaseNote.matches(line)) {
|
||||
releaseNoteFlag = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (releaseNoteFlag) {
|
||||
if (language.matches(line)) {
|
||||
languageFlag = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (languageFlag) {
|
||||
if (end.matches(line))
|
||||
break
|
||||
|
||||
result.append(line+"\n")
|
||||
}
|
||||
}
|
||||
|
||||
return getString(R.string.update_release_note, update["tag_name"]?.content, result.toString())
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
||||
return
|
||||
|
||||
@@ -114,8 +212,13 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
val dialog = AlertDialog.Builder(this@MainActivity).apply {
|
||||
setTitle(R.string.update_title)
|
||||
setMessage(getString(R.string.update_message, update["tag_name"], BuildConfig.VERSION_NAME))
|
||||
val msg = extractReleaseNote(update, Locale.getDefault().language)
|
||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
Toast.makeText(
|
||||
context, getString(R.string.update_download_started), Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
val dest = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName)
|
||||
val desturi =
|
||||
FileProvider.getUriForFile(
|
||||
@@ -131,6 +234,7 @@ class MainActivity : AppCompatActivity() {
|
||||
setDescription(getString(R.string.update_notification_description))
|
||||
setTitle(getString(R.string.app_name))
|
||||
setDestinationUri(Uri.fromFile(dest))
|
||||
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
|
||||
}
|
||||
|
||||
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
@@ -161,12 +265,15 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun setupRecyclerView() {
|
||||
with(main_recyclerview) {
|
||||
adapter = GalleryBlockAdapter(galleries).apply {
|
||||
setClickListener {
|
||||
setClickListener { galleryID, title ->
|
||||
val intent = Intent(this@MainActivity, GalleryActivity::class.java)
|
||||
intent.putExtra("GALLERY_ID", it)
|
||||
intent.putExtra("GALLERY_ID", galleryID)
|
||||
intent.putExtra("GALLERY_TITLE", title)
|
||||
|
||||
//TODO: Maybe sprinke some transitions will be nice :D
|
||||
startActivity(intent)
|
||||
|
||||
Histories.default.add(galleryID)
|
||||
}
|
||||
}
|
||||
addOnScrollListener(
|
||||
@@ -176,9 +283,9 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||
|
||||
if (!isLoading)
|
||||
if (loadingJob?.isActive != true)
|
||||
if (layoutManager.findLastCompletelyVisibleItemPosition() == galleries.size)
|
||||
fetchGalleries(query)
|
||||
loadBlocks()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -295,101 +402,107 @@ class MainActivity : AppCompatActivity() {
|
||||
if (query != this@MainActivity.query) {
|
||||
this@MainActivity.query = query
|
||||
|
||||
fetchGalleries(query, true)
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
attachNavigationDrawerToMenuButton(main_drawer_layout)
|
||||
}
|
||||
}
|
||||
|
||||
private val cache = ArrayList<Int>()
|
||||
private var currentFetchingJob: Job? = null
|
||||
private var cleanJob: Job? = null
|
||||
|
||||
private fun cancelFetch() {
|
||||
isLoading = false
|
||||
|
||||
runBlocking {
|
||||
cleanJob?.join()
|
||||
currentFetchingJob?.cancelAndJoin()
|
||||
galleryIDs?.cancelAndJoin()
|
||||
loadingJob?.cancelAndJoin()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchGalleries(query: String, clear: Boolean = false) {
|
||||
private fun clearGalleries() {
|
||||
galleries.clear()
|
||||
|
||||
main_recyclerview.adapter?.notifyDataSetChanged()
|
||||
|
||||
main_noresult.visibility = View.INVISIBLE
|
||||
main_progressbar.show()
|
||||
main_swipe_layout.isRefreshing = false
|
||||
}
|
||||
|
||||
private fun fetchGalleries(query: String, from: Int = 0) {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
||||
val defaultQuery = preference.getString("default_query", "")!!
|
||||
|
||||
if (clear) {
|
||||
cancelFetch()
|
||||
cleanJob = CoroutineScope(Dispatchers.Main).launch {
|
||||
cache.clear()
|
||||
galleries.clear()
|
||||
galleryIDs = null
|
||||
|
||||
main_recyclerview.adapter?.notifyDataSetChanged()
|
||||
|
||||
main_noresult.visibility = View.INVISIBLE
|
||||
main_progressbar.show()
|
||||
main_swipe_layout.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading)
|
||||
if (galleryIDs?.isActive == true)
|
||||
return
|
||||
|
||||
isLoading = true
|
||||
galleryIDs = CoroutineScope(Dispatchers.IO).async {
|
||||
when {
|
||||
query.contains("HISTORY") ->
|
||||
Histories.default.toList()
|
||||
query.isEmpty() and defaultQuery.isEmpty() ->
|
||||
fetchNozomi(start = from, count = perPage)
|
||||
else ->
|
||||
doSearch("$defaultQuery $query")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentFetchingJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val galleryIDs: List<Int>
|
||||
private fun loadBlocks() {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
||||
val defaultQuery = preference.getString("default_query", "")!!
|
||||
|
||||
cleanJob?.join()
|
||||
loadingJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
val galleryIDs = galleryIDs?.await()
|
||||
|
||||
if (query.isEmpty() && defaultQuery.isEmpty())
|
||||
galleryIDs = fetchNozomi(start = galleries.size, count = perPage)
|
||||
else {
|
||||
if (cache.isEmpty())
|
||||
cache.addAll(doSearch("$defaultQuery $query"))
|
||||
|
||||
galleryIDs = cache.slice(galleries.size until Math.min(galleries.size + perPage, cache.size))
|
||||
|
||||
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
||||
noMore = galleries.size + perPage >= cache.size
|
||||
}
|
||||
if (galleryIDs.isNullOrEmpty()) { //No result
|
||||
withContext(Dispatchers.Main) {
|
||||
main_noresult.visibility = View.VISIBLE
|
||||
main_progressbar.hide()
|
||||
}
|
||||
|
||||
if (query.isNotEmpty() and defaultQuery.isNotEmpty() and cache.isNullOrEmpty()) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (query.isEmpty() and defaultQuery.isEmpty())
|
||||
fetchGalleries("", galleries.size+perPage)
|
||||
else
|
||||
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
||||
noMore = galleries.size + perPage >= galleryIDs.size
|
||||
}
|
||||
|
||||
when {
|
||||
query.isEmpty() and defaultQuery.isEmpty() ->
|
||||
galleryIDs
|
||||
else ->
|
||||
galleryIDs.slice(galleries.size until Math.min(galleries.size+perPage, galleryIDs.size))
|
||||
}.chunked(4).forEach { chunked ->
|
||||
chunked.map {
|
||||
async {
|
||||
val galleryBlock = getGalleryBlock(it)
|
||||
val thumbnail: Bitmap
|
||||
|
||||
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
|
||||
thumbnail = BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
|
||||
Pair(galleryBlock, thumbnail)
|
||||
}
|
||||
}.forEach {
|
||||
val galleryBlock = it.await()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
main_noresult.visibility = View.VISIBLE
|
||||
main_progressbar.hide()
|
||||
|
||||
galleries.add(galleryBlock)
|
||||
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
galleryIDs.chunked(4).forEach { chunked ->
|
||||
chunked.map {
|
||||
async {
|
||||
val galleryBlock = getGalleryBlock(it)
|
||||
val thumbnail: Bitmap
|
||||
|
||||
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
|
||||
thumbnail = BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
|
||||
Pair(galleryBlock, thumbnail)
|
||||
}
|
||||
}.forEach {
|
||||
val galleryBlock = it.await()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
main_progressbar.hide()
|
||||
|
||||
galleries.add(galleryBlock)
|
||||
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import xyz.quaver.pupil.util.Histories
|
||||
import java.io.File
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
@@ -25,6 +28,18 @@ class SettingsActivity : AppCompatActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
if (preferences.getBoolean("security_mode", false))
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE)
|
||||
else
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private val suffix = listOf(
|
||||
@@ -35,8 +50,8 @@ class SettingsActivity : AppCompatActivity() {
|
||||
"TB" //really?
|
||||
)
|
||||
|
||||
private fun getCacheSize() : String {
|
||||
var size = context!!.cacheDir.walk().map { it.length() }.sum()
|
||||
private fun getCacheSize(dir: File) : String {
|
||||
var size = dir.walk().map { it.length() }.sum()
|
||||
var suffixIndex = 0
|
||||
|
||||
while (size >= 1024) {
|
||||
@@ -50,22 +65,43 @@ class SettingsActivity : AppCompatActivity() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
|
||||
with(findPreference<Preference>("delete_cache")) {
|
||||
with(findPreference<Preference>("delete_image_cache")) {
|
||||
this ?: return@with
|
||||
|
||||
summary = getCacheSize()
|
||||
val dir = File(context.cacheDir, "imageCache")
|
||||
|
||||
summary = getCacheSize(dir)
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_cache_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
with(context.cacheDir) {
|
||||
if (exists())
|
||||
deleteRecursively()
|
||||
}
|
||||
if (dir.exists())
|
||||
dir.deleteRecursively()
|
||||
|
||||
summary = getCacheSize()
|
||||
summary = getCacheSize(dir)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
with(findPreference<Preference>("clear_history")) {
|
||||
this ?: return@with
|
||||
|
||||
val histories = Histories.default
|
||||
|
||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.settings_clear_history_alert_message)
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
histories.clear()
|
||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
|
||||
@@ -33,8 +33,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?
|
||||
class ViewHolder(val view: CardView) : RecyclerView.ViewHolder(view)
|
||||
class ProgressViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
|
||||
|
||||
private var callback: ((Int) -> Unit)? = null
|
||||
fun setClickListener(callback: ((Int) -> Unit)?) {
|
||||
private var callback: ((Int, String) -> Unit)? = null
|
||||
fun setClickListener(callback: ((Int, String) -> Unit)?) {
|
||||
this.callback = callback
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?
|
||||
val series = gallery.series.ifEmpty { listOf("N/A") }
|
||||
|
||||
setOnClickListener {
|
||||
callback?.invoke(gallery.id)
|
||||
callback?.invoke(gallery.id, gallery.title)
|
||||
}
|
||||
|
||||
galleryblock_thumbnail.setImageBitmap(thumbnail)
|
||||
|
||||
74
app/src/main/java/xyz/quaver/pupil/util/history.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.parseList
|
||||
import kotlinx.serialization.stringify
|
||||
import java.io.File
|
||||
|
||||
|
||||
class Histories(private val file: File) : ArrayList<Int>() {
|
||||
|
||||
init {
|
||||
if (!file.exists())
|
||||
file.parentFile.mkdirs()
|
||||
|
||||
try {
|
||||
load()
|
||||
} catch (e: Exception) {
|
||||
save()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var default: Histories
|
||||
|
||||
fun load(file: File) : Histories {
|
||||
return Histories(file).load()
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||
fun load() : Histories {
|
||||
return apply {
|
||||
super.clear()
|
||||
addAll(
|
||||
Json(JsonConfiguration.Stable).parseList(
|
||||
file.bufferedReader().use { it.readText() }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||
fun save() {
|
||||
file.writeText(Json(JsonConfiguration.Stable).stringify(this))
|
||||
}
|
||||
|
||||
override fun add(element: Int): Boolean {
|
||||
load()
|
||||
|
||||
if (contains(element))
|
||||
super.remove(element)
|
||||
|
||||
super.add(0, element)
|
||||
|
||||
save()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun remove(element: Int): Boolean {
|
||||
load()
|
||||
val retval = super.remove(element)
|
||||
save()
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
super.clear()
|
||||
save()
|
||||
}
|
||||
}
|
||||
10
app/src/main/res/drawable-anydpi/ic_history.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable-anydpi/ic_home.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_history.png
Normal file
|
After Width: | Height: | Size: 585 B |
BIN
app/src/main/res/drawable-hdpi/ic_home.png
Normal file
|
After Width: | Height: | Size: 279 B |
BIN
app/src/main/res/drawable-mdpi/ic_history.png
Normal file
|
After Width: | Height: | Size: 366 B |
BIN
app/src/main/res/drawable-mdpi/ic_home.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
app/src/main/res/drawable-xhdpi/ic_history.png
Normal file
|
After Width: | Height: | Size: 700 B |
BIN
app/src/main/res/drawable-xhdpi/ic_home.png
Normal file
|
After Width: | Height: | Size: 314 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_history.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_home.png
Normal file
|
After Width: | Height: | Size: 395 B |
BIN
app/src/main/res/drawable/side_nav_bar.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -1,83 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
<include layout="@layout/activity_main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/main_appbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:visibility="invisible"
|
||||
android:background="@color/transparent"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:id="@+id/main_progressbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_noresult"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/main_no_result"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/main_swipe_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="-64dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/main_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="64dp"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.arlib.floatingsearchview.FloatingSearchView
|
||||
android:id="@+id/main_searchview"
|
||||
android:layout_width="match_parent"
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/main_nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:floatingSearch_searchBarMarginLeft="8dp"
|
||||
app:floatingSearch_searchBarMarginRight="8dp"
|
||||
app:floatingSearch_searchBarMarginTop="8dp"
|
||||
app:floatingSearch_searchHint="@string/search_hint"
|
||||
app:floatingSearch_suggestionsListAnimDuration="250"
|
||||
app:floatingSearch_showSearchKey="true"
|
||||
app:floatingSearch_leftActionMode="noLeftAction"
|
||||
app:floatingSearch_menu="@menu/main"
|
||||
app:floatingSearch_dismissOnOutsideTouch="true"
|
||||
app:floatingSearch_close_search_on_keyboard_dismiss="true"/>
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:headerLayout="@layout/nav_header_main"
|
||||
app:menu="@menu/activity_main_drawer"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
83
app/src/main/res/layout/activity_main_content.xml
Normal file
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/main_appbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:visibility="invisible"
|
||||
android:background="@color/transparent"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:id="@+id/main_progressbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_noresult"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/main_no_result"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/main_swipe_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="-64dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/main_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="64dp"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.arlib.floatingsearchview.FloatingSearchView
|
||||
android:id="@+id/main_searchview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:floatingSearch_searchBarMarginLeft="8dp"
|
||||
app:floatingSearch_searchBarMarginRight="8dp"
|
||||
app:floatingSearch_searchBarMarginTop="8dp"
|
||||
app:floatingSearch_searchHint="@string/search_hint"
|
||||
app:floatingSearch_suggestionsListAnimDuration="250"
|
||||
app:floatingSearch_showSearchKey="true"
|
||||
app:floatingSearch_leftActionMode="showHamburger"
|
||||
app:floatingSearch_menu="@menu/main"
|
||||
app:floatingSearch_dismissOnOutsideTouch="true"
|
||||
app:floatingSearch_close_search_on_keyboard_dismiss="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
12
app/src/main/res/layout/nav_header_main.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_header_height"
|
||||
android:background="@drawable/side_nav_bar"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:gravity="bottom"/>
|
||||
15
app/src/main/res/menu/activity_main_drawer.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<group android:checkableBehavior="single">
|
||||
<item android:id="@+id/main_drawer_home"
|
||||
android:title="@string/main_drawer_home"
|
||||
android:checked="true"
|
||||
android:icon="@drawable/ic_home"/>
|
||||
|
||||
<item android:id="@+id/main_drawer_history"
|
||||
android:title="@string/main_drawer_history"
|
||||
android:icon="@drawable/ic_history"/>
|
||||
</group>
|
||||
|
||||
</menu>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Pupil</string>
|
||||
<string name="galleryblock_language">言語: %1$s</string>
|
||||
<string name="galleryblock_series">シリーズ: %1$s</string>
|
||||
<string name="galleryblock_type">タイプ: %1$s</string>
|
||||
@@ -8,7 +7,7 @@
|
||||
<string name="main_search">検索</string>
|
||||
<string name="search_hint">ギャラリー検索</string>
|
||||
<string name="settings_cache_title">キャッシュ</string>
|
||||
<string name="settings_clear_cache">キャッシュクリア</string>
|
||||
<string name="settings_clear_image_cache">イメージキャッシュクリア</string>
|
||||
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
|
||||
<string name="settings_clear_cache_summary">キャッシュサイズ: %1$d%2$s</string>
|
||||
<string name="settings_default_query">デフォルト検索キーワード</string>
|
||||
@@ -16,8 +15,19 @@
|
||||
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
|
||||
<string name="settings_search_title">検索設定</string>
|
||||
<string name="settings_title">設定</string>
|
||||
<string name="update_message">新バージョン%1$sをリリースしました\n(現バージョン: %2$s)\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_use_hiyobi_summary">ロード速度を向上させるためhiyobi.meからイメージロード</string>
|
||||
<string name="settings_use_hiyobi_title">hiyobi.meからロード</string>
|
||||
<string name="settings_clear_history">履歴の削除</string>
|
||||
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
|
||||
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
||||
<string name="main_drawer_history">履歴</string>
|
||||
<string name="main_drawer_home">トップ</string>
|
||||
<string name="update_download_started">ダウンロード中</string>
|
||||
<string name="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
|
||||
<string name="settings_security_mode_title">セキュリティーモード</string>
|
||||
<string name="settings_security_mode_summary">アプリ履歴でアプリの画面を表示しない</string>
|
||||
</resources>
|
||||
@@ -1,23 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Pupil</string>
|
||||
<string name="galleryblock_language">언어: %1$s</string>
|
||||
<string name="galleryblock_series">시리즈: %1$s</string>
|
||||
<string name="galleryblock_type">종류: %1$s</string>
|
||||
<string name="permission_explain">권한을 거부하면 일부 기능이 작동하지 않을 수 있습니다</string>
|
||||
<string name="search_hint">갤러리 검색</string>
|
||||
<string name="settings_default_query">기본 검색어</string>
|
||||
<string name="settings_clear_cache">캐시 정리하기</string>
|
||||
<string name="settings_clear_image_cache">이미지 캐시 정리하기</string>
|
||||
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
|
||||
<string name="settings_clear_cache_summary">현재 캐시 사용량: %1$d%2$s</string>
|
||||
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
|
||||
<string name="settings_search_title">검색 설정</string>
|
||||
<string name="settings_title">설정</string>
|
||||
<string name="update_message">버전 %1$s이 출시되었습니다.\n(현재 %2$s)\n업데이트 하시겠습니까?</string>
|
||||
<string name="update_notification_description">apk 다운로드중…</string>
|
||||
<string name="update_title">업데이트가 있습니다!</string>
|
||||
<string name="warning">경고</string>
|
||||
<string name="main_no_result">결과 없음</string>
|
||||
<string name="main_search">검색</string>
|
||||
<string name="settings_cache_title">캐시</string>
|
||||
<string name="settings_miscellaneous_title">기타</string>
|
||||
<string name="settings_use_hiyobi_summary">속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드</string>
|
||||
<string name="settings_use_hiyobi_title">hiyobi.me 사용</string>
|
||||
<string name="settings_clear_history">히스토리 삭제</string>
|
||||
<string name="settings_clear_history_alert_message">히스토리를 삭제하시겠습니까?</string>
|
||||
<string name="settings_clear_history_summary">히스토리 %1$d개 저장됨</string>
|
||||
<string name="main_drawer_history">히스토리</string>
|
||||
<string name="main_drawer_home">홈</string>
|
||||
<string name="update_download_started">다운로드 중</string>
|
||||
<string name="update_release_note"># 릴리즈 노트(v%1$s)\n%2$s</string>
|
||||
<string name="settings_security_mode_summary">최근 앱 목록 창에서 앱 화면을 보이지 않게 합니다</string>
|
||||
<string name="settings_security_mode_title">보안 모드 활성화</string>
|
||||
</resources>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:statusBarColor">@color/transparent</item>
|
||||
|
||||
@@ -2,4 +2,9 @@
|
||||
<resources>
|
||||
<dimen name="appbar_padding">64dp</dimen>
|
||||
<dimen name="progress_view_offset">80dp</dimen>
|
||||
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="nav_header_vertical_spacing">8dp</dimen>
|
||||
<dimen name="nav_header_height">176dp</dimen>
|
||||
</resources>
|
||||
@@ -18,9 +18,13 @@
|
||||
<string name="main_search">Search</string>
|
||||
<string name="main_no_result">No result</string>
|
||||
|
||||
<string name="main_drawer_home">Home</string>
|
||||
<string name="main_drawer_history">History</string>
|
||||
|
||||
<string name="update_title">Update available</string>
|
||||
<string name="update_message">Version %1$s is available!\n(current version is %2$s)\nDo you want to update?</string>
|
||||
<string name="update_download_started">Download started</string>
|
||||
<string name="update_notification_description">Downloading apk…</string>
|
||||
<string name="update_release_note"># Release Note(v%1$s)\n%2$s</string>
|
||||
|
||||
<string name="search_hint">Search galleries</string>
|
||||
|
||||
@@ -33,8 +37,16 @@
|
||||
<string name="settings_galleries_per_page">Galleries per page</string>
|
||||
<string name="settings_default_query">Default query</string>
|
||||
<string name="settings_cache_title">Cache</string>
|
||||
<string name="settings_clear_cache">Clear cache</string>
|
||||
<string name="settings_clear_image_cache">Clear image cache</string>
|
||||
<string name="settings_clear_cache_summary">Currently using %1$d%2$s of cache</string>
|
||||
<string name="settings_clear_cache_alert_message">Deleting cache can affect image loading speed. Do you want to continue?</string>
|
||||
<string name="settings_clear_history">Clear history</string>
|
||||
<string name="settings_clear_history_alert_message">Do you want to clear histories?</string>
|
||||
<string name="settings_clear_history_summary">%1$d histories saved</string>
|
||||
<string name="settings_miscellaneous_title">Miscellaneous</string>
|
||||
<string name="settings_use_hiyobi_title">Use hiyobi.me</string>
|
||||
<string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string>
|
||||
<string name="settings_security_mode_title">Enable security mode</string>
|
||||
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -25,8 +25,28 @@
|
||||
app:title="@string/settings_cache_title">
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_clear_cache"
|
||||
app:key="delete_cache"/>
|
||||
app:title="@string/settings_clear_image_cache"
|
||||
app:key="delete_image_cache"/>
|
||||
|
||||
<Preference
|
||||
app:title="@string/settings_clear_history"
|
||||
app:key="clear_history"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="@string/settings_miscellaneous_title">
|
||||
|
||||
<SwitchPreference
|
||||
app:key="use_hiyobi"
|
||||
app:title="@string/settings_use_hiyobi_title"
|
||||
app:summary="@string/settings_use_hiyobi_summary"/>
|
||||
|
||||
<SwitchPreference
|
||||
app:key="security_mode"
|
||||
app:title="@string/settings_security_mode_title"
|
||||
app:summary="@string/settings_security_mode_summary"
|
||||
app:defaultValue="true"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import org.junit.Test
|
||||
import xyz.quaver.pupil.util.checkUpdate
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
@@ -12,8 +12,9 @@ import xyz.quaver.pupil.util.checkUpdate
|
||||
class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
@ImplicitReflectionSerializer
|
||||
fun test() {
|
||||
print(checkUpdate("https://api.github.com/repos/tom5079/Pupil-issue/releases", "0.0.1"))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package xyz.quaver.hitomi
|
||||
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.list
|
||||
import kotlinx.serialization.parseList
|
||||
import org.jsoup.Jsoup
|
||||
import java.net.URL
|
||||
|
||||
@@ -18,10 +16,7 @@ data class GalleryInfo(
|
||||
val name: String,
|
||||
val height: Int
|
||||
)
|
||||
data class Reader(
|
||||
val title: String,
|
||||
val images: List<Pair<URL, GalleryInfo?>>
|
||||
)
|
||||
typealias Reader = List<Pair<URL, GalleryInfo?>>
|
||||
//Set header `Referer` to reader url to avoid 403 error
|
||||
fun getReader(galleryID: Int) : Reader {
|
||||
val readerUrl = "https://hitomi.la/reader/$galleryID.html"
|
||||
@@ -29,8 +24,6 @@ fun getReader(galleryID: Int) : Reader {
|
||||
|
||||
val doc = Jsoup.connect(readerUrl).get()
|
||||
|
||||
val title = doc.selectFirst("title").text()
|
||||
|
||||
val images = doc.select(".img-url").map {
|
||||
URL(protocol + urlFromURL(it.text()))
|
||||
}
|
||||
@@ -49,5 +42,5 @@ fun getReader(galleryID: Int) : Reader {
|
||||
if (images.size > galleryInfo.size)
|
||||
galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
|
||||
|
||||
return Reader(title, images zip galleryInfo)
|
||||
return images zip galleryInfo
|
||||
}
|
||||
47
libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package xyz.quaver.hiyobi
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.json.content
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
const val hiyobi = "xn--9w3b15m8vo.asia"
|
||||
const val user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
|
||||
|
||||
var cookie: String = ""
|
||||
|
||||
fun renewCookie() : String {
|
||||
val url = "https://$hiyobi/"
|
||||
|
||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("User-Agent", user_agent)
|
||||
connectTimeout = 2000
|
||||
connect()
|
||||
return headerFields["Set-Cookie"]!![0]
|
||||
}
|
||||
}
|
||||
|
||||
fun getReader(galleryId: Int) : Reader {
|
||||
val url = "https://$hiyobi/data/json/${galleryId}_list.json"
|
||||
|
||||
if (cookie.isEmpty())
|
||||
cookie = renewCookie()
|
||||
|
||||
val json = Json(JsonConfiguration.Stable).parseJson(
|
||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("User-Agent", user_agent)
|
||||
setRequestProperty("Cookie", cookie)
|
||||
connectTimeout = 2000
|
||||
connect()
|
||||
|
||||
inputStream.bufferedReader().use { it.readText() }
|
||||
}
|
||||
)
|
||||
|
||||
return json.jsonArray.map {
|
||||
val name = it.jsonObject["name"]!!.content
|
||||
Pair(URL("https://$hiyobi/data/$galleryId/$name"), null)
|
||||
}
|
||||
}
|
||||
@@ -61,4 +61,9 @@ class UnitTest {
|
||||
|
||||
print(reader)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_hiyobi() {
|
||||
xyz.quaver.hiyobi.getReader(1414061)
|
||||
}
|
||||
}
|
||||