@@ -9,8 +9,8 @@ android {
|
||||
applicationId "xyz.quaver.pupil"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 3
|
||||
versionName "1.2"
|
||||
versionCode 4
|
||||
versionName "1.3"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
@@ -40,6 +40,7 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||
implementation 'com.shawnlin:number-picker:2.4.8'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
|
||||
@@ -4,16 +4,10 @@ import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
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.
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
package="xyz.quaver.pupil">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -25,7 +23,7 @@
|
||||
android:resource="@xml/filepaths"/>
|
||||
</provider>
|
||||
|
||||
<activity android:name=".GalleryActivity"
|
||||
<activity android:name=".ReaderActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"/>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
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
|
||||
import xyz.quaver.hitomi.getReader
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.pupil.adapters.GalleryAdapter
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class GalleryActivity : AppCompatActivity() {
|
||||
|
||||
private val images = ArrayList<String>()
|
||||
private var galleryID = 0
|
||||
private lateinit var reader: Deferred<Reader>
|
||||
private var loadJob: Job? = null
|
||||
private var screenMode = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
initView()
|
||||
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()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
gallery_recyclerview.adapter = GalleryAdapter(images).apply {
|
||||
setOnClick {
|
||||
val attrs = window.attributes
|
||||
|
||||
screenMode = (screenMode+1)%2
|
||||
|
||||
when(screenMode) {
|
||||
0 -> {
|
||||
attrs.flags = attrs.flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
|
||||
supportActionBar?.show()
|
||||
}
|
||||
1 -> {
|
||||
attrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
supportActionBar?.hide()
|
||||
}
|
||||
}
|
||||
window.attributes = attrs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadImages() {
|
||||
|
||||
fun webpUrlFromUrl(url: URL) = URL(url.toString().replace("/galleries/", "/webp/") + ".webp")
|
||||
|
||||
loadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||
val reader = reader.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
with(gallery_progressbar) {
|
||||
max = reader.size
|
||||
progress = 0
|
||||
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
reader.chunked(8).forEach { chunked ->
|
||||
chunked.map {
|
||||
async(Dispatchers.IO) {
|
||||
val url = if (it.second?.haswebp == 1) webpUrlFromUrl(it.first) else it.first
|
||||
|
||||
val fileName: String
|
||||
|
||||
with(url.path) {
|
||||
fileName = substring(lastIndexOf('/')+1)
|
||||
}
|
||||
|
||||
val cache = File(cacheDir, "/imageCache/$galleryID/$fileName")
|
||||
|
||||
if (!cache.exists())
|
||||
with(url.openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("Referer", getReferer(galleryID))
|
||||
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
inputStream.copyTo(FileOutputStream(cache))
|
||||
}
|
||||
|
||||
cache.absolutePath
|
||||
}
|
||||
}.forEach {
|
||||
val cache = it.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
images.add(cache)
|
||||
gallery_recyclerview.adapter?.notifyItemInserted(images.size - 1)
|
||||
gallery_progressbar.progress++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
gallery_progressbar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,15 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
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
|
||||
import android.text.*
|
||||
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
|
||||
@@ -44,19 +28,18 @@ 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.ItemClickSupport
|
||||
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.io.FileOutputStream
|
||||
import java.util.*
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val permissionRequestCode = 4585
|
||||
private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>()
|
||||
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
|
||||
|
||||
private var query = ""
|
||||
|
||||
@@ -67,9 +50,12 @@ class MainActivity : AppCompatActivity() {
|
||||
Histories.default = Histories(File(cacheDir, "histories.json"))
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_main)
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
)
|
||||
|
||||
checkPermission()
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
checkUpdate()
|
||||
|
||||
@@ -81,7 +67,11 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
|
||||
with(main_swipe_layout) {
|
||||
setProgressViewOffset(false, 0, resources.getDimensionPixelSize(R.dimen.progress_view_offset))
|
||||
setProgressViewOffset(
|
||||
false,
|
||||
resources.getDimensionPixelSize(R.dimen.progress_view_start),
|
||||
resources.getDimensionPixelSize(R.dimen.progress_view_offset)
|
||||
)
|
||||
|
||||
setOnRefreshListener {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@@ -97,17 +87,36 @@ class MainActivity : AppCompatActivity() {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
main_drawer_layout.closeDrawers()
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
when(it.itemId) {
|
||||
R.id.main_drawer_home -> {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
query = query.replace("HISTORY", "")
|
||||
fetchGalleries(query)
|
||||
}
|
||||
R.id.main_drawer_history -> {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
query += "HISTORY"
|
||||
fetchGalleries(query)
|
||||
}
|
||||
R.id.main_drawer_help -> {
|
||||
AlertDialog.Builder(this@MainActivity).apply {
|
||||
title = getString(R.string.help_dialog_title)
|
||||
setMessage(R.string.help_dialog_message)
|
||||
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
}
|
||||
R.id.main_drawer_github -> {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
|
||||
}
|
||||
R.id.main_drawer_homepage -> {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
|
||||
}
|
||||
R.id.main_drawer_email -> {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
||||
}
|
||||
}
|
||||
loadBlocks()
|
||||
}
|
||||
@@ -140,23 +149,6 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
private fun checkPermission() {
|
||||
val permissions = arrayOf(
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
if (permissions.any { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }) {
|
||||
if (permissions.any { ActivityCompat.shouldShowRequestPermissionRationale(this, it) })
|
||||
AlertDialog.Builder(this).apply {
|
||||
setTitle(R.string.warning)
|
||||
setMessage(R.string.permission_explain)
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
else
|
||||
ActivityCompat.requestPermissions(this, permissions, permissionRequestCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUpdate() {
|
||||
|
||||
fun extractReleaseNote(update: JsonObject, locale: String) : String {
|
||||
@@ -177,7 +169,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
val result = StringBuilder()
|
||||
|
||||
for(line in markdown.split('\n')) {
|
||||
for(line in markdown.lines()) {
|
||||
if (releaseNote.matches(line)) {
|
||||
releaseNoteFlag = true
|
||||
continue
|
||||
@@ -201,57 +193,16 @@ class MainActivity : AppCompatActivity() {
|
||||
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
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val update =
|
||||
checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch
|
||||
|
||||
val (url, fileName) = getApkUrl(update, getString(R.string.release_name)) ?: return@launch
|
||||
|
||||
val dialog = AlertDialog.Builder(this@MainActivity).apply {
|
||||
setTitle(R.string.update_title)
|
||||
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(
|
||||
applicationContext,
|
||||
"xyz.quaver.pupil.provider",
|
||||
dest
|
||||
)
|
||||
|
||||
if (dest.exists())
|
||||
dest.delete()
|
||||
|
||||
val request = DownloadManager.Request(Uri.parse(url)).apply {
|
||||
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
|
||||
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(desturi, manager.getMimeTypeForDownloadedFile(id))
|
||||
}
|
||||
|
||||
startActivity(install)
|
||||
unregisterReceiver(this)
|
||||
finish()
|
||||
}
|
||||
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ ->}
|
||||
}
|
||||
@@ -264,18 +215,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
with(main_recyclerview) {
|
||||
adapter = GalleryBlockAdapter(galleries).apply {
|
||||
setClickListener { galleryID, title ->
|
||||
val intent = Intent(this@MainActivity, GalleryActivity::class.java)
|
||||
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)
|
||||
}
|
||||
}
|
||||
adapter = GalleryBlockAdapter(galleries)
|
||||
addOnScrollListener(
|
||||
object: RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
@@ -289,6 +229,17 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
)
|
||||
ItemClickSupport.addTo(this).setOnItemClickListener { _, position, _ ->
|
||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||
val gallery = galleries[position].first
|
||||
intent.putExtra("GALLERY_ID", gallery.id)
|
||||
intent.putExtra("GALLERY_TITLE", gallery.title)
|
||||
|
||||
//TODO: Maybe sprinke some transitions will be nice :D
|
||||
startActivity(intent)
|
||||
|
||||
Histories.default.add(gallery.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,9 +353,11 @@ class MainActivity : AppCompatActivity() {
|
||||
if (query != this@MainActivity.query) {
|
||||
this@MainActivity.query = query
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query)
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -481,29 +434,43 @@ class MainActivity : AppCompatActivity() {
|
||||
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
|
||||
}.chunked(4).let { chunks ->
|
||||
for (chunk in chunks)
|
||||
chunk.map {
|
||||
async {
|
||||
try {
|
||||
val galleryBlock = getGalleryBlock(it)
|
||||
|
||||
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
|
||||
thumbnail = BitmapFactory.decodeStream(inputStream)
|
||||
val thumbnail = async {
|
||||
val cache = File(cacheDir, "imageCache/$it/thumbnail.${galleryBlock.thumbnails[0].path.split('.').last()}")
|
||||
|
||||
if (!cache.exists())
|
||||
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
inputStream.copyTo(FileOutputStream(cache))
|
||||
}
|
||||
|
||||
cache.absolutePath
|
||||
}
|
||||
|
||||
Pair(galleryBlock, thumbnail)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.forEach {
|
||||
val galleryBlock = it.await() ?: return@forEach
|
||||
|
||||
Pair(galleryBlock, thumbnail)
|
||||
}
|
||||
}.forEach {
|
||||
val galleryBlock = it.await()
|
||||
withContext(Dispatchers.Main) {
|
||||
main_progressbar.hide()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
main_progressbar.hide()
|
||||
|
||||
galleries.add(galleryBlock)
|
||||
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
|
||||
galleries.add(galleryBlock)
|
||||
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
254
app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt
Normal file
@@ -0,0 +1,254 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_reader.*
|
||||
import kotlinx.android.synthetic.main.activity_reader.view.*
|
||||
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.getReader
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
private val images = ArrayList<String>()
|
||||
private var galleryID = 0
|
||||
private var gallerySize: Int = 0
|
||||
private var currentPage: Int = 0
|
||||
private lateinit var reader: Deferred<Reader>
|
||||
private var loadJob: Job? = null
|
||||
|
||||
private lateinit var snapHelper: PagerSnapHelper
|
||||
|
||||
private var menu: Menu? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE)
|
||||
|
||||
setContentView(R.layout.activity_reader)
|
||||
|
||||
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@ReaderActivity)
|
||||
if (preference.getBoolean("use_hiyobi", false)) {
|
||||
try {
|
||||
xyz.quaver.hiyobi.getReader(galleryID)
|
||||
} catch (e: Exception) {
|
||||
getReader(galleryID)
|
||||
}
|
||||
}
|
||||
getReader(galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
snapHelper = PagerSnapHelper()
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
val attrs = window.attributes
|
||||
|
||||
if (preferences.getBoolean("reader_fullscreen", false)) {
|
||||
attrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
supportActionBar?.hide()
|
||||
} else {
|
||||
attrs.flags = attrs.flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
|
||||
supportActionBar?.show()
|
||||
}
|
||||
|
||||
window.attributes = attrs
|
||||
|
||||
if (preferences.getBoolean("reader_one_by_one", false)) {
|
||||
snapHelper.attachToRecyclerView(reader_recyclerview)
|
||||
reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
||||
} else {
|
||||
snapHelper.attachToRecyclerView(null)
|
||||
reader_recyclerview.layoutManager = LinearLayoutManager(this)
|
||||
}
|
||||
|
||||
initView()
|
||||
loadImages()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.reader, menu)
|
||||
this.menu = menu
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
when(item?.itemId) {
|
||||
R.id.reader_menu_page_indicator -> {
|
||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
|
||||
with(view.reader_dialog_number_picker) {
|
||||
minValue=1
|
||||
maxValue=gallerySize
|
||||
value=currentPage
|
||||
}
|
||||
val dialog = AlertDialog.Builder(this).apply {
|
||||
setView(view)
|
||||
}.create()
|
||||
view.reader_dialog_ok.setOnClickListener {
|
||||
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(view.reader_dialog_number_picker.value-1, 0)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
loadJob?.cancel()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
with(reader_recyclerview) {
|
||||
adapter = ReaderAdapter(images)
|
||||
|
||||
addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
|
||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||
|
||||
if (layoutManager.findFirstVisibleItemPosition() == -1)
|
||||
return
|
||||
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/$gallerySize"
|
||||
}
|
||||
})
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
ItemClickSupport.addTo(this)
|
||||
.setOnItemClickListener { _, _, _ ->
|
||||
val attrs = window.attributes
|
||||
val fullscreen = preferences.getBoolean("reader_fullscreen", false)
|
||||
|
||||
if (fullscreen) {
|
||||
attrs.flags = attrs.flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
|
||||
supportActionBar?.show()
|
||||
} else {
|
||||
attrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
supportActionBar?.hide()
|
||||
}
|
||||
|
||||
window.attributes = attrs
|
||||
|
||||
preferences.edit().putBoolean("reader_fullscreen", !fullscreen).apply()
|
||||
}.setOnItemLongClickListener { _, _, _ ->
|
||||
val oneByOne = preferences.getBoolean("reader_one_by_one", false)
|
||||
if (oneByOne) {
|
||||
snapHelper.attachToRecyclerView(null)
|
||||
reader_recyclerview.layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
else {
|
||||
snapHelper.attachToRecyclerView(reader_recyclerview)
|
||||
reader_recyclerview.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||
}
|
||||
|
||||
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
||||
|
||||
preferences.edit().putBoolean("reader_one_by_one", !oneByOne).apply()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadImages() {
|
||||
fun webpUrlFromUrl(url: URL) = URL(url.toString().replace("/galleries/", "/webp/") + ".webp")
|
||||
|
||||
loadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||
val reader = reader.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
with(reader_progressbar) {
|
||||
max = reader.size
|
||||
progress = 0
|
||||
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
gallerySize = reader.size
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/$gallerySize"
|
||||
}
|
||||
|
||||
reader.chunked(8).forEach { chunked ->
|
||||
chunked.map {
|
||||
async(Dispatchers.IO) {
|
||||
val url = if (it.second?.haswebp == 1) webpUrlFromUrl(it.first) else it.first
|
||||
|
||||
val fileName: String
|
||||
|
||||
with(url.path) {
|
||||
fileName = substring(lastIndexOf('/')+1)
|
||||
}
|
||||
|
||||
val cache = File(cacheDir, "/imageCache/$galleryID/$fileName")
|
||||
|
||||
if (!cache.exists())
|
||||
with(url.openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("Referer", getReferer(galleryID))
|
||||
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
inputStream.copyTo(FileOutputStream(cache))
|
||||
}
|
||||
|
||||
cache.absolutePath
|
||||
}
|
||||
}.forEach {
|
||||
val cache = it.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
images.add(cache)
|
||||
reader_recyclerview.adapter?.notifyItemInserted(images.size - 1)
|
||||
reader_progressbar.progress++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
reader_progressbar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,22 @@ package xyz.quaver.pupil
|
||||
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import kotlinx.android.synthetic.main.dialog_default_query.*
|
||||
import kotlinx.android.synthetic.main.dialog_default_query.view.*
|
||||
import xyz.quaver.pupil.types.Tags
|
||||
import xyz.quaver.pupil.util.Histories
|
||||
import java.io.File
|
||||
|
||||
@@ -109,6 +119,120 @@ class SettingsActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
with(findPreference<Preference>("default_query")) {
|
||||
this ?: return@with
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
summary = preferences.getString("default_query", "") ?: ""
|
||||
|
||||
val languages = resources.getStringArray(R.array.languages).map {
|
||||
it.split("|").let { split ->
|
||||
Pair(split[0], split[1])
|
||||
}
|
||||
}.toMap()
|
||||
val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
|
||||
|
||||
val excludeBL = "-male:yaoi"
|
||||
val excludeGuro = listOf("-female:guro", "-male:guro")
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
val dialogView = LayoutInflater.from(context).inflate(
|
||||
R.layout.dialog_default_query,
|
||||
null
|
||||
)
|
||||
|
||||
val tags = Tags.parse(
|
||||
preferences.getString("default_query", "") ?: ""
|
||||
)
|
||||
|
||||
summary = tags.toString()
|
||||
|
||||
with(dialogView.default_query_dialog_language_selector) {
|
||||
adapter =
|
||||
ArrayAdapter<String>(
|
||||
context,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
arrayListOf(
|
||||
getString(R.string.default_query_dialog_language_selector_none)
|
||||
).apply {
|
||||
addAll(languages.values)
|
||||
}
|
||||
)
|
||||
if (tags.any { it.area == "language" }) {
|
||||
val tag = languages[tags.first { it.area == "language" }.tag]
|
||||
if (tag != null) {
|
||||
setSelection(
|
||||
(adapter as ArrayAdapter<String>).getPosition(tag)
|
||||
)
|
||||
tags.removeByArea("language")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_BL_checkbox) {
|
||||
isChecked = tags.contains(excludeBL)
|
||||
if (tags.contains(excludeBL))
|
||||
tags.remove(excludeBL)
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_guro_checkbox) {
|
||||
isChecked = excludeGuro.all { tags.contains(it) }
|
||||
if (excludeGuro.all { tags.contains(it) })
|
||||
excludeGuro.forEach {
|
||||
tags.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
with(dialogView.default_query_dialog_edittext) {
|
||||
setText(tags.toString(), TextView.BufferType.EDITABLE)
|
||||
addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
s ?: return
|
||||
|
||||
if (s.any { it.isUpperCase() })
|
||||
s.replace(0, s.length, s.toString().toLowerCase())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val dialog = AlertDialog.Builder(context!!).apply {
|
||||
setView(dialogView)
|
||||
}.create()
|
||||
|
||||
dialogView.default_query_dialog_ok.setOnClickListener {
|
||||
val newTags = Tags.parse(dialogView.default_query_dialog_edittext.text.toString())
|
||||
|
||||
with(dialogView.default_query_dialog_language_selector) {
|
||||
if (selectedItemPosition != 0)
|
||||
newTags.add("language:${reverseLanguages[selectedItem]}")
|
||||
}
|
||||
|
||||
if (dialogView.default_query_dialog_BL_checkbox.isChecked)
|
||||
newTags.add(excludeBL)
|
||||
|
||||
if (dialogView.default_query_dialog_guro_checkbox.isChecked)
|
||||
excludeGuro.forEach { tag ->
|
||||
newTags.add(tag)
|
||||
}
|
||||
|
||||
preferenceManager.sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
||||
summary = preferences.getString("default_query", "") ?: ""
|
||||
tags.clear()
|
||||
tags.addAll(newTags)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.chip.Chip
|
||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.toTag
|
||||
import xyz.quaver.pupil.R
|
||||
import java.io.File
|
||||
|
||||
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private enum class ViewType {
|
||||
VIEW_ITEM,
|
||||
@@ -33,11 +36,6 @@ 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, String) -> Unit)? = null
|
||||
fun setClickListener(callback: ((Int, String) -> Unit)?) {
|
||||
this.callback = callback
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
when(viewType) {
|
||||
ViewType.VIEW_ITEM.ordinal -> {
|
||||
@@ -70,21 +68,43 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?
|
||||
}.toMap()
|
||||
val (gallery, thumbnail) = galleries[position]
|
||||
|
||||
val artists = gallery.artists.ifEmpty { listOf("N/A") }
|
||||
val series = gallery.series.ifEmpty { listOf("N/A") }
|
||||
val artists = gallery.artists
|
||||
val series = gallery.series
|
||||
|
||||
setOnClickListener {
|
||||
callback?.invoke(gallery.id, gallery.title)
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
galleryblock_thumbnail.setImageBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
galleryblock_thumbnail.setImageBitmap(thumbnail)
|
||||
galleryblock_title.text = gallery.title
|
||||
galleryblock_artist.text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||
galleryblock_series.text =
|
||||
resources.getString(R.string.galleryblock_series, series.joinToString(", ") { it.wordCapitalize() })
|
||||
with(galleryblock_artist) {
|
||||
text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||
visibility = when {
|
||||
artists.isNotEmpty() -> View.VISIBLE
|
||||
else -> View.GONE
|
||||
}
|
||||
}
|
||||
with(galleryblock_series) {
|
||||
text =
|
||||
resources.getString(
|
||||
R.string.galleryblock_series,
|
||||
series.joinToString(", ") { it.wordCapitalize() })
|
||||
visibility = when {
|
||||
series.isNotEmpty() -> View.VISIBLE
|
||||
else -> View.GONE
|
||||
}
|
||||
}
|
||||
galleryblock_type.text = resources.getString(R.string.galleryblock_type, gallery.type).wordCapitalize()
|
||||
galleryblock_language.text =
|
||||
resources.getString(R.string.galleryblock_language, languages[gallery.language])
|
||||
with(galleryblock_language) {
|
||||
text =
|
||||
resources.getString(R.string.galleryblock_language, languages[gallery.language])
|
||||
visibility = when {
|
||||
gallery.language.isNotEmpty() -> View.VISIBLE
|
||||
else -> View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
galleryblock_tag_group.removeAllViews()
|
||||
gallery.relatedTags.forEach {
|
||||
|
||||
@@ -1,39 +1,31 @@
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.item_reader.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import xyz.quaver.pupil.R
|
||||
|
||||
class GalleryAdapter(private val images: List<String>) : RecyclerView.Adapter<GalleryAdapter.ViewHolder>() {
|
||||
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
|
||||
|
||||
private var onClick: (() -> Unit)? = null
|
||||
fun setOnClick(callback: (() -> Unit)?) {
|
||||
this.onClick = callback
|
||||
}
|
||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.item_gallery, parent, false
|
||||
R.layout.item_reader, parent, false
|
||||
).let {
|
||||
return ViewHolder(it as ImageView)
|
||||
return ViewHolder(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
with(holder.view) {
|
||||
setOnClickListener {
|
||||
onClick?.invoke()
|
||||
}
|
||||
|
||||
with(holder.view as ImageView) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val options = BitmapFactory.Options()
|
||||
|
||||
96
app/src/main/java/xyz/quaver/pupil/types/Tags.kt
Normal file
@@ -0,0 +1,96 @@
|
||||
package xyz.quaver.pupil.types
|
||||
|
||||
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
|
||||
companion object {
|
||||
fun parseTag(tag: String) : Tag {
|
||||
if (tag.first() == '-') {
|
||||
tag.substring(1).split(Regex(":"), 2).let {
|
||||
return when(it.size) {
|
||||
2 -> Tag(it[0], it[1], true)
|
||||
else -> Tag(null, tag, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
tag.split(Regex(":"), 2).let {
|
||||
return when(it.size) {
|
||||
2 -> Tag(it[0], it[1])
|
||||
else -> Tag(null, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return (if (isNegative) "-" else "") + when(area) {
|
||||
null -> tag
|
||||
else -> "$area:$tag"
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Tag)
|
||||
return false
|
||||
|
||||
if (other.area == area && other.tag == tag)
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return super.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
class Tags(tag: List<Tag?>?) : ArrayList<Tag>() {
|
||||
|
||||
companion object {
|
||||
fun parse(tags: String) : Tags {
|
||||
return Tags(
|
||||
tags.split(' ').map {
|
||||
if (it.isNotEmpty())
|
||||
Tag.parseTag(it)
|
||||
else
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
tag?.forEach {
|
||||
if (it != null)
|
||||
add(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun contains(element: String): Boolean {
|
||||
forEach {
|
||||
if (it.toString() == element)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun add(element: String): Boolean {
|
||||
return super.add(Tag.parseTag(element))
|
||||
}
|
||||
|
||||
fun remove(element: String) {
|
||||
filter { it.toString() == element }.forEach {
|
||||
remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeByArea(area: String) {
|
||||
filter { it.area == area }.forEach {
|
||||
remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return joinToString(" ") { it.toString() }
|
||||
}
|
||||
|
||||
}
|
||||
107
app/src/main/java/xyz/quaver/pupil/util/ItemClickSupport.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package xyz.quaver.pupil.util;
|
||||
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import xyz.quaver.pupil.R;
|
||||
|
||||
/*
|
||||
Source: http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/
|
||||
USAGE:
|
||||
|
||||
ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
|
||||
// do it
|
||||
}
|
||||
});
|
||||
|
||||
*/
|
||||
public class ItemClickSupport {
|
||||
private final RecyclerView mRecyclerView;
|
||||
private OnItemClickListener mOnItemClickListener;
|
||||
private OnItemLongClickListener mOnItemLongClickListener;
|
||||
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mOnItemClickListener != null) {
|
||||
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
|
||||
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
|
||||
}
|
||||
}
|
||||
};
|
||||
private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (mOnItemLongClickListener != null) {
|
||||
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
|
||||
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
private RecyclerView.OnChildAttachStateChangeListener mAttachListener
|
||||
= new RecyclerView.OnChildAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onChildViewAttachedToWindow(@NonNull View view) {
|
||||
if (mOnItemClickListener != null) {
|
||||
view.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
if (mOnItemLongClickListener != null) {
|
||||
view.setOnLongClickListener(mOnLongClickListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildViewDetachedFromWindow(@NonNull View view) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private ItemClickSupport(RecyclerView recyclerView) {
|
||||
mRecyclerView = recyclerView;
|
||||
mRecyclerView.setTag(R.id.item_click_support, this);
|
||||
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
|
||||
}
|
||||
|
||||
public static ItemClickSupport addTo(RecyclerView view) {
|
||||
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
|
||||
if (support == null) {
|
||||
support = new ItemClickSupport(view);
|
||||
}
|
||||
return support;
|
||||
}
|
||||
|
||||
public static ItemClickSupport removeFrom(RecyclerView view) {
|
||||
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
|
||||
if (support != null) {
|
||||
support.detach(view);
|
||||
}
|
||||
return support;
|
||||
}
|
||||
|
||||
public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
|
||||
mOnItemClickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
|
||||
mOnItemLongClickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void detach(RecyclerView view) {
|
||||
view.removeOnChildAttachStateChangeListener(mAttachListener);
|
||||
view.setTag(R.id.item_click_support, null);
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
|
||||
void onItemClicked(RecyclerView recyclerView, int position, View v);
|
||||
}
|
||||
|
||||
public interface OnItemLongClickListener {
|
||||
|
||||
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,5 @@ fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
|
||||
if (currentVersion != releases[0].jsonObject["tag_name"]?.content)
|
||||
return releases[0].jsonObject
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun getApkUrl(releases: JsonObject, releaseName: String) : Pair<String?, String?>? {
|
||||
releases["assets"]?.jsonArray?.forEach {
|
||||
if (Regex(releaseName).matches(it.jsonObject["name"]?.content ?: ""))
|
||||
return Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
10
app/src/main/res/drawable-anydpi/ic_email.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="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable-anydpi/ic_help.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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_email.png
Normal file
|
After Width: | Height: | Size: 325 B |
BIN
app/src/main/res/drawable-hdpi/ic_help.png
Normal file
|
After Width: | Height: | Size: 571 B |
BIN
app/src/main/res/drawable-mdpi/ic_email.png
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
app/src/main/res/drawable-mdpi/ic_help.png
Normal file
|
After Width: | Height: | Size: 345 B |
BIN
app/src/main/res/drawable-xhdpi/ic_email.png
Normal file
|
After Width: | Height: | Size: 383 B |
BIN
app/src/main/res/drawable-xhdpi/ic_help.png
Normal file
|
After Width: | Height: | Size: 676 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_email.png
Normal file
|
After Width: | Height: | Size: 550 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_help.png
Normal file
|
After Width: | Height: | Size: 1000 B |
@@ -6,7 +6,6 @@
|
||||
android:id="@+id/main_drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<include layout="@layout/activity_main_content"
|
||||
@@ -18,7 +17,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:headerLayout="@layout/nav_header_main"
|
||||
app:menu="@menu/activity_main_drawer"/>
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:layout_height="80dp"
|
||||
android:visibility="invisible"
|
||||
android:background="@color/transparent"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
@@ -50,13 +50,13 @@
|
||||
android:id="@+id/main_swipe_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="-64dp">
|
||||
android:layout_marginBottom="-80dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/main_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="64dp"
|
||||
android:paddingTop="80dp"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||
@@ -71,7 +71,7 @@
|
||||
android:layout_height="match_parent"
|
||||
app:floatingSearch_searchBarMarginLeft="8dp"
|
||||
app:floatingSearch_searchBarMarginRight="8dp"
|
||||
app:floatingSearch_searchBarMarginTop="8dp"
|
||||
app:floatingSearch_searchBarMarginTop="24dp"
|
||||
app:floatingSearch_searchHint="@string/search_hint"
|
||||
app:floatingSearch_suggestionsListAnimDuration="250"
|
||||
app:floatingSearch_showSearchKey="true"
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".GalleryActivity">
|
||||
tools:context=".ReaderActivity">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/reader_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/gallery_framelayout"
|
||||
android:id="@+id/reader_framelayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/gallery_progressbar"
|
||||
android:id="@+id/reader_progressbar"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
@@ -20,11 +27,4 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gallery_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/gallery_framelayout"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
111
app/src/main/res/layout/dialog_default_query.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/default_query_dialog_title"
|
||||
style="@style/TextAppearance.AppCompat.Large"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/default_query_dialog_title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<EditText
|
||||
tools:ignore="Autofill"
|
||||
android:inputType="text"
|
||||
android:hint="@string/settings_default_query"
|
||||
android:id="@+id/default_query_dialog_edittext"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/default_query_dialog_language_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_edittext"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/default_query_dialog_language"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/default_query_dialog_language_selector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/default_query_dialog_BL_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingRight="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_language_layout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/default_query_dialog_filter_BL"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/default_query_dialog_BL_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/default_query_dialog_guro_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingRight="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_BL_layout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/default_query_dialog_filter_guro"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/default_query_dialog_guro_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/default_query_dialog_ok"
|
||||
style="?borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_guro_layout"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:text="@android:string/ok"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
35
app/src/main/res/layout/dialog_numberpicker.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
style="?android:textAppearanceLarge"
|
||||
android:id="@+id/reader_dialog_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reader_go_to_page"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<NumberPicker
|
||||
android:id="@+id/reader_dialog_number_picker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/reader_dialog_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/reader_dialog_ok"
|
||||
style="?borderlessButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@android:string/ok"
|
||||
app:layout_constraintTop_toBottomOf="@id/reader_dialog_number_picker"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -78,8 +78,17 @@
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
||||
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
|
||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
|
||||
|
||||
<View
|
||||
android:id="@+id/galleryblock_padding"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
||||
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/galleryblock_tag_group"
|
||||
android:layout_width="0dp"
|
||||
@@ -90,7 +99,7 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -4,6 +4,5 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true"/>
|
||||
@@ -12,4 +12,21 @@
|
||||
android:icon="@drawable/ic_history"/>
|
||||
</group>
|
||||
|
||||
<item android:title="@string/main_drawer_group_contact_title">
|
||||
<menu>
|
||||
<item android:id="@+id/main_drawer_help"
|
||||
android:title="@string/main_drawer_group_contact_help"
|
||||
android:icon="@drawable/ic_help"/>
|
||||
<item android:id="@+id/main_drawer_github"
|
||||
android:title="@string/main_drawer_group_contact_github"
|
||||
android:icon="@drawable/github_circle"/>
|
||||
<item android:id="@+id/main_drawer_homepage"
|
||||
android:title="@string/main_drawer_group_contact_homepage"
|
||||
android:icon="@drawable/ic_home"/>
|
||||
<item android:id="@+id/main_drawer_email"
|
||||
android:title="@string/main_drawer_group_contact_email"
|
||||
android:icon="@drawable/ic_email"/>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
</menu>
|
||||
9
app/src/main/res/menu/reader.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/reader_menu_page_indicator"
|
||||
android:title="@string/reader_page_indicator_placeholder"
|
||||
app:showAsAction="always|withText"/>
|
||||
|
||||
</menu>
|
||||
@@ -10,7 +10,7 @@
|
||||
<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>
|
||||
<string name="settings_default_query">デフォルトキーワード</string>
|
||||
<string name="permission_explain">権限を拒否すると一部の機能が利用できません</string>
|
||||
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
|
||||
<string name="settings_search_title">検索設定</string>
|
||||
@@ -30,4 +30,17 @@
|
||||
<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>
|
||||
<string name="reader_go_to_page">移動</string>
|
||||
<string name="default_query_dialog_language_selector_none">非選択</string>
|
||||
<string name="default_query_dialog_filter_BL">BLフィルター</string>
|
||||
<string name="default_query_dialog_filter_guro">グロフィルター</string>
|
||||
<string name="default_query_dialog_language">"言語: "</string>
|
||||
<string name="default_query_dialog_title">デフォルトキーワード設定</string>
|
||||
<string name="main_drawer_group_contact_title">お問い合わせ先</string>
|
||||
<string name="main_drawer_group_contact_homepage">ホームページ</string>
|
||||
<string name="main_drawer_group_contact_help">ヘルプ</string>
|
||||
<string name="main_drawer_group_contact_github">Github</string>
|
||||
<string name="main_drawer_group_contact_email">メールを送る</string>
|
||||
<string name="help_dialog_title">準備中</string>
|
||||
<string name="help_dialog_message">準備中です。</string>
|
||||
</resources>
|
||||
@@ -30,4 +30,17 @@
|
||||
<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>
|
||||
<string name="reader_go_to_page">이동</string>
|
||||
<string name="default_query_dialog_language_selector_none">미선택</string>
|
||||
<string name="default_query_dialog_filter_BL">BL물 필터</string>
|
||||
<string name="default_query_dialog_filter_guro">고어물 필터</string>
|
||||
<string name="default_query_dialog_language">"언어: "</string>
|
||||
<string name="default_query_dialog_title">기본 검색어 설정</string>
|
||||
<string name="main_drawer_group_contact_email">메일 보내기!</string>
|
||||
<string name="main_drawer_group_contact_github">Github</string>
|
||||
<string name="main_drawer_group_contact_help">도움말</string>
|
||||
<string name="main_drawer_group_contact_homepage">홈페이지</string>
|
||||
<string name="main_drawer_group_contact_title">문의</string>
|
||||
<string name="help_dialog_title">준비 중</string>
|
||||
<string name="help_dialog_message">준비중입니다.\n만화 화면에서 사진을 길게 누르면 스크롤 방식이 바뀝니다. 알고 계셨나요? :)</string>
|
||||
</resources>
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:statusBarColor">@color/transparent</item>
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="appbar_padding">64dp</dimen>
|
||||
<dimen name="progress_view_offset">80dp</dimen>
|
||||
<dimen name="progress_view_start">32dp</dimen>
|
||||
<dimen name="progress_view_offset">96dp</dimen>
|
||||
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
4
app/src/main/res/values/ids.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="item_click_support" type="id" />
|
||||
</resources>
|
||||
@@ -4,10 +4,17 @@
|
||||
<string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil-issue/releases</string>
|
||||
<string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
|
||||
|
||||
<string name="home_page" translatable="false">https://tom5079.github.io/Pupil</string>
|
||||
<string name="github" translatable="false">https://github.com/tom5079/Pupil-issue/issues/new/choose</string>
|
||||
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
|
||||
|
||||
<string name="main_settings" translatable="false">Settings</string>
|
||||
<string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string>
|
||||
|
||||
<string name="reader_imageview_description" translatable="false">Content ImageView</string>
|
||||
<string name="reader_page_indicator_placeholder" translatable="false">-/-</string>
|
||||
|
||||
<string name="plus_to_close" translatable="false">Fab</string>
|
||||
|
||||
<!-- Translate needed down here -->
|
||||
|
||||
@@ -20,6 +27,14 @@
|
||||
|
||||
<string name="main_drawer_home">Home</string>
|
||||
<string name="main_drawer_history">History</string>
|
||||
<string name="main_drawer_group_contact_title">Contact</string>
|
||||
<string name="main_drawer_group_contact_help">Help</string>
|
||||
<string name="main_drawer_group_contact_homepage">Visit homepage</string>
|
||||
<string name="main_drawer_group_contact_github">Visit github</string>
|
||||
<string name="main_drawer_group_contact_email">Email me!</string>
|
||||
|
||||
<string name="help_dialog_title">WIP</string>
|
||||
<string name="help_dialog_message">While in progress!\nOne thing might you don\'t know:\nLong tap an image in reader will change scrolling mode! Go try it :)</string>
|
||||
|
||||
<string name="update_title">Update available</string>
|
||||
<string name="update_download_started">Download started</string>
|
||||
@@ -32,6 +47,8 @@
|
||||
<string name="galleryblock_type">Type: %1$s</string>
|
||||
<string name="galleryblock_language">Language: %1$s</string>
|
||||
|
||||
<string name="reader_go_to_page">Go to page</string>
|
||||
|
||||
<string name="settings_title">Settings</string>
|
||||
<string name="settings_search_title">Search Settings</string>
|
||||
<string name="settings_galleries_per_page">Galleries per page</string>
|
||||
@@ -49,4 +66,10 @@
|
||||
<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>
|
||||
|
||||
<string name="default_query_dialog_title">Set default query</string>
|
||||
<string name="default_query_dialog_language">Language: </string>
|
||||
<string name="default_query_dialog_filter_BL">Filter BL</string>
|
||||
<string name="default_query_dialog_filter_guro">Filter Guro</string>
|
||||
<string name="default_query_dialog_language_selector_none">Any</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
app:defaultValue="25"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
|
||||
<EditTextPreference
|
||||
<Preference
|
||||
app:key="default_query"
|
||||
app:title="@string/settings_default_query"
|
||||
app:defaultValue=""
|
||||
|
||||