Merge pull request #2 from tom5079/development

Pupil v1.3
This commit is contained in:
tom5079
2019-05-14 19:11:12 +09:00
committed by GitHub
38 changed files with 994 additions and 353 deletions

View File

@@ -9,8 +9,8 @@ android {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionCode 3 versionCode 4
versionName "1.2" versionName "1.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@@ -40,6 +40,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "ru.noties.markwon:core:${markwonVersion}" implementation "ru.noties.markwon:core:${markwonVersion}"
implementation 'com.shawnlin:number-picker:2.4.8'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.1.1'

View File

@@ -4,16 +4,10 @@ import android.graphics.BitmapFactory
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry 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.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import xyz.quaver.hiyobi.getReader
import java.io.File import java.io.File
import java.util.*
/** /**
* Instrumented test, which will execute on an Android device. * Instrumented test, which will execute on an Android device.

View File

@@ -3,8 +3,6 @@
package="xyz.quaver.pupil"> package="xyz.quaver.pupil">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -25,7 +23,7 @@
android:resource="@xml/filepaths"/> android:resource="@xml/filepaths"/>
</provider> </provider>
<activity android:name=".GalleryActivity" <activity android:name=".ReaderActivity"
android:configChanges="keyboardHidden|orientation|screenSize"/> android:configChanges="keyboardHidden|orientation|screenSize"/>
<activity <activity
android:name=".SettingsActivity" android:name=".SettingsActivity"

View File

@@ -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
}
}
}
}

View File

@@ -1,31 +1,15 @@
package xyz.quaver.pupil 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.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
import android.util.Log
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity 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.content.res.ResourcesCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@@ -44,19 +28,18 @@ import xyz.quaver.hitomi.*
import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.util.Histories import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.SetLineOverlap import xyz.quaver.pupil.util.SetLineOverlap
import xyz.quaver.pupil.util.checkUpdate import xyz.quaver.pupil.util.checkUpdate
import xyz.quaver.pupil.util.getApkUrl
import java.io.File import java.io.File
import java.lang.StringBuilder import java.io.FileOutputStream
import java.util.* import java.util.*
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val permissionRequestCode = 4585 private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>()
private var query = "" private var query = ""
@@ -67,9 +50,12 @@ class MainActivity : AppCompatActivity() {
Histories.default = Histories(File(cacheDir, "histories.json")) Histories.default = Histories(File(cacheDir, "histories.json"))
super.onCreate(savedInstanceState) 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() checkUpdate()
@@ -81,7 +67,11 @@ class MainActivity : AppCompatActivity() {
) )
with(main_swipe_layout) { 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 { setOnRefreshListener {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@@ -97,17 +87,36 @@ class MainActivity : AppCompatActivity() {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
main_drawer_layout.closeDrawers() main_drawer_layout.closeDrawers()
cancelFetch()
clearGalleries()
when(it.itemId) { when(it.itemId) {
R.id.main_drawer_home -> { R.id.main_drawer_home -> {
cancelFetch()
clearGalleries()
query = query.replace("HISTORY", "") query = query.replace("HISTORY", "")
fetchGalleries(query) fetchGalleries(query)
} }
R.id.main_drawer_history -> { R.id.main_drawer_history -> {
cancelFetch()
clearGalleries()
query += "HISTORY" query += "HISTORY"
fetchGalleries(query) 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() loadBlocks()
} }
@@ -140,23 +149,6 @@ class MainActivity : AppCompatActivity() {
super.onResume() 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() { private fun checkUpdate() {
fun extractReleaseNote(update: JsonObject, locale: String) : String { fun extractReleaseNote(update: JsonObject, locale: String) : String {
@@ -177,7 +169,7 @@ class MainActivity : AppCompatActivity() {
val result = StringBuilder() val result = StringBuilder()
for(line in markdown.split('\n')) { for(line in markdown.lines()) {
if (releaseNote.matches(line)) { if (releaseNote.matches(line)) {
releaseNoteFlag = true releaseNoteFlag = true
continue continue
@@ -201,57 +193,16 @@ class MainActivity : AppCompatActivity() {
return getString(R.string.update_release_note, update["tag_name"]?.content, result.toString()) 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 { CoroutineScope(Dispatchers.Default).launch {
val update = val update =
checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch
val (url, fileName) = getApkUrl(update, getString(R.string.release_name)) ?: return@launch
val dialog = AlertDialog.Builder(this@MainActivity).apply { val dialog = AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.update_title) setTitle(R.string.update_title)
val msg = extractReleaseNote(update, Locale.getDefault().language) val msg = extractReleaseNote(update, Locale.getDefault().language)
setMessage(Markwon.create(context).toMarkdown(msg)) setMessage(Markwon.create(context).toMarkdown(msg))
setPositiveButton(android.R.string.yes) { _, _ -> setPositiveButton(android.R.string.yes) { _, _ ->
Toast.makeText( startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
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))
} }
setNegativeButton(android.R.string.no) { _, _ ->} setNegativeButton(android.R.string.no) { _, _ ->}
} }
@@ -264,18 +215,7 @@ class MainActivity : AppCompatActivity() {
private fun setupRecyclerView() { private fun setupRecyclerView() {
with(main_recyclerview) { with(main_recyclerview) {
adapter = GalleryBlockAdapter(galleries).apply { adapter = GalleryBlockAdapter(galleries)
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)
}
}
addOnScrollListener( addOnScrollListener(
object: RecyclerView.OnScrollListener() { object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 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) { if (query != this@MainActivity.query) {
this@MainActivity.query = query this@MainActivity.query = query
cancelFetch() CoroutineScope(Dispatchers.Main).launch {
clearGalleries() cancelFetch()
fetchGalleries(query) clearGalleries()
fetchGalleries(query)
}
} }
} }
}) })
@@ -481,29 +434,43 @@ class MainActivity : AppCompatActivity() {
galleryIDs galleryIDs
else -> else ->
galleryIDs.slice(galleries.size until Math.min(galleries.size+perPage, galleryIDs.size)) galleryIDs.slice(galleries.size until Math.min(galleries.size+perPage, galleryIDs.size))
}.chunked(4).forEach { chunked -> }.chunked(4).let { chunks ->
chunked.map { for (chunk in chunks)
async { chunk.map {
val galleryBlock = getGalleryBlock(it) async {
val thumbnail: Bitmap try {
val galleryBlock = getGalleryBlock(it)
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) { val thumbnail = async {
thumbnail = BitmapFactory.decodeStream(inputStream) 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) withContext(Dispatchers.Main) {
} main_progressbar.hide()
}.forEach {
val galleryBlock = it.await()
withContext(Dispatchers.Main) { galleries.add(galleryBlock)
main_progressbar.hide() main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
}
galleries.add(galleryBlock)
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
} }
} }
}
} }
} }
} }

View 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
}
}
}
}

View File

@@ -2,12 +2,22 @@ package xyz.quaver.pupil
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.view.WindowManager 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.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat 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 xyz.quaver.pupil.util.Histories
import java.io.File import java.io.File
@@ -109,6 +119,120 @@ class SettingsActivity : AppCompatActivity() {
true 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
}
}
} }
} }

View File

@@ -1,18 +1,21 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.graphics.Bitmap import android.graphics.BitmapFactory
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.item_galleryblock.view.* import kotlinx.android.synthetic.main.item_galleryblock.view.*
import kotlinx.coroutines.*
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.toTag import xyz.quaver.hitomi.toTag
import xyz.quaver.pupil.R 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 { private enum class ViewType {
VIEW_ITEM, VIEW_ITEM,
@@ -33,11 +36,6 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?
class ViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) class ViewHolder(val view: CardView) : RecyclerView.ViewHolder(view)
class ProgressViewHolder(view: LinearLayout) : 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 { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when(viewType) { when(viewType) {
ViewType.VIEW_ITEM.ordinal -> { ViewType.VIEW_ITEM.ordinal -> {
@@ -70,21 +68,43 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?
}.toMap() }.toMap()
val (gallery, thumbnail) = galleries[position] val (gallery, thumbnail) = galleries[position]
val artists = gallery.artists.ifEmpty { listOf("N/A") } val artists = gallery.artists
val series = gallery.series.ifEmpty { listOf("N/A") } val series = gallery.series
setOnClickListener { CoroutineScope(Dispatchers.Default).launch {
callback?.invoke(gallery.id, gallery.title) val bitmap = BitmapFactory.decodeFile(thumbnail.await())
CoroutineScope(Dispatchers.Main).launch {
galleryblock_thumbnail.setImageBitmap(bitmap)
}
} }
galleryblock_thumbnail.setImageBitmap(thumbnail)
galleryblock_title.text = gallery.title galleryblock_title.text = gallery.title
galleryblock_artist.text = artists.joinToString(", ") { it.wordCapitalize() } with(galleryblock_artist) {
galleryblock_series.text = text = artists.joinToString(", ") { it.wordCapitalize() }
resources.getString(R.string.galleryblock_series, series.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_type.text = resources.getString(R.string.galleryblock_type, gallery.type).wordCapitalize()
galleryblock_language.text = with(galleryblock_language) {
resources.getString(R.string.galleryblock_language, languages[gallery.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() galleryblock_tag_group.removeAllViews()
gallery.relatedTags.forEach { gallery.relatedTags.forEach {

View File

@@ -1,39 +1,31 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_reader.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import xyz.quaver.pupil.R 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) class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
private var onClick: (() -> Unit)? = null
fun setOnClick(callback: (() -> Unit)?) {
this.onClick = callback
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
LayoutInflater.from(parent.context).inflate( LayoutInflater.from(parent.context).inflate(
R.layout.item_gallery, parent, false R.layout.item_reader, parent, false
).let { ).let {
return ViewHolder(it as ImageView) return ViewHolder(it)
} }
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder.view) { with(holder.view as ImageView) {
setOnClickListener {
onClick?.invoke()
}
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
val options = BitmapFactory.Options() val options = BitmapFactory.Options()

View 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() }
}
}

View 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);
}
}

View File

@@ -18,14 +18,5 @@ fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
if (currentVersion != releases[0].jsonObject["tag_name"]?.content) if (currentVersion != releases[0].jsonObject["tag_name"]?.content)
return releases[0].jsonObject 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 return null
} }

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

View File

@@ -6,7 +6,6 @@
android:id="@+id/main_drawer_layout" android:id="@+id/main_drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start"> tools:openDrawer="start">
<include layout="@layout/activity_main_content" <include layout="@layout/activity_main_content"
@@ -18,7 +17,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main" app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer"/> app:menu="@menu/activity_main_drawer"/>

View File

@@ -22,7 +22,7 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="80dp"
android:visibility="invisible" android:visibility="invisible"
android:background="@color/transparent" android:background="@color/transparent"
app:layout_scrollFlags="scroll|enterAlways" app:layout_scrollFlags="scroll|enterAlways"
@@ -50,13 +50,13 @@
android:id="@+id/main_swipe_layout" android:id="@+id/main_swipe_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginBottom="-64dp"> android:layout_marginBottom="-80dp">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_recyclerview" android:id="@+id/main_recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingTop="64dp" android:paddingTop="80dp"
android:clipToPadding="false" android:clipToPadding="false"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
@@ -71,7 +71,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:floatingSearch_searchBarMarginLeft="8dp" app:floatingSearch_searchBarMarginLeft="8dp"
app:floatingSearch_searchBarMarginRight="8dp" app:floatingSearch_searchBarMarginRight="8dp"
app:floatingSearch_searchBarMarginTop="8dp" app:floatingSearch_searchBarMarginTop="24dp"
app:floatingSearch_searchHint="@string/search_hint" app:floatingSearch_searchHint="@string/search_hint"
app:floatingSearch_suggestionsListAnimDuration="250" app:floatingSearch_suggestionsListAnimDuration="250"
app:floatingSearch_showSearchKey="true" app:floatingSearch_showSearchKey="true"

View File

@@ -1,18 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <FrameLayout
android:id="@+id/gallery_framelayout" android:id="@+id/reader_framelayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="4dp"> android:layout_height="wrap_content">
<ProgressBar <ProgressBar
android:id="@+id/gallery_progressbar" android:id="@+id/reader_progressbar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="4dp" android:layout_height="4dp"
@@ -20,11 +27,4 @@
</FrameLayout> </FrameLayout>
<androidx.recyclerview.widget.RecyclerView </androidx.coordinatorlayout.widget.CoordinatorLayout>
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>

View 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>

View 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>

View File

@@ -78,8 +78,17 @@
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_type" app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" /> 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 <com.google.android.material.chip.ChipGroup
android:id="@+id/galleryblock_tag_group" android:id="@+id/galleryblock_tag_group"
android:layout_width="0dp" android:layout_width="0dp"
@@ -90,7 +99,7 @@
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_language" app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,6 +4,5 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:adjustViewBounds="true" android:scaleType="fitCenter"
android:clickable="true" android:adjustViewBounds="true"/>
android:focusable="true"/>

View File

@@ -12,4 +12,21 @@
android:icon="@drawable/ic_history"/> android:icon="@drawable/ic_history"/>
</group> </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> </menu>

View 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>

View File

@@ -10,7 +10,7 @@
<string name="settings_clear_image_cache">イメージキャッシュクリア</string> <string name="settings_clear_image_cache">イメージキャッシュクリア</string>
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string> <string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
<string name="settings_clear_cache_summary">キャッシュサイズ: %1$d%2$s</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="permission_explain">権限を拒否すると一部の機能が利用できません</string>
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string> <string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
<string name="settings_search_title">検索設定</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="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
<string name="settings_security_mode_title">セキュリティーモード</string> <string name="settings_security_mode_title">セキュリティーモード</string>
<string name="settings_security_mode_summary">アプリ履歴でアプリの画面を表示しない</string> <string name="settings_security_mode_summary">アプリ履歴でアプリの画面を表示しない</string>
<string name="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> </resources>

View File

@@ -30,4 +30,17 @@
<string name="update_release_note"># 릴리즈 노트(v%1$s)\n%2$s</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_summary">최근 앱 목록 창에서 앱 화면을 보이지 않게 합니다</string>
<string name="settings_security_mode_title">보안 모드 활성화</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> </resources>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <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:windowLightStatusBar">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@color/transparent</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="appbar_padding">64dp</dimen> <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_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>

View File

@@ -4,10 +4,17 @@
<string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil-issue/releases</string> <string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil-issue/releases</string>
<string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string> <string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
<string name="home_page" translatable="false">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="main_settings" translatable="false">Settings</string>
<string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string> <string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string>
<string name="reader_imageview_description" translatable="false">Content ImageView</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 --> <!-- Translate needed down here -->
@@ -20,6 +27,14 @@
<string name="main_drawer_home">Home</string> <string name="main_drawer_home">Home</string>
<string name="main_drawer_history">History</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_title">Update available</string>
<string name="update_download_started">Download started</string> <string name="update_download_started">Download started</string>
@@ -32,6 +47,8 @@
<string name="galleryblock_type">Type: %1$s</string> <string name="galleryblock_type">Type: %1$s</string>
<string name="galleryblock_language">Language: %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_title">Settings</string>
<string name="settings_search_title">Search Settings</string> <string name="settings_search_title">Search Settings</string>
<string name="settings_galleries_per_page">Galleries per page</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_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="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> </resources>

View File

@@ -13,7 +13,7 @@
app:defaultValue="25" app:defaultValue="25"
app:useSimpleSummaryProvider="true"/> app:useSimpleSummaryProvider="true"/>
<EditTextPreference <Preference
app:key="default_query" app:key="default_query"
app:title="@string/settings_default_query" app:title="@string/settings_default_query"
app:defaultValue="" app:defaultValue=""