Compare commits

..

3 Commits
4.14 ... 4.15

Author SHA1 Message Date
Pupil
3198c6cbfd Merge pull request #74 from tom5079/dev
Version 4.15
2020-03-25 19:02:50 -07:00
pupil
b3feee6d9d Fast scroll Added
Not able to download after finished downloading to cache Fixed
Trying to move files from different threads causing exceptions Fixed
Import old galleries Added
2020-03-25 10:29:05 -07:00
Pupil
f0f53e6bce Fixed - Jump to page in Reader not working 2020-03-02 20:30:26 +09:00
30 changed files with 489 additions and 52 deletions

View File

@@ -19,8 +19,8 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 29
versionCode 50
versionName "4.14"
versionCode 51
versionName "4.15"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
@@ -46,7 +46,7 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildToolsVersion = '29.0.2'
buildToolsVersion = '29.0.3'
}
dependencies {
@@ -66,7 +66,7 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.2.0-alpha05'
implementation 'com.google.firebase:firebase-core:17.2.2'
implementation 'com.google.firebase:firebase-core:17.2.3'
implementation 'com.google.firebase:firebase-perf:19.0.5'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1'

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":50,"versionName":"4.14","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}]
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":51,"versionName":"4.15","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}]

View File

@@ -28,6 +28,7 @@ import androidx.test.rule.ActivityTestRule
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.getReader
@@ -62,6 +63,13 @@ class ExampleInstrumentedTest {
}
}
@Test
fun test_nozomi() {
val nozomi = getGalleryIDsFromNozomi(null, "index", "all")
Log.i("PUPILD", nozomi.size.toString())
}
@Test
fun test_doSearch() {
val reader = getReader( 1426382)

View File

@@ -30,10 +30,17 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider
import androidx.preference.PreferenceManager
import xyz.quaver.pupil.util.NOTIFICATION_ID_UPDATE
import xyz.quaver.pupil.util.cancelImport
import java.io.File
class BroadcastReciever : BroadcastReceiver() {
companion object {
const val ACTION_CANCEL_IMPORT = "ACTION_CANCEL_IMPORT"
const val EXTRA_IMPORT_NOTIFICATION_ID = "EXTRA_IMPORT_NOTIFICATION_ID"
}
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
@@ -87,6 +94,9 @@ class BroadcastReciever : BroadcastReceiver() {
notificationManager.notify(NOTIFICATION_ID_UPDATE, notification)
}
ACTION_CANCEL_IMPORT -> {
cancelImport = true
}
}
}

View File

