Merge pull request #1 from tom5079/development

Version 1.2
This commit is contained in:
tom5079
2019-05-13 21:47:00 +09:00
committed by GitHub
33 changed files with 630 additions and 212 deletions

1
.idea/vcs.xml generated
View File

@@ -2,6 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/gh-pages" vcs="Git" />
</component>
</project>

View File

@@ -7,10 +7,10 @@ android {
compileSdkVersion 28
defaultConfig {
applicationId "xyz.quaver.pupil"
minSdkVersion 15
minSdkVersion 16
targetSdkVersion 28
versionCode 2
versionName "1.1"
versionCode 3
versionName "1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -19,9 +19,14 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
kotlinOptions {
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
}
}
dependencies {
def markwonVersion = "3.0.1"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
@@ -34,6 +39,7 @@ dependencies {
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "ru.noties.markwon:core:${markwonVersion}"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'

View File

@@ -11,7 +11,9 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import xyz.quaver.hiyobi.getReader
import java.io.File
import java.util.*
/**
* Instrumented test, which will execute on an Android device.
@@ -38,16 +40,7 @@ class ExampleInstrumentedTest {
}
@Test
@ExperimentalUnsignedTypes
fun test_doSearch() {
Log.d("TEST", "Starting...")
runBlocking {
CoroutineScope(Dispatchers.Main).launch {
Log.d("TEST", "This is started! wow")
}.join()
}
Log.d("TEST", "Finished! ...Really?")
}
}

View File

@@ -1,9 +1,11 @@
package xyz.quaver.pupil
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.activity_gallery.*
import kotlinx.coroutines.*
import xyz.quaver.hitomi.Reader
@@ -32,9 +34,20 @@ class GalleryActivity : AppCompatActivity() {
setContentView(R.layout.activity_gallery)
supportActionBar?.title = intent.getStringExtra("GALLERY_TITLE")
galleryID = intent.getIntExtra("GALLERY_ID", 0)
CoroutineScope(Dispatchers.Unconfined).launch {
reader = async(Dispatchers.IO) {
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryActivity)
if (preference.getBoolean("use_hiyobi", false)) {
try {
xyz.quaver.hiyobi.getReader(galleryID)
Log.d("Pupil", "Using Hiyobi.me")
} catch (e: Exception) {
getReader(galleryID)
}
}
getReader(galleryID)
}
}
@@ -43,6 +56,18 @@ class GalleryActivity : AppCompatActivity() {
loadImages()
}
override fun onResume() {
val preferences = android.preference.PreferenceManager.getDefaultSharedPreferences(this)
if (preferences.getBoolean("security_mode", false))
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
super.onResume()
}
override fun onDestroy() {
super.onDestroy()
loadJob?.cancel()
@@ -78,17 +103,15 @@ class GalleryActivity : AppCompatActivity() {
val reader = reader.await()
launch(Dispatchers.Main) {
supportActionBar?.title = reader.title
with(gallery_progressbar) {
max = reader.images.size
max = reader.size
progress = 0
visibility = View.VISIBLE
}
}
reader.images.chunked(8).forEach { chunked ->
reader.chunked(8).forEach { chunked ->
chunked.map {
async(Dispatchers.IO) {
val url = if (it.second?.haswebp == 1) webpUrlFromUrl(it.first) else it.first

View File

@@ -10,6 +10,7 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.preference.PreferenceManager
@@ -18,12 +19,15 @@ import android.text.style.AlignmentSpan
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.arlib.floatingsearchview.FloatingSearchView
@@ -31,36 +35,43 @@ import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView
import com.google.android.material.appbar.AppBarLayout
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_content.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.content
import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.*
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.SetLineOverlap
import xyz.quaver.pupil.util.checkUpdate
import xyz.quaver.pupil.util.getApkUrl
import java.io.File
import java.lang.StringBuilder
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity() {
private val PERMISSION_REQUEST_CODE = 4585
private val permissionRequestCode = 4585
private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>()
private var isLoading = false
private var query = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
private var galleryIDs: Deferred<List<Int>>? = null
private var loadingJob: Job? = null
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
override fun onCreate(savedInstanceState: Bundle?) {
Histories.default = Histories(File(cacheDir, "histories.json"))
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkPermission()
update()
checkUpdate()
main_appbar_layout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, p1 ->
@@ -73,16 +84,60 @@ class MainActivity : AppCompatActivity() {
setProgressViewOffset(false, 0, resources.getDimensionPixelSize(R.dimen.progress_view_offset))
setOnRefreshListener {
runBlocking {
cleanJob?.join()
CoroutineScope(Dispatchers.Main).launch {
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
fetchGalleries(query, true)
}
}
main_nav_view.setNavigationItemSelectedListener {
CoroutineScope(Dispatchers.Main).launch {
main_drawer_layout.closeDrawers()
cancelFetch()
clearGalleries()
when(it.itemId) {
R.id.main_drawer_home -> {
query = query.replace("HISTORY", "")
fetchGalleries(query)
}
R.id.main_drawer_history -> {
query += "HISTORY"
fetchGalleries(query)
}
}
loadBlocks()
}
true
}
setupRecyclerView()
setupSearchBar()
fetchGalleries(query)
loadBlocks()
}
override fun onBackPressed() {
if (main_drawer_layout.isDrawerOpen(GravityCompat.START))
main_drawer_layout.closeDrawer(GravityCompat.START)
else
super.onBackPressed()
}
override fun onResume() {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
if (preferences.getBoolean("security_mode", false))
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
super.onResume()
}
private fun checkPermission() {
@@ -98,11 +153,54 @@ class MainActivity : AppCompatActivity() {
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
else
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
ActivityCompat.requestPermissions(this, permissions, permissionRequestCode)
}
}
private fun update() {
private fun checkUpdate() {
fun extractReleaseNote(update: JsonObject, locale: String) : String {
val markdown = update["body"]!!.content
val target = when(locale) {
"ko" -> "한국어"
"ja" -> "日本語"
else -> "English"
}
val releaseNote = Regex("^# Release Note.+$")
val language = Regex("^## $target$")
val end = Regex("^#.+$")
var releaseNoteFlag = false
var languageFlag = false
val result = StringBuilder()
for(line in markdown.split('\n')) {
if (releaseNote.matches(line)) {
releaseNoteFlag = true
continue
}
if (releaseNoteFlag) {
if (language.matches(line)) {
languageFlag = true
continue
}
}
if (languageFlag) {
if (end.matches(line))
break
result.append(line+"\n")
}
}
return getString(R.string.update_release_note, update["tag_name"]?.content, result.toString())
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
return
@@ -114,8 +212,13 @@ class MainActivity : AppCompatActivity() {
val dialog = AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.update_title)
setMessage(getString(R.string.update_message, update["tag_name"], BuildConfig.VERSION_NAME))
val msg = extractReleaseNote(update, Locale.getDefault().language)
setMessage(Markwon.create(context).toMarkdown(msg))
setPositiveButton(android.R.string.yes) { _, _ ->
Toast.makeText(
context, getString(R.string.update_download_started), Toast.LENGTH_SHORT
).show()
val dest = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName)
val desturi =
FileProvider.getUriForFile(
@@ -131,6 +234,7 @@ class MainActivity : AppCompatActivity() {
setDescription(getString(R.string.update_notification_description))
setTitle(getString(R.string.app_name))
setDestinationUri(Uri.fromFile(dest))
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
}
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
@@ -161,12 +265,15 @@ class MainActivity : AppCompatActivity() {
private fun setupRecyclerView() {
with(main_recyclerview) {
adapter = GalleryBlockAdapter(galleries).apply {
setClickListener {
setClickListener { galleryID, title ->
val intent = Intent(this@MainActivity, GalleryActivity::class.java)
intent.putExtra("GALLERY_ID", it)
intent.putExtra("GALLERY_ID", galleryID)
intent.putExtra("GALLERY_TITLE", title)
//TODO: Maybe sprinke some transitions will be nice :D
startActivity(intent)
Histories.default.add(galleryID)
}
}
addOnScrollListener(
@@ -176,9 +283,9 @@ class MainActivity : AppCompatActivity() {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
if (!isLoading)
if (loadingJob?.isActive != true)
if (layoutManager.findLastCompletelyVisibleItemPosition() == galleries.size)
fetchGalleries(query)
loadBlocks()
}
}
)
@@ -295,101 +402,107 @@ class MainActivity : AppCompatActivity() {
if (query != this@MainActivity.query) {
this@MainActivity.query = query
fetchGalleries(query, true)
cancelFetch()
clearGalleries()
fetchGalleries(query)
}
}
})
attachNavigationDrawerToMenuButton(main_drawer_layout)
}
}
private val cache = ArrayList<Int>()
private var currentFetchingJob: Job? = null
private var cleanJob: Job? = null
private fun cancelFetch() {
isLoading = false
runBlocking {
cleanJob?.join()
currentFetchingJob?.cancelAndJoin()
galleryIDs?.cancelAndJoin()
loadingJob?.cancelAndJoin()
}
}
private fun fetchGalleries(query: String, clear: Boolean = false) {
private fun clearGalleries() {
galleries.clear()
main_recyclerview.adapter?.notifyDataSetChanged()
main_noresult.visibility = View.INVISIBLE
main_progressbar.show()
main_swipe_layout.isRefreshing = false
}
private fun fetchGalleries(query: String, from: Int = 0) {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
if (clear) {
cancelFetch()
cleanJob = CoroutineScope(Dispatchers.Main).launch {
cache.clear()
galleries.clear()
galleryIDs = null
main_recyclerview.adapter?.notifyDataSetChanged()
main_noresult.visibility = View.INVISIBLE
main_progressbar.show()
main_swipe_layout.isRefreshing = false
}
}
if (isLoading)
if (galleryIDs?.isActive == true)
return
isLoading = true
galleryIDs = CoroutineScope(Dispatchers.IO).async {
when {
query.contains("HISTORY") ->
Histories.default.toList()
query.isEmpty() and defaultQuery.isEmpty() ->
fetchNozomi(start = from, count = perPage)
else ->
doSearch("$defaultQuery $query")
}
}
}
currentFetchingJob = CoroutineScope(Dispatchers.IO).launch {
try {
val galleryIDs: List<Int>
private fun loadBlocks() {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
cleanJob?.join()
loadingJob = CoroutineScope(Dispatchers.IO).launch {
val galleryIDs = galleryIDs?.await()
if (query.isEmpty() && defaultQuery.isEmpty())
galleryIDs = fetchNozomi(start = galleries.size, count = perPage)
else {
if (cache.isEmpty())
cache.addAll(doSearch("$defaultQuery $query"))
galleryIDs = cache.slice(galleries.size until Math.min(galleries.size + perPage, cache.size))
with(main_recyclerview.adapter as GalleryBlockAdapter) {
noMore = galleries.size + perPage >= cache.size
}
if (galleryIDs.isNullOrEmpty()) { //No result
withContext(Dispatchers.Main) {
main_noresult.visibility = View.VISIBLE
main_progressbar.hide()
}
if (query.isNotEmpty() and defaultQuery.isNotEmpty() and cache.isNullOrEmpty()) {
return@launch
}
if (query.isEmpty() and defaultQuery.isEmpty())
fetchGalleries("", galleries.size+perPage)
else
with(main_recyclerview.adapter as GalleryBlockAdapter) {
noMore = galleries.size + perPage >= galleryIDs.size
}
when {
query.isEmpty() and defaultQuery.isEmpty() ->
galleryIDs
else ->
galleryIDs.slice(galleries.size until Math.min(galleries.size+perPage, galleryIDs.size))
}.chunked(4).forEach { chunked ->
chunked.map {
async {
val galleryBlock = getGalleryBlock(it)
val thumbnail: Bitmap
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
thumbnail = BitmapFactory.decodeStream(inputStream)
}
Pair(galleryBlock, thumbnail)
}
}.forEach {
val galleryBlock = it.await()
withContext(Dispatchers.Main) {
main_noresult.visibility = View.VISIBLE
main_progressbar.hide()
galleries.add(galleryBlock)
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
}
}
galleryIDs.chunked(4).forEach { chunked ->
chunked.map {
async {
val galleryBlock = getGalleryBlock(it)
val thumbnail: Bitmap
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
thumbnail = BitmapFactory.decodeStream(inputStream)
}
Pair(galleryBlock, thumbnail)
}
}.forEach {
val galleryBlock = it.await()
withContext(Dispatchers.Main) {
main_progressbar.hide()
galleries.add(galleryBlock)
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
}
}
}
} finally {
isLoading = false
}
}
}

View File

@@ -1,12 +1,15 @@
package xyz.quaver.pupil
import android.os.Bundle
import android.preference.PreferenceManager
import android.view.MenuItem
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import xyz.quaver.pupil.util.Histories
import java.io.File
class SettingsActivity : AppCompatActivity() {
@@ -25,6 +28,18 @@ class SettingsActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onResume() {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
if (preferences.getBoolean("security_mode", false))
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
super.onResume()
}
class SettingsFragment : PreferenceFragmentCompat() {
private val suffix = listOf(
@@ -35,8 +50,8 @@ class SettingsActivity : AppCompatActivity() {
"TB" //really?
)
private fun getCacheSize() : String {
var size = context!!.cacheDir.walk().map { it.length() }.sum()
private fun getCacheSize(dir: File) : String {
var size = dir.walk().map { it.length() }.sum()
var suffixIndex = 0
while (size >= 1024) {
@@ -50,22 +65,43 @@ class SettingsActivity : AppCompatActivity() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
with(findPreference<Preference>("delete_cache")) {
with(findPreference<Preference>("delete_image_cache")) {
this ?: return@with
summary = getCacheSize()
val dir = File(context.cacheDir, "imageCache")
summary = getCacheSize(dir)
setOnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_cache_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
with(context.cacheDir) {
if (exists())
deleteRecursively()
}
if (dir.exists())
dir.deleteRecursively()
summary = getCacheSize()
summary = getCacheSize(dir)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
true
}
}
with(findPreference<Preference>("clear_history")) {
this ?: return@with
val histories = Histories.default
summary = getString(R.string.settings_clear_history_summary, histories.size)
setOnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_history_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
histories.clear()
summary = getString(R.string.settings_clear_history_summary, histories.size)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()

View File

@@ -33,8 +33,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?
class ViewHolder(val view: CardView) : RecyclerView.ViewHolder(view)
class ProgressViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
private var callback: ((Int) -> Unit)? = null
fun setClickListener(callback: ((Int) -> Unit)?) {
private var callback: ((Int, String) -> Unit)? = null
fun setClickListener(callback: ((Int, String) -> Unit)?) {
this.callback = callback
}
@@ -74,7 +74,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?
val series = gallery.series.ifEmpty { listOf("N/A") }
setOnClickListener {
callback?.invoke(gallery.id)
callback?.invoke(gallery.id, gallery.title)
}
galleryblock_thumbnail.setImageBitmap(thumbnail)

View File

@@ -0,0 +1,74 @@
package xyz.quaver.pupil.util
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.parseList
import kotlinx.serialization.stringify
import java.io.File
class Histories(private val file: File) : ArrayList<Int>() {
init {
if (!file.exists())
file.parentFile.mkdirs()
try {
load()
} catch (e: Exception) {
save()
}
}
companion object {
lateinit var default: Histories
fun load(file: File) : Histories {
return Histories(file).load()
}
}
@UseExperimental(ImplicitReflectionSerializer::class)
fun load() : Histories {
return apply {
super.clear()
addAll(
Json(JsonConfiguration.Stable).parseList(
file.bufferedReader().use { it.readText() }
)
)
}
}
@UseExperimental(ImplicitReflectionSerializer::class)
fun save() {
file.writeText(Json(JsonConfiguration.Stable).stringify(this))
}
override fun add(element: Int): Boolean {
load()
if (contains(element))
super.remove(element)
super.add(0, element)
save()
return true
}
override fun remove(element: Int): Boolean {
load()
val retval = super.remove(element)
save()
return retval
}
override fun clear() {
super.clear()
save()
}
}

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="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

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="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,83 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<androidx.coordinatorlayout.widget.CoordinatorLayout
<include layout="@layout/activity_main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"/>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/main_appbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent">
<View
android:layout_width="match_parent"
android:layout_height="64dp"
android:visibility="invisible"
android:background="@color/transparent"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.ContentLoadingProgressBar
style="?android:attr/progressBarStyle"
android:id="@+id/main_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"/>
<TextView
android:id="@+id/main_noresult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/main_no_result"
android:visibility="invisible"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/main_swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="-64dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="64dp"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.arlib.floatingsearchview.FloatingSearchView
android:id="@+id/main_searchview"
android:layout_width="match_parent"
<com.google.android.material.navigation.NavigationView
android:id="@+id/main_nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:floatingSearch_searchBarMarginLeft="8dp"
app:floatingSearch_searchBarMarginRight="8dp"
app:floatingSearch_searchBarMarginTop="8dp"
app:floatingSearch_searchHint="@string/search_hint"
app:floatingSearch_suggestionsListAnimDuration="250"
app:floatingSearch_showSearchKey="true"
app:floatingSearch_leftActionMode="noLeftAction"
app:floatingSearch_menu="@menu/main"
app:floatingSearch_dismissOnOutsideTouch="true"
app:floatingSearch_close_search_on_keyboard_dismiss="true"/>
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer"/>
</RelativeLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/main_appbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent">
<View
android:layout_width="match_parent"
android:layout_height="64dp"
android:visibility="invisible"
android:background="@color/transparent"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.ContentLoadingProgressBar
style="?android:attr/progressBarStyle"
android:id="@+id/main_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"/>
<TextView
android:id="@+id/main_noresult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/main_no_result"
android:visibility="invisible"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/main_swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="-64dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="64dp"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.arlib.floatingsearchview.FloatingSearchView
android:id="@+id/main_searchview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:floatingSearch_searchBarMarginLeft="8dp"
app:floatingSearch_searchBarMarginRight="8dp"
app:floatingSearch_searchBarMarginTop="8dp"
app:floatingSearch_searchHint="@string/search_hint"
app:floatingSearch_suggestionsListAnimDuration="250"
app:floatingSearch_showSearchKey="true"
app:floatingSearch_leftActionMode="showHamburger"
app:floatingSearch_menu="@menu/main"
app:floatingSearch_dismissOnOutsideTouch="true"
app:floatingSearch_close_search_on_keyboard_dismiss="true"/>
</RelativeLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:gravity="bottom"/>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item android:id="@+id/main_drawer_home"
android:title="@string/main_drawer_home"
android:checked="true"
android:icon="@drawable/ic_home"/>
<item android:id="@+id/main_drawer_history"
android:title="@string/main_drawer_history"
android:icon="@drawable/ic_history"/>
</group>
</menu>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Pupil</string>
<string name="galleryblock_language">言語: %1$s</string>
<string name="galleryblock_series">シリーズ: %1$s</string>
<string name="galleryblock_type">タイプ: %1$s</string>
@@ -8,7 +7,7 @@
<string name="main_search">検索</string>
<string name="search_hint">ギャラリー検索</string>
<string name="settings_cache_title">キャッシュ</string>
<string name="settings_clear_cache">キャッシュクリア</string>
<string name="settings_clear_image_cache">イメージキャッシュクリア</string>
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
<string name="settings_clear_cache_summary">キャッシュサイズ: %1$d%2$s</string>
<string name="settings_default_query">デフォルト検索キーワード</string>
@@ -16,8 +15,19 @@
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
<string name="settings_search_title">検索設定</string>
<string name="settings_title">設定</string>
<string name="update_message">新バージョン%1$sをリリースしました\n(現バージョン: %2$s)\nアップデートしますか</string>
<string name="update_notification_description">アップデートダウンロード中</string>
<string name="update_title">新しいアップデートがあります</string>
<string name="warning">注意</string>
<string name="settings_miscellaneous_title">その他</string>
<string name="settings_use_hiyobi_summary">ロード速度を向上させるためhiyobi.meからイメージロード</string>
<string name="settings_use_hiyobi_title">hiyobi.meからロード</string>
<string name="settings_clear_history">履歴の削除</string>
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
<string name="settings_clear_history_summary">履歴数: %1$d</string>
<string name="main_drawer_history">履歴</string>
<string name="main_drawer_home">トップ</string>
<string name="update_download_started">ダウンロード中</string>
<string name="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
<string name="settings_security_mode_title">セキュリティーモード</string>
<string name="settings_security_mode_summary">アプリ履歴でアプリの画面を表示しない</string>
</resources>

View File

@@ -1,23 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Pupil</string>
<string name="galleryblock_language">언어: %1$s</string>
<string name="galleryblock_series">시리즈: %1$s</string>
<string name="galleryblock_type">종류: %1$s</string>
<string name="permission_explain">권한을 거부하면 일부 기능이 작동하지 않을 수 있습니다</string>
<string name="search_hint">갤러리 검색</string>
<string name="settings_default_query">기본 검색어</string>
<string name="settings_clear_cache">캐시 정리하기</string>
<string name="settings_clear_image_cache">이미지 캐시 정리하기</string>
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
<string name="settings_clear_cache_summary">현재 캐시 사용량: %1$d%2$s</string>
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
<string name="settings_search_title">검색 설정</string>
<string name="settings_title">설정</string>
<string name="update_message">버전 %1$s이 출시되었습니다.\n(현재 %2$s)\n업데이트 하시겠습니까?</string>
<string name="update_notification_description">apk 다운로드중&#8230;</string>
<string name="update_title">업데이트가 있습니다!</string>
<string name="warning">경고</string>
<string name="main_no_result">결과 없음</string>
<string name="main_search">검색</string>
<string name="settings_cache_title">캐시</string>
<string name="settings_miscellaneous_title">기타</string>
<string name="settings_use_hiyobi_summary">속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드</string>
<string name="settings_use_hiyobi_title">hiyobi.me 사용</string>
<string name="settings_clear_history">히스토리 삭제</string>
<string name="settings_clear_history_alert_message">히스토리를 삭제하시겠습니까?</string>
<string name="settings_clear_history_summary">히스토리 %1$d개 저장됨</string>
<string name="main_drawer_history">히스토리</string>
<string name="main_drawer_home"></string>
<string name="update_download_started">다운로드 중</string>
<string name="update_release_note"># 릴리즈 노트(v%1$s)\n%2$s</string>
<string name="settings_security_mode_summary">최근 앱 목록 창에서 앱 화면을 보이지 않게 합니다</string>
<string name="settings_security_mode_title">보안 모드 활성화</string>
</resources>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="android:windowFullscreen">true</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@color/transparent</item>

View File

@@ -2,4 +2,9 @@
<resources>
<dimen name="appbar_padding">64dp</dimen>
<dimen name="progress_view_offset">80dp</dimen>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
</resources>

View File

@@ -18,9 +18,13 @@
<string name="main_search">Search</string>
<string name="main_no_result">No result</string>
<string name="main_drawer_home">Home</string>
<string name="main_drawer_history">History</string>
<string name="update_title">Update available</string>
<string name="update_message">Version %1$s is available!\n(current version is %2$s)\nDo you want to update?</string>
<string name="update_download_started">Download started</string>
<string name="update_notification_description">Downloading apk&#8230;</string>
<string name="update_release_note"># Release Note(v%1$s)\n%2$s</string>
<string name="search_hint">Search galleries</string>
@@ -33,8 +37,16 @@
<string name="settings_galleries_per_page">Galleries per page</string>
<string name="settings_default_query">Default query</string>
<string name="settings_cache_title">Cache</string>
<string name="settings_clear_cache">Clear cache</string>
<string name="settings_clear_image_cache">Clear image cache</string>
<string name="settings_clear_cache_summary">Currently using %1$d%2$s of cache</string>
<string name="settings_clear_cache_alert_message">Deleting cache can affect image loading speed. Do you want to continue?</string>
<string name="settings_clear_history">Clear history</string>
<string name="settings_clear_history_alert_message">Do you want to clear histories?</string>
<string name="settings_clear_history_summary">%1$d histories saved</string>
<string name="settings_miscellaneous_title">Miscellaneous</string>
<string name="settings_use_hiyobi_title">Use hiyobi.me</string>
<string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string>
<string name="settings_security_mode_title">Enable security mode</string>
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
</resources>

View File

@@ -25,8 +25,28 @@
app:title="@string/settings_cache_title">
<Preference
app:title="@string/settings_clear_cache"
app:key="delete_cache"/>
app:title="@string/settings_clear_image_cache"
app:key="delete_image_cache"/>
<Preference
app:title="@string/settings_clear_history"
app:key="clear_history"/>
</PreferenceCategory>
<PreferenceCategory
app:title="@string/settings_miscellaneous_title">
<SwitchPreference
app:key="use_hiyobi"
app:title="@string/settings_use_hiyobi_title"
app:summary="@string/settings_use_hiyobi_summary"/>
<SwitchPreference
app:key="security_mode"
app:title="@string/settings_security_mode_title"
app:summary="@string/settings_security_mode_summary"
app:defaultValue="true"/>
</PreferenceCategory>

View File

@@ -1,7 +1,7 @@
package xyz.quaver.pupil
import kotlinx.serialization.ImplicitReflectionSerializer
import org.junit.Test
import xyz.quaver.pupil.util.checkUpdate
/**
* Example local unit test, which will execute on the development machine (host).
@@ -12,8 +12,9 @@ import xyz.quaver.pupil.util.checkUpdate
class ExampleUnitTest {
@Test
@ImplicitReflectionSerializer
fun test() {
print(checkUpdate("https://api.github.com/repos/tom5079/Pupil-issue/releases", "0.0.1"))
}
}

View File

@@ -1,11 +1,9 @@
package xyz.quaver.hitomi
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import kotlinx.serialization.parseList
import org.jsoup.Jsoup
import java.net.URL
@@ -18,10 +16,7 @@ data class GalleryInfo(
val name: String,
val height: Int
)
data class Reader(
val title: String,
val images: List<Pair<URL, GalleryInfo?>>
)
typealias Reader = List<Pair<URL, GalleryInfo?>>
//Set header `Referer` to reader url to avoid 403 error
fun getReader(galleryID: Int) : Reader {
val readerUrl = "https://hitomi.la/reader/$galleryID.html"
@@ -29,8 +24,6 @@ fun getReader(galleryID: Int) : Reader {
val doc = Jsoup.connect(readerUrl).get()
val title = doc.selectFirst("title").text()
val images = doc.select(".img-url").map {
URL(protocol + urlFromURL(it.text()))
}
@@ -49,5 +42,5 @@ fun getReader(galleryID: Int) : Reader {
if (images.size > galleryInfo.size)
galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
return Reader(title, images zip galleryInfo)
return images zip galleryInfo
}

View File

@@ -0,0 +1,47 @@
package xyz.quaver.hiyobi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.content
import xyz.quaver.hitomi.Reader
import java.net.URL
import javax.net.ssl.HttpsURLConnection
const val hiyobi = "xn--9w3b15m8vo.asia"
const val user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
var cookie: String = ""
fun renewCookie() : String {
val url = "https://$hiyobi/"
with(URL(url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
connectTimeout = 2000
connect()
return headerFields["Set-Cookie"]!![0]
}
}
fun getReader(galleryId: Int) : Reader {
val url = "https://$hiyobi/data/json/${galleryId}_list.json"
if (cookie.isEmpty())
cookie = renewCookie()
val json = Json(JsonConfiguration.Stable).parseJson(
with(URL(url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
connectTimeout = 2000
connect()
inputStream.bufferedReader().use { it.readText() }
}
)
return json.jsonArray.map {
val name = it.jsonObject["name"]!!.content
Pair(URL("https://$hiyobi/data/$galleryId/$name"), null)
}
}

View File

@@ -61,4 +61,9 @@ class UnitTest {
print(reader)
}
@Test
fun test_hiyobi() {
xyz.quaver.hiyobi.getReader(1414061)
}
}