Added mirror selector
Developing new Downloader under xyz.quaver.pupil.util.download
This commit is contained in:
7
.idea/kotlinCodeInsightSettings.xml
generated
Normal file
7
.idea/kotlinCodeInsightSettings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KotlinCodeInsightWorkspaceSettings">
|
||||||
|
<option name="addUnambiguousImportsOnTheFly" value="true" />
|
||||||
|
<option name="optimizeImportsOnTheFly" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Kotlin2JvmCompilerArguments">
|
||||||
|
<option name="jvmTarget" value="1.8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -41,6 +41,9 @@ android {
|
|||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
}
|
||||||
buildToolsVersion = '29.0.2'
|
buildToolsVersion = '29.0.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +53,8 @@ dependencies {
|
|||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
@@ -73,11 +76,12 @@ dependencies {
|
|||||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
|
implementation ("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
implementation "com.squareup.okhttp3:okhttp:4.3.1"
|
||||||
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||||
kapt 'com.github.bumptech.glide:compiler:4.9.0'
|
kapt 'com.github.bumptech.glide:compiler:4.9.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
|||||||
85
app/src/main/java/xyz/quaver/pupil/adapters/MirrorAdapter.kt
Normal file
85
app/src/main/java/xyz/quaver/pupil/adapters/MirrorAdapter.kt
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.item_mirrors.view.*
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class MirrorAdapter(context: Context) : RecyclerView.Adapter<MirrorAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
|
val mirrors = context.resources.getStringArray(R.array.mirrors).map {
|
||||||
|
it.split('|').let { split ->
|
||||||
|
Pair(split.first(), split.last())
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
val list = mirrors.keys.toMutableList().apply {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getString("mirrors", "")!!
|
||||||
|
.split(">")
|
||||||
|
.reversed()
|
||||||
|
.forEach {
|
||||||
|
if (this.contains(it)) {
|
||||||
|
this.remove(it)
|
||||||
|
this.add(0, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val onItemMove : ((Int, Int) -> Unit) = { from, to ->
|
||||||
|
Collections.swap(list, from, to)
|
||||||
|
notifyItemMoved(from, to)
|
||||||
|
onItemMoved?.invoke(list)
|
||||||
|
}
|
||||||
|
var onStartDrag : ((ViewHolder) -> Unit)? = null
|
||||||
|
var onItemMoved : ((List<String>) -> (Unit))? = null
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
with(holder.view) {
|
||||||
|
mirror_name.text = mirrors[list.elementAt(position)]
|
||||||
|
mirror_button.setOnTouchListener { _, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_DOWN)
|
||||||
|
onStartDrag?.invoke(holder)
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
return LayoutInflater.from(parent.context).inflate(
|
||||||
|
R.layout.item_mirrors, parent, false
|
||||||
|
).let {
|
||||||
|
ViewHolder(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = mirrors.size
|
||||||
|
|
||||||
|
}
|
||||||
@@ -40,6 +40,8 @@ class ReaderAdapter(private val glide: RequestManager,
|
|||||||
|
|
||||||
var isFullScreen = false
|
var isFullScreen = false
|
||||||
|
|
||||||
|
var onItemClickListener : ((Int) -> (Unit))? = null
|
||||||
|
|
||||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
@@ -60,12 +62,16 @@ class ReaderAdapter(private val glide: RequestManager,
|
|||||||
|
|
||||||
var reader: Reader? = null
|
var reader: Reader? = null
|
||||||
with (GalleryDownloader[galleryID]?.reader) {
|
with (GalleryDownloader[galleryID]?.reader) {
|
||||||
if (this?.isCompleted == true)
|
if (reader == null && this?.isCompleted == true)
|
||||||
runBlocking {
|
runBlocking {
|
||||||
reader = await()
|
reader = await()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
||||||
|
onItemClickListener?.invoke(position)
|
||||||
|
}
|
||||||
|
|
||||||
glide
|
glide
|
||||||
.load(File(getCachedGallery(holder.view.context, galleryID), images[position]))
|
.load(File(getCachedGallery(holder.view.context, galleryID), images[position]))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
|||||||
@@ -413,7 +413,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val downloader = GalleryDownloader.get(galleryID)
|
val downloader = GalleryDownloader.get(galleryID)
|
||||||
|
|
||||||
if (downloader == null)
|
if (downloader == null)
|
||||||
GalleryDownloader(context, galleryID, true).start()
|
GalleryDownloader(
|
||||||
|
context,
|
||||||
|
galleryID,
|
||||||
|
true
|
||||||
|
).start()
|
||||||
else {
|
else {
|
||||||
downloader.cancel()
|
downloader.cancel()
|
||||||
downloader.clearNotification()
|
downloader.clearNotification()
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import xyz.quaver.pupil.R
|
|||||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||||
import xyz.quaver.pupil.util.GalleryDownloader
|
import xyz.quaver.pupil.util.GalleryDownloader
|
||||||
import xyz.quaver.pupil.util.Histories
|
import xyz.quaver.pupil.util.Histories
|
||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -333,7 +332,19 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
with(reader_recyclerview) {
|
with(reader_recyclerview) {
|
||||||
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID, images)
|
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID, images).apply {
|
||||||
|
onItemClickListener = {
|
||||||
|
if (isScroll) {
|
||||||
|
isScroll = false
|
||||||
|
isFullscreen = true
|
||||||
|
|
||||||
|
scrollMode(false)
|
||||||
|
fullscreen(true)
|
||||||
|
} else {
|
||||||
|
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage) //Moves to next page because currentPage is 1-based indexing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
@@ -353,19 +364,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
this@ReaderActivity.reader_progressbar.progress = currentPage
|
this@ReaderActivity.reader_progressbar.progress = currentPage
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ItemClickSupport.addTo(this)
|
|
||||||
.setOnItemClickListener { _, _, _ ->
|
|
||||||
if (isScroll) {
|
|
||||||
isScroll = false
|
|
||||||
isFullscreen = true
|
|
||||||
|
|
||||||
scrollMode(false)
|
|
||||||
fullscreen(true)
|
|
||||||
} else {
|
|
||||||
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage) //Moves to next page because currentPage is 1-based indexing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
with(reader_fab_download) {
|
with(reader_fab_download) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package xyz.quaver.pupil.ui.dialog
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
@@ -28,6 +29,7 @@ import android.view.View
|
|||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import kotlinx.android.synthetic.main.dialog_default_query.*
|
||||||
import kotlinx.android.synthetic.main.dialog_default_query.view.*
|
import kotlinx.android.synthetic.main.dialog_default_query.view.*
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.types.Tags
|
import xyz.quaver.pupil.types.Tags
|
||||||
@@ -50,21 +52,41 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
|||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
initDialog()
|
||||||
|
|
||||||
|
setTitle(R.string.default_query_dialog_title)
|
||||||
|
setView(dialogView)
|
||||||
|
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ ->
|
||||||
|
val newTags = Tags.parse(default_query_dialog_edittext.text.toString())
|
||||||
|
|
||||||
|
with(default_query_dialog_language_selector) {
|
||||||
|
if (selectedItemPosition != 0)
|
||||||
|
newTags.add("language:${reverseLanguages[selectedItem]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (default_query_dialog_BL_checkbox.isChecked)
|
||||||
|
newTags.add(excludeBL)
|
||||||
|
|
||||||
|
if (default_query_dialog_guro_checkbox.isChecked)
|
||||||
|
excludeGuro.forEach { tag ->
|
||||||
|
newTags.add(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositiveButtonClickListener?.invoke(newTags)
|
||||||
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_default_query, null)
|
|
||||||
|
|
||||||
initView()
|
|
||||||
|
|
||||||
setContentView(dialogView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
@SuppressLint("InflateParams")
|
||||||
|
private fun initDialog() {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val tags = Tags.parse(
|
val tags = Tags.parse(
|
||||||
preferences.getString("default_query", "") ?: ""
|
preferences.getString("default_query", "") ?: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_default_query, null)
|
||||||
|
|
||||||
with(dialogView.default_query_dialog_language_selector) {
|
with(dialogView.default_query_dialog_language_selector) {
|
||||||
adapter =
|
adapter =
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
@@ -105,7 +127,13 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
|||||||
with(dialogView.default_query_dialog_edittext) {
|
with(dialogView.default_query_dialog_edittext) {
|
||||||
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
|
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
|
||||||
addTextChangedListener(object : TextWatcher {
|
addTextChangedListener(object : TextWatcher {
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
override fun beforeTextChanged(
|
||||||
|
s: CharSequence?,
|
||||||
|
start: Int,
|
||||||
|
count: Int,
|
||||||
|
after: Int
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
|
||||||
@@ -113,29 +141,14 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
|||||||
s ?: return
|
s ?: return
|
||||||
|
|
||||||
if (s.any { it.isUpperCase() })
|
if (s.any { it.isUpperCase() })
|
||||||
s.replace(0, s.length, s.toString().toLowerCase(java.util.Locale.getDefault()))
|
s.replace(
|
||||||
|
0,
|
||||||
|
s.length,
|
||||||
|
s.toString().toLowerCase(java.util.Locale.getDefault())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
onPositiveButtonClickListener?.invoke(newTags)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ package xyz.quaver.pupil.ui.dialog
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
@@ -37,7 +38,7 @@ class DownloadLocationDialog(context: Context) : AlertDialog(context) {
|
|||||||
private val buttons = mutableListOf<RadioButton>()
|
private val buttons = mutableListOf<RadioButton>()
|
||||||
var onDownloadLocationChangedListener : ((Int) -> (Unit))? = null
|
var onDownloadLocationChangedListener : ((Int) -> (Unit))? = null
|
||||||
|
|
||||||
init {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout
|
val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout
|
||||||
|
|
||||||
ContextCompat.getExternalFilesDirs(context, null).forEachIndexed { index, dir ->
|
ContextCompat.getExternalFilesDirs(context, null).forEachIndexed { index, dir ->
|
||||||
@@ -73,6 +74,8 @@ class DownloadLocationDialog(context: Context) : AlertDialog(context) {
|
|||||||
setButton(Dialog.BUTTON_POSITIVE, context.getText(android.R.string.ok)) { _, _ ->
|
setButton(Dialog.BUTTON_POSITIVE, context.getText(android.R.string.ok)) { _, _ ->
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
97
app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt
Normal file
97
app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.adapters.MirrorAdapter
|
||||||
|
|
||||||
|
class MirrorDialog(context: Context) : AlertDialog(context) {
|
||||||
|
|
||||||
|
class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
|
||||||
|
|
||||||
|
var onMoveItem : ((Int, Int) -> (Unit))? = null
|
||||||
|
|
||||||
|
override fun getMovementFlags(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder
|
||||||
|
) = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
|
||||||
|
|
||||||
|
override fun onMove(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
onMoveItem?.invoke(viewHolder.adapterPosition, target.adapterPosition)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var recyclerView: RecyclerView
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
initDialog()
|
||||||
|
|
||||||
|
setTitle(R.string.settings_mirror_title)
|
||||||
|
setView(recyclerView)
|
||||||
|
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> }
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initDialog() {
|
||||||
|
recyclerView = RecyclerView(context).apply recyclerview@{
|
||||||
|
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = MirrorAdapter(context).apply adapter@{
|
||||||
|
val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback().apply {
|
||||||
|
onMoveItem = this@adapter.onItemMove
|
||||||
|
}).apply {
|
||||||
|
attachToRecyclerView(this@recyclerview)
|
||||||
|
}
|
||||||
|
|
||||||
|
onStartDrag = {
|
||||||
|
itemTouchHelper.startDrag(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
onItemMoved = {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.edit()
|
||||||
|
.putString("mirrors", it.joinToString(">"))
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ import xyz.quaver.pupil.ui.LockActivity
|
|||||||
import xyz.quaver.pupil.ui.SettingsActivity
|
import xyz.quaver.pupil.ui.SettingsActivity
|
||||||
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialog
|
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialog
|
||||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog
|
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog
|
||||||
|
import xyz.quaver.pupil.ui.dialog.MirrorDialog
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -137,8 +138,6 @@ class SettingsFragment :
|
|||||||
onPositiveButtonClickListener = { newTags ->
|
onPositiveButtonClickListener = { newTags ->
|
||||||
sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
||||||
summary = newTags.toString()
|
summary = newTags.toString()
|
||||||
dismiss() //This sucks
|
|
||||||
// TODO: make dialog dissmiss itself :P
|
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
@@ -146,6 +145,10 @@ class SettingsFragment :
|
|||||||
val intent = Intent(context, LockActivity::class.java)
|
val intent = Intent(context, LockActivity::class.java)
|
||||||
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_LOCK)
|
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_LOCK)
|
||||||
}
|
}
|
||||||
|
"mirrors" -> {
|
||||||
|
MirrorDialog(context)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
"backup" -> {
|
"backup" -> {
|
||||||
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
|
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
|
||||||
File(getDownloadDirectory(context), "favorites.json"),
|
File(getDownloadDirectory(context), "favorites.json"),
|
||||||
@@ -259,6 +262,9 @@ class SettingsFragment :
|
|||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
"mirrors" -> {
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
"dark_mode" -> {
|
"dark_mode" -> {
|
||||||
onPreferenceChangeListener = this@SettingsFragment
|
onPreferenceChangeListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Pupil, Hitomi.la viewer for Android
|
* Pupil, Hitomi.la viewer for Android
|
||||||
* Copyright (C) 2019 tom5079
|
* Copyright (C) 2020 tom5079
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -65,7 +65,10 @@ class GalleryDownloader(
|
|||||||
notificationManager.notify(galleryID, notificationBuilder.build())
|
notificationManager.notify(galleryID, notificationBuilder.build())
|
||||||
|
|
||||||
if (reader?.isActive == false && downloadJob?.isActive != true) {
|
if (reader?.isActive == false && downloadJob?.isActive != true) {
|
||||||
val data = File(getDownloadDirectory(this), galleryID.toString())
|
val data = File(
|
||||||
|
getDownloadDirectory(
|
||||||
|
this
|
||||||
|
), galleryID.toString())
|
||||||
val cache = File(cacheDir, "imageCache/$galleryID")
|
val cache = File(cacheDir, "imageCache/$galleryID")
|
||||||
|
|
||||||
if (File(cache, "images").exists() && !data.exists()) {
|
if (File(cache, "images").exists() && !data.exists()) {
|
||||||
@@ -111,7 +114,11 @@ class GalleryDownloader(
|
|||||||
val serializer = Reader.serializer()
|
val serializer = Reader.serializer()
|
||||||
|
|
||||||
//Check cache
|
//Check cache
|
||||||
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json")
|
val cache = File(
|
||||||
|
getCachedGallery(
|
||||||
|
this@GalleryDownloader,
|
||||||
|
galleryID
|
||||||
|
), "reader.json")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
json.parse(serializer, cache.readText())
|
json.parse(serializer, cache.readText())
|
||||||
@@ -197,7 +204,11 @@ class GalleryDownloader(
|
|||||||
val name = "$index".padStart(4, '0')
|
val name = "$index".padStart(4, '0')
|
||||||
val ext = url.split('.').last()
|
val ext = url.split('.').last()
|
||||||
|
|
||||||
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "images/$name.$ext")
|
val cache = File(
|
||||||
|
getCachedGallery(
|
||||||
|
this@GalleryDownloader,
|
||||||
|
galleryID
|
||||||
|
), "images/$name.$ext")
|
||||||
|
|
||||||
if (!cache.exists())
|
if (!cache.exists())
|
||||||
try {
|
try {
|
||||||
@@ -255,7 +266,10 @@ class GalleryDownloader(
|
|||||||
if (download) {
|
if (download) {
|
||||||
File(cacheDir, "imageCache/${galleryID}").let {
|
File(cacheDir, "imageCache/${galleryID}").let {
|
||||||
if (it.exists()) {
|
if (it.exists()) {
|
||||||
val target = File(getDownloadDirectory(this@GalleryDownloader), galleryID.toString())
|
val target = File(
|
||||||
|
getDownloadDirectory(
|
||||||
|
this@GalleryDownloader
|
||||||
|
), galleryID.toString())
|
||||||
|
|
||||||
if (!target.exists())
|
if (!target.exists())
|
||||||
target.mkdirs()
|
target.mkdirs()
|
||||||
|
|||||||
142
app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt
Normal file
142
app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util.download
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.parse
|
||||||
|
import kotlinx.serialization.stringify
|
||||||
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class Cache(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
|
// Search in this order
|
||||||
|
// Download -> Cache
|
||||||
|
fun getCachedGallery(galleryID: Int) : File? {
|
||||||
|
var file : File
|
||||||
|
|
||||||
|
ContextCompat.getExternalFilesDirs(this, null).forEach {
|
||||||
|
file = File(it, galleryID.toString())
|
||||||
|
|
||||||
|
if (file.exists())
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
file = File(cacheDir, "imageCache/$galleryID")
|
||||||
|
|
||||||
|
return if (file.exists())
|
||||||
|
file
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||||
|
fun getCachedMetadata(galleryID: Int) : Metadata? {
|
||||||
|
val file = File(getCachedGallery(galleryID) ?: return null, ".metadata")
|
||||||
|
|
||||||
|
if (!file.exists())
|
||||||
|
return null
|
||||||
|
|
||||||
|
return try {
|
||||||
|
Json.parse(file.readText())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
//File corrupted
|
||||||
|
file.delete()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||||
|
fun setCachedMetadata(galleryID: Int, metadata: Metadata) {
|
||||||
|
val file = File(getCachedGallery(galleryID), ".metadata")
|
||||||
|
|
||||||
|
if (!file.exists())
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
file.writeText(Json.stringify(metadata))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGalleryBlock(galleryID: Int): GalleryBlock {
|
||||||
|
var meta = Cache(this).getCachedMetadata(galleryID)
|
||||||
|
|
||||||
|
if (meta == null) {
|
||||||
|
meta = Metadata(galleryBlock = xyz.quaver.hitomi.getGalleryBlock(galleryID))
|
||||||
|
|
||||||
|
Cache(this).setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
meta
|
||||||
|
)
|
||||||
|
} else if (meta.galleryBlock == null)
|
||||||
|
Cache(this).setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
meta.apply {
|
||||||
|
galleryBlock = xyz.quaver.hitomi.getGalleryBlock(galleryID)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return meta.galleryBlock!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReaders(galleryID: Int): List<Reader> {
|
||||||
|
var meta = getCachedMetadata(galleryID)
|
||||||
|
|
||||||
|
if (meta == null) {
|
||||||
|
meta = Metadata(reader = mutableListOf(xyz.quaver.hitomi.getReader(galleryID)))
|
||||||
|
|
||||||
|
setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
meta
|
||||||
|
)
|
||||||
|
} else if (meta.reader == null)
|
||||||
|
setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
meta.apply {
|
||||||
|
reader = mutableListOf(xyz.quaver.hitomi.getReader(galleryID))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else if (!meta.reader!!.any { it.code == Reader.Code.HITOMI })
|
||||||
|
setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
meta.apply {
|
||||||
|
reader!!.add(xyz.quaver.hitomi.getReader(galleryID))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return meta.reader!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImage(galleryID: Int, index: Int): File {
|
||||||
|
val cache = getCachedGallery(galleryID)
|
||||||
|
|
||||||
|
if (cache == null)
|
||||||
|
;//TODO: initiate image download
|
||||||
|
|
||||||
|
return File(cache, "%04d".format(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util.download
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import okio.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class DownloadWorker(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
|
interface ProgressListener {
|
||||||
|
fun update(bytesRead : Long, contentLength: Long, done: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
//region ProgressResponseBody
|
||||||
|
class ProgressResponseBody(
|
||||||
|
val responseBody: ResponseBody,
|
||||||
|
val progressListener : ProgressListener
|
||||||
|
) : ResponseBody() {
|
||||||
|
var bufferedSource : BufferedSource? = null
|
||||||
|
|
||||||
|
override fun contentLength() = responseBody.contentLength()
|
||||||
|
override fun contentType() = responseBody.contentType()
|
||||||
|
|
||||||
|
override fun source(): BufferedSource {
|
||||||
|
if (bufferedSource == null)
|
||||||
|
bufferedSource = source(responseBody.source()).buffer()
|
||||||
|
|
||||||
|
return bufferedSource!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun source(source: Source) = object: ForwardingSource(source) {
|
||||||
|
|
||||||
|
var totalBytesRead = 0L
|
||||||
|
|
||||||
|
override fun read(sink: Buffer, byteCount: Long): Long {
|
||||||
|
val bytesRead = super.read(sink, byteCount)
|
||||||
|
|
||||||
|
totalBytesRead += if (bytesRead == -1L) 0L else bytesRead
|
||||||
|
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
|
||||||
|
|
||||||
|
return bytesRead
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
val queue = Channel<Int>()
|
||||||
|
val worker = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
|
||||||
|
|
||||||
|
val progressListener = object: ProgressListener {
|
||||||
|
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.addNetworkInterceptor { chain ->
|
||||||
|
chain.proceed(chain.request()).let { originalResponse ->
|
||||||
|
originalResponse.newBuilder()
|
||||||
|
.body(ProgressResponseBody(originalResponse.body!!, progressListener))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
init {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
while (true) {
|
||||||
|
val galleryID = queue.receive()
|
||||||
|
|
||||||
|
val reader = Cache(context).getReaders(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
app/src/main/java/xyz/quaver/pupil/util/download/Metadata.kt
Normal file
30
app/src/main/java/xyz/quaver/pupil/util/download/Metadata.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util.download
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Metadata(
|
||||||
|
var thumbnail: String? = null,
|
||||||
|
var galleryBlock: GalleryBlock? = null,
|
||||||
|
var reader: MutableList<Reader>? = null
|
||||||
|
)
|
||||||
@@ -49,6 +49,7 @@ fun URL.download(to: File, onDownloadProgress: ((Long, Long) -> Unit)? = null) {
|
|||||||
|
|
||||||
var bytesCopied: Long = 0
|
var bytesCopied: Long = 0
|
||||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
|
|
||||||
var bytes = it.read(buffer)
|
var bytes = it.read(buffer)
|
||||||
while (bytes >= 0) {
|
while (bytes >= 0) {
|
||||||
out.write(buffer, 0, bytes)
|
out.write(buffer, 0, bytes)
|
||||||
|
|||||||
8
app/src/main/res/drawable/menu.xml
Normal file
8
app/src/main/res/drawable/menu.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/menu.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#fff" android:pathData="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
|
||||||
|
</vector>
|
||||||
@@ -18,21 +18,12 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
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:padding="16dp">
|
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
|
<EditText
|
||||||
tools:ignore="Autofill"
|
tools:ignore="Autofill"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
@@ -40,7 +31,7 @@
|
|||||||
android:id="@+id/default_query_dialog_edittext"
|
android:id="@+id/default_query_dialog_edittext"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_title"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
@@ -116,14 +107,4 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
47
app/src/main/res/layout/item_mirrors.xml
Normal file
47
app/src/main/res/layout/item_mirrors.xml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2020 tom5079
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mirror_name"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/mirror_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/menu"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<string name="update_title">新しいアップデートがあります</string>
|
<string name="update_title">新しいアップデートがあります</string>
|
||||||
<string name="warning">注意</string>
|
<string name="warning">注意</string>
|
||||||
<string name="settings_miscellaneous_title">その他</string>
|
<string name="settings_miscellaneous_title">その他</string>
|
||||||
<string name="settings_use_hiyobi_title">hiyobi.meからロード</string>
|
<string name="settings_mirror_title">ミラーサーバー</string>
|
||||||
<string name="settings_clear_history">履歴を削除</string>
|
<string name="settings_clear_history">履歴を削除</string>
|
||||||
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
|
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
|
||||||
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<string name="main_export_error">エクスポートエラーが発生しました</string>
|
<string name="main_export_error">エクスポートエラーが発生しました</string>
|
||||||
<string name="settings_clear_downloads">ダウンロード削除</string>
|
<string name="settings_clear_downloads">ダウンロード削除</string>
|
||||||
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか?</string>
|
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか?</string>
|
||||||
<string name="settings_use_hiyobi_summary">ロード速度を向上させるため可能であればhiyobi.meからイメージロード</string>
|
<string name="settings_mirror_summary">ミラーサーバからイメージをロード</string>
|
||||||
<string name="main_drawer_favorite">お気に入り</string>
|
<string name="main_drawer_favorite">お気に入り</string>
|
||||||
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
|
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
|
||||||
<string name="main_open_gallery_by_id_error">エラーが発生しました</string>
|
<string name="main_open_gallery_by_id_error">エラーが発生しました</string>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
<string name="main_no_result">결과 없음</string>
|
<string name="main_no_result">결과 없음</string>
|
||||||
<string name="main_search">검색</string>
|
<string name="main_search">검색</string>
|
||||||
<string name="settings_miscellaneous_title">기타</string>
|
<string name="settings_miscellaneous_title">기타</string>
|
||||||
<string name="settings_use_hiyobi_title">hiyobi.me 사용</string>
|
|
||||||
<string name="settings_clear_history">기록 삭제</string>
|
<string name="settings_clear_history">기록 삭제</string>
|
||||||
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
|
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
|
||||||
<string name="settings_clear_history_summary">기록 %1$d개 저장됨</string>
|
<string name="settings_clear_history_summary">기록 %1$d개 저장됨</string>
|
||||||
@@ -61,7 +60,6 @@
|
|||||||
<string name="main_export_error">내보내기 오류가 발생했습니다</string>
|
<string name="main_export_error">내보내기 오류가 발생했습니다</string>
|
||||||
<string name="settings_clear_downloads">다운로드 삭제</string>
|
<string name="settings_clear_downloads">다운로드 삭제</string>
|
||||||
<string name="settings_clear_downloads_alert_message">다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까?</string>
|
<string name="settings_clear_downloads_alert_message">다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까?</string>
|
||||||
<string name="settings_use_hiyobi_summary">속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드</string>
|
|
||||||
<string name="main_drawer_favorite">즐겨찾기</string>
|
<string name="main_drawer_favorite">즐겨찾기</string>
|
||||||
<string name="main_open_gallery_by_id">갤러리 번호로 열기</string>
|
<string name="main_open_gallery_by_id">갤러리 번호로 열기</string>
|
||||||
<string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string>
|
<string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string>
|
||||||
@@ -120,4 +118,6 @@
|
|||||||
<string name="settings_app_version_description">v%s</string>
|
<string name="settings_app_version_description">v%s</string>
|
||||||
<string name="settings_low_quality">저해상도 이미지</string>
|
<string name="settings_low_quality">저해상도 이미지</string>
|
||||||
<string name="settings_low_quality_summary">로드 속도와 데이터 사용량을 줄이기 위해 저해상도 이미지를 로드</string>
|
<string name="settings_low_quality_summary">로드 속도와 데이터 사용량을 줄이기 위해 저해상도 이미지를 로드</string>
|
||||||
|
<string name="settings_mirror_summary">미러 서버에서 이미지 로드</string>
|
||||||
|
<string name="settings_mirror_title">미러 설정</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -57,4 +57,9 @@
|
|||||||
<item>japanese|日本語</item>
|
<item>japanese|日本語</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="mirrors">
|
||||||
|
<item>HITOMI|hitomi.la</item>
|
||||||
|
<item>HIYOBI|hiyobi.me</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -151,8 +151,7 @@
|
|||||||
<!-- SETTINGS/MISCELLANEOUS -->
|
<!-- SETTINGS/MISCELLANEOUS -->
|
||||||
|
|
||||||
<string name="settings_miscellaneous_title">Miscellaneous</string>
|
<string name="settings_miscellaneous_title">Miscellaneous</string>
|
||||||
<string name="settings_use_hiyobi_title">Use hiyobi.me</string>
|
<string name="settings_mirror_summary">Load images from mirrors</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_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="settings_dark_mode_title">Dark mode</string>
|
<string name="settings_dark_mode_title">Dark mode</string>
|
||||||
@@ -186,5 +185,6 @@
|
|||||||
<string name="default_query_dialog_filter_BL">Filter BL</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_filter_guro">Filter Guro</string>
|
||||||
<string name="default_query_dialog_language_selector_none">Any</string>
|
<string name="default_query_dialog_language_selector_none">Any</string>
|
||||||
|
<string name="settings_mirror_title">Mirrors</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -66,10 +66,10 @@
|
|||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
app:title="@string/settings_miscellaneous_title">
|
app:title="@string/settings_miscellaneous_title">
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<Preference
|
||||||
app:key="use_hiyobi"
|
app:key="mirrors"
|
||||||
app:title="@string/settings_use_hiyobi_title"
|
app:title="@string/settings_mirror_title"
|
||||||
app:summary="@string/settings_use_hiyobi_summary"/>
|
app:summary="@string/settings_mirror_summary"/>
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="security_mode"
|
app:key="security_mode"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress("UNUSED_VARIABLE")
|
@file:Suppress("UNUSED_VARIABLE", "IncorrectScope")
|
||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class ExampleUnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test() {
|
fun test() {
|
||||||
URL("https://github.om/tom5079/Pupil/releases/download/4.2-beta2-hotfix2/Pupil-v4.2-beta2-hotfix2.apk").download(
|
URL("https://github.com/tom5079/Pupil/releases/download/4.2-beta2-hotfix2/Pupil-v4.2-beta2-hotfix2.apk").download(
|
||||||
File(System.getenv("USERPROFILE"), "Pupil.apk")
|
File(System.getenv("USERPROFILE"), "Pupil.apk")
|
||||||
) { downloaded, fileSize ->
|
) { downloaded, fileSize ->
|
||||||
println("%.1f%%".format(downloaded*100.0/fileSize))
|
println("%.1f%%".format(downloaded*100.0/fileSize))
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
|
|
||||||
package xyz.quaver.hitomi
|
package xyz.quaver.hitomi
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class Gallery(
|
data class Gallery(
|
||||||
val related: List<Int>,
|
val related: List<Int>,
|
||||||
val langList: List<Pair<String, String>>,
|
val langList: List<Pair<String, String>>,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ fun doSearch(query: String, sortByPopularity: Boolean = false) : List<Int> {
|
|||||||
val terms = query
|
val terms = query
|
||||||
.trim()
|
.trim()
|
||||||
.replace(Regex("""^\?"""), "")
|
.replace(Regex("""^\?"""), "")
|
||||||
.toLowerCase()
|
.toLowerCase(Locale.US)
|
||||||
.split(Regex("\\s+"))
|
.split(Regex("\\s+"))
|
||||||
.map {
|
.map {
|
||||||
it.replace('_', ' ')
|
it.replace('_', ' ')
|
||||||
|
|||||||
Reference in New Issue
Block a user