@@ -89,6 +89,13 @@ class Pupil : MultiDexApplication() {
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
manager.createNotificationChannel(NotificationChannel("import", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply {
description = getString(R.string.channel_update_description)
enableLights(false)
enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
}
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)

View File

@@ -414,18 +414,14 @@ class MainActivity : AppCompatActivity() {
}
onDownloadClickedHandler = { position ->
val galleryID = galleries[position].id
val worker = DownloadWorker.getInstance(context)
if (!completeFlag.get(galleryID, false)) {
val worker = DownloadWorker.getInstance(context)
if (Cache(context).isDownloading(galleryID)) //download in progress
worker.cancel(galleryID)
else {
Cache(context).setDownloading(galleryID, true)
if (Cache(context).isDownloading(galleryID)) //download in progress
worker.cancel(galleryID)
else {
Cache(context).setDownloading(galleryID, true)
if (!worker.queue.contains(galleryID))
worker.queue.add(galleryID)
}
worker.queue.add(galleryID)
}
closeAllItems()
@@ -477,6 +473,7 @@ class MainActivity : AppCompatActivity() {
GalleryDialog(
this@MainActivity,
Glide.with(this@MainActivity),
galleryID
).apply {
onChipClickedHandler.add {
@@ -731,6 +728,16 @@ class MainActivity : AppCompatActivity() {
favoritesFile.writeText(json.stringify(serializer, Tags(listOf())))
}
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
override fun onMenuOpened() {
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
}
override fun onMenuClosed() {
//Do Nothing
}
})
setOnMenuItemClickListener {
when(it.itemId) {
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)

View File

@@ -158,10 +158,10 @@ class ReaderActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId) {
R.id.reader_menu_page_indicator -> {
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
with(view.dialog_number_picker) {
minValue=1
maxValue=reader_recyclerview?.adapter?.itemCount ?: 0
maxValue=Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.files?.size ?: 0
value=currentPage
}
val dialog = AlertDialog.Builder(this).apply {

View File

@@ -156,6 +156,43 @@ class SettingsActivity : AppCompatActivity() {
.apply()
}
}
REQUEST_IMPORT_OLD_GALLERIES -> {
if (resultCode == Activity.RESULT_OK) {
data?.data?.also { uri ->
val takeFlags: Int =
intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
contentResolver.takePersistableUriPermission(uri, takeFlags)
val file = uri.toFile(this)
if (file?.canRead() != true)
Snackbar.make(
settings,
resources.getText(R.string.import_old_galleries_folder_not_readable),
Snackbar.LENGTH_LONG
).show()
else
importOldGalleries(this, file)
}
}
}
REQUEST_IMPORT_OLD_GALLERIES_OLD -> {
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
if (!File(directory).canRead())
Snackbar.make(
settings,
resources.getText(R.string.import_old_galleries_folder_not_readable),
Snackbar.LENGTH_LONG
).show()
else {
importOldGalleries(this, File(directory))
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}

View File

@@ -18,6 +18,7 @@
package xyz.quaver.pupil.ui.dialog
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.content.Intent
@@ -29,7 +30,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_gallery.*
@@ -53,7 +54,7 @@ import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.wordCapitalize
class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) {
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) {
private val languages = context.resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
@@ -61,8 +62,6 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
}
}.toMap()
private val glide = Glide.with(context)
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -90,7 +89,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
try {
val gallery = getGallery(galleryID)
launch(Dispatchers.Main) {
gallery_cover.post {
gallery_progressbar.visibility = View.GONE
gallery_title.text = gallery.title
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
@@ -112,7 +111,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
}
}
Glide.with(context)
glide
.load(gallery.cover)
.apply {
if (BuildConfig.CENSOR)
@@ -226,7 +225,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
val inflater = LayoutInflater.from(context)
val galleries = ArrayList<GalleryBlock>()
val adapter = GalleryBlockAdapter(Glide.with(ownerActivity ?: return), galleries).apply {
val adapter = GalleryBlockAdapter(glide, galleries).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
@@ -264,6 +263,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
.setOnItemLongClickListener { _, position, _ ->
GalleryDialog(
context,
glide,
galleries[position].id
).apply {
onChipClickedHandler.add { tag ->

View File

@@ -18,17 +18,23 @@
package xyz.quaver.pupil.ui.fragment
import android.Manifest
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import net.rdrei.android.dirchooser.DirectoryChooserActivity
import net.rdrei.android.dirchooser.DirectoryChooserConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.LockActivity
@@ -168,6 +174,31 @@ class SettingsFragment :
activity?.startActivityForResult(intent, REQUEST_RESTORE)
}
"old_import_galleries" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(activity!!, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_PERMISSION_AND_SAF)
else {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
putExtra("android.content.extra.SHOW_ADVANCED", true)
}
activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES)
}
} else { // Can't use SAF on old Androids!
val config = DirectoryChooserConfig.builder()
.newDirectoryName("Pupil")
.allowNewDirectoryNameModification(true)
.build()
val intent = Intent(context, DirectoryChooserActivity::class.java).apply {
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
}
activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES_OLD)
}
}
else -> return false
}
}
@@ -297,6 +328,9 @@ class SettingsFragment :
"restore" -> {
onPreferenceClickListener = this@SettingsFragment
}
"old_import_galleries" -> {
onPreferenceClickListener = this@SettingsFragment
}
}
}

View File

@@ -20,9 +20,16 @@ package xyz.quaver.pupil.util
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import xyz.quaver.proxy
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
const val REQUEST_LOCK = 38238
const val REQUEST_RESTORE = 16546
const val REQUEST_IMPORT_OLD_GALLERIES = 6458
const val REQUEST_IMPORT_OLD_GALLERIES_OLD = 5946
const val REQUEST_DOWNLOAD_FOLDER = 3874
const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425
const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900

View File

@@ -38,11 +38,16 @@ import xyz.quaver.pupil.util.json
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.util.concurrent.Executors
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
class Cache(context: Context) : ContextWrapper(context) {
companion object {
private val moving = mutableListOf<Int>()
}
private val locks = SparseArray<Lock>()
private fun lock(galleryID: Int) {
synchronized(locks) {
@@ -245,27 +250,32 @@ class Cache(context: Context) : ContextWrapper(context) {
}
}
fun moveToDownload(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
val cache = getCachedGallery(galleryID).also {
if (!it.exists())
fun moveToDownload(galleryID: Int) {
if (moving.contains(galleryID))
return
CoroutineScope(Dispatchers.IO).launch {
val cache = getCachedGallery(galleryID).also {
if (!it.exists())
return@launch
}
val download = File(getDownloadDirectory(this@Cache), galleryID.toString())
if (download.isParentOf(cache))
return@launch
Log.i("PUPILD", "MOVING ${cache.canonicalPath} --> ${download.canonicalPath}")
cache.copyRecursively(download, true) { file, err ->
Log.i("PUPILD", "MOVING ERROR ${file.canonicalPath} ${err.message}")
OnErrorAction.SKIP
}
Log.i("PUPILD", "MOVED ${cache.canonicalPath}")
Log.i("PUPILD", "DELETING ${cache.canonicalPath}")
cache.deleteRecursively()
Log.i("PUPILD", "DELETED ${cache.canonicalPath}")
}
val download = File(getDownloadDirectory(this@Cache), galleryID.toString())
if (download.isParentOf(cache))
return@launch
Log.i("PUPILD", "MOVING ${cache.canonicalPath} --> ${download.canonicalPath}")
cache.copyRecursively(download, true) { file, err ->
Log.i("PUPILD", "MOVING ERROR ${file.canonicalPath} ${err.message}")
OnErrorAction.SKIP
}
Log.i("PUPILD", "MOVED ${cache.canonicalPath}")
Log.i("PUPILD", "DELETING ${cache.canonicalPath}")
cache.deleteRecursively()
Log.i("PUPILD", "DELETED ${cache.canonicalPath}")
}
fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true

View File

@@ -158,10 +158,11 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
.body(ProgressResponseBody(request.tag(), response.body(), progressListener))
.build()
}
val client =
OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(0, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.readTimeout(0, TimeUnit.SECONDS)
.dispatcher(Dispatcher(Executors.newFixedThreadPool(4)))
.proxy(proxy)
@@ -178,7 +179,11 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
worker[galleryID]?.cancel()
}
client.dispatcher().cancelAll()
client.dispatcher().queuedCalls().filter {
it.request().tag() is Pair<*, *>
}.forEach {
it.cancel()
}
progress.clear()
exception.clear()
@@ -411,7 +416,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
val galleryID = queue.peek() ?: continue
if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
continue
cancel(galleryID)
if (notification[galleryID] == null)
initNotification(galleryID)

View File

@@ -18,24 +18,40 @@
package xyz.quaver.pupil.util
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Base64
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.content
import okhttp3.*
import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.hitomi.getReader
import xyz.quaver.proxy
import xyz.quaver.pupil.BroadcastReciever
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.Metadata
import java.io.File
import java.io.IOException
import java.net.URL
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
fun getReleases(url: String) : JsonArray {
return try {
@@ -172,4 +188,149 @@ fun checkUpdate(context: Context, force: Boolean = false) {
dialog.show()
}
}
}
var cancelImport = false
@SuppressLint("RestrictedApi")
fun importOldGalleries(context: Context, folder: File) = CoroutineScope(Dispatchers.IO).launch {
val client = OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
.proxy(proxy)
.build()
val cancelIntent = Intent(context, BroadcastReciever::class.java).apply {
action = BroadcastReciever.ACTION_CANCEL_IMPORT
putExtra(BroadcastReciever.EXTRA_IMPORT_NOTIFICATION_ID, 0)
}
val pendingIntent = PendingIntent.getBroadcast(context, 0, cancelIntent, 0)
val notificationManager = NotificationManagerCompat.from(context)
val notificationBuilder = NotificationCompat.Builder(context, "import").apply {
setContentTitle(context.getText(R.string.import_old_galleries_notification))
setProgress(0, 0, true)
setSmallIcon(R.drawable.ic_notification)
addAction(0, context.getText(android.R.string.cancel), pendingIntent)
setOngoing(true)
}
notificationManager.notify(0, notificationBuilder.build())
if (!folder.isDirectory)
return@launch
val galleryRegex = Regex("""[0-9]+$""")
val imageRegex = Regex("""^[0-9]+\..+$""")
var size = 0
fun setProgress(progress: Int) {
notificationBuilder.apply {
setContentText(
context.getString(
R.string.import_old_galleries_notification_text,
progress,
size
)
)
setProgress(size, progress, false)
}
notificationManager.notify(0, notificationBuilder.build())
}
folder.listFiles { _, name ->
galleryRegex.matches(name)
}?.also {
size = it.size
setProgress(0)
}?.forEachIndexed { index, gallery ->
if (cancelImport)
return@forEachIndexed
setProgress(index)
val galleryID = gallery.name.toIntOrNull() ?: return@forEachIndexed
File(getDownloadDirectory(context), galleryID.toString()).mkdirs()
val reader = async {
kotlin.runCatching {
json.parse(Reader.serializer(), File(gallery, "reader.json").readText())
}.getOrElse {
getReader(galleryID)
}
}
val galleryBlock = async {
kotlin.runCatching {
json.parse(GalleryBlock.serializer(), File(gallery, "galleryBlock.json").readText())
}.getOrElse {
getGalleryBlock(galleryID)
}
}
@Suppress("NAME_SHADOWING")
val thumbnail = async thumbnail@{
val galleryBlock = galleryBlock.await()
Base64.encodeToString(try {
File(gallery, "thumbnail.jpg").readBytes()
} catch (e: Exception) {
val url = galleryBlock?.thumbnails?.firstOrNull()
if (url == null)
null
else {
val request = Request.Builder().url(url).build()
var done = false
var result: ByteArray? = null
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
done = true
}
override fun onResponse(call: Call?, response: Response?) {
result = response?.body()?.use {
it.bytes()
}
done = true
}
})
if (!done)
yield()
result
}
} ?: return@thumbnail null, Base64.DEFAULT)
}
Cache(context).setCachedMetadata(galleryID,
Metadata(
thumbnail.await(),
galleryBlock.await(),
reader.await()
)
)
File(gallery, "images").listFiles { _, name ->
imageRegex.matches(name)
}?.forEach {
if (cancelImport)
return@forEach
@Suppress("NAME_SHADOWING")
val index = it.nameWithoutExtension.toIntOrNull() ?: return@forEach
Cache(context).putImage(galleryID, index, it.extension, it.inputStream())
}
}
notificationBuilder.apply {
setContentText(context.getText(R.string.import_old_galleries_notification_done))
setProgress(0, 0, false)
setOngoing(false)
mActions.clear()
}
notificationManager.notify(0, notificationBuilder.build())
cancelImport = false
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

View File

@@ -0,0 +1,34 @@
<?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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="44dp"
android:topRightRadius="44dp"
android:bottomLeftRadius="44dp" />
<padding
android:paddingLeft="22dp"
android:paddingRight="22dp" />
<solid android:color="@color/colorPrimary" />
</shape>

View File

@@ -0,0 +1,27 @@
<?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/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/thumb"/>
<item
android:drawable="@drawable/thumb"/>
</selector>

View File

@@ -0,0 +1,30 @@
<?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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<padding
android:top="10dp"
android:left="10dp"
android:right="10dp"
android:bottom="10dp"/>
</shape>

View File

@@ -0,0 +1,27 @@
<?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/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/track"/>
<item
android:drawable="@drawable/track"/>
</selector>

View File

@@ -71,7 +71,11 @@
android:layout_height="match_parent"
android:paddingTop="64dp"
android:clipToPadding="false"
android:scrollbars="vertical"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/track_drawable"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<com.github.clans.fab.FloatingActionMenu

View File

@@ -30,6 +30,11 @@
android:id="@+id/reader_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/track_drawable"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<LinearLayout

View File

@@ -134,4 +134,10 @@
<string name="main_fab_cancel">すべてのダウンロードキャンセル</string>
<string name="channel_update">アップデート</string>
<string name="channel_update_description">アップデートの進行状態を表示</string>
<string name="channel_import">インポート</string>
<string name="channel_import_description">インポート状態を表示</string>
<string name="settings_import_old_galleries">旧ギャラリーインポート</string>
<string name="import_old_galleries_folder_not_readable">フォルダを読めません</string>
<string name="import_old_galleries_notification">旧ギャラリーインポート中…</string>
<string name="import_old_galleries_notification_done">インポート完了</string>
</resources>

View File

@@ -134,4 +134,10 @@
<string name="main_fab_cancel">다운로드 모두 취소</string>
<string name="channel_update">업데이트</string>
<string name="channel_update_description">업데이트 진행상황 표시</string>
<string name="channel_import">가져오기</string>
<string name="channel_import_description">가져오기 상태 표시</string>
<string name="settings_import_old_galleries">이전 버전 갤러리 가져오기</string>
<string name="import_old_galleries_folder_not_readable">폴더를 읽을 수 없습니다</string>
<string name="import_old_galleries_notification">이전 버전 갤러리 가져오는 중…</string>
<string name="import_old_galleries_notification_done">가져오기 완료</string>
</resources>

View File

@@ -38,6 +38,9 @@
<string name="channel_update">Update</string>
<string name="channel_update_description">Shows update progress</string>
<string name="channel_import">Import</string>
<string name="channel_import_description">Shows progress of import</string>
<string name="unable_to_connect">Unable to connect to hitomi.la</string>
<string name="lock_corrupted">Lock file corrupted! Please re-install Pupil</string>
@@ -173,6 +176,7 @@
<string name="settings_restore_title">Restore favorites</string>
<string name="settings_restore_failed">Restore failed</string>
<string name="settings_restore_successful">%1$d entries restored</string>
<string name="settings_import_old_galleries">Import old galleries</string>
<!-- SETTINGS/APP LOCK ACTIVITY -->
@@ -205,4 +209,10 @@
<string name="proxy_dialog_error">Wrong value</string>
<string name="proxy_dialog_server">server</string>
<!-- IMPORT OLD GALLERIES -->
<string name="import_old_galleries_folder_not_readable">This folder is not readable</string>
<string name="import_old_galleries_notification">Importing old galleries…</string>
<string name="import_old_galleries_notification_text" translatable="false">%1$d/%2$d</string>
<string name="import_old_galleries_notification_done">Importing completed</string>
</resources>

View File

@@ -99,6 +99,10 @@
app:key="restore"
app:title="@string/settings_restore_title"/>
<Preference
app:key="old_import_galleries"
app:title="@string/settings_import_old_galleries"/>
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@@ -34,8 +34,6 @@ class ExampleUnitTest {
@Test
fun test() {
val arr = SparseArray<Float>()
print(arr.indexOfKey(34))
}
}