Migrate to Android 29

Re-Added Cache clear to prevent deleting downloading images
This commit is contained in:
tom5079
2019-06-23 00:23:17 +09:00
parent f303e49e97
commit 6d1505241e
30 changed files with 420 additions and 119 deletions
@@ -24,7 +24,7 @@ import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.ReaderItem
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.ui.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.Histories
@@ -6,9 +6,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.quaver.pupil.R
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
@@ -0,0 +1,29 @@
package xyz.quaver.pupil.ui
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_lock.*
import xyz.quaver.pupil.R
class LockActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lock)
supportFragmentManager.beginTransaction().add(
R.id.lock_content,
PatternLockFragment().apply {
onPatternDrawn = {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
}
}
).commit()
lock_pattern.isEnabled = false
lock_fingerprint.isEnabled = false
lock_password.isEnabled = false
}
}
@@ -1,4 +1,4 @@
package xyz.quaver.pupil
package xyz.quaver.pupil.ui
import android.Manifest
import android.content.Intent
@@ -6,7 +6,6 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Animatable
import android.net.Uri
import android.os.Bundle
import android.preference.PreferenceManager
import android.text.*
import android.text.style.AlignmentSpan
import android.view.*
@@ -21,6 +20,7 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat
import androidx.preference.PreferenceManager
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
@@ -40,6 +40,8 @@ import kotlinx.serialization.list
import kotlinx.serialization.stringify
import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.*
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.types.TagSuggestion
@@ -51,6 +53,7 @@ import java.net.URL
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
import kotlin.math.min
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() {
@@ -65,6 +68,12 @@ class MainActivity : AppCompatActivity() {
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
private var query = ""
set(value) {
field = value
findViewById<SearchInputView>(R.id.search_bar_text)
.setText(query, TextView.BufferType.EDITABLE)
}
private var mode = Mode.SEARCH
private val SETTINGS = 45162
@@ -110,19 +119,11 @@ class MainActivity : AppCompatActivity() {
initView()
}
override fun onDestroy() {
super.onDestroy()
if (cacheDir.exists())
cacheDir.deleteRecursively()
}
override fun onBackPressed() {
when {
main_drawer_layout.isDrawerOpen(GravityCompat.START) -> main_drawer_layout.closeDrawer(GravityCompat.START)
query.isNotEmpty() -> runOnUiThread {
query = ""
findViewById<SearchInputView>(R.id.search_bar_text).setText(query, TextView.BufferType.EDITABLE)
cancelFetch()
clearGalleries()
@@ -352,8 +353,7 @@ class MainActivity : AppCompatActivity() {
onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
this@MainActivity.findViewById<SearchInputView>(R.id.search_bar_text)
.setText(query, TextView.BufferType.EDITABLE)
currentPage = 0
cancelFetch()
clearGalleries()
@@ -726,7 +726,8 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
} catch (e: Exception) {
Snackbar.make(main_layout, R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
Snackbar.make(main_layout,
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
}
}
}
@@ -804,7 +805,9 @@ class MainActivity : AppCompatActivity() {
favorites.remove(tag)
}
else {
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star))
setImageDrawable(AnimatedVectorDrawableCompat.create(context,
R.drawable.avd_star
))
(drawable as Animatable).start()
favorites.add(tag)
@@ -880,10 +883,8 @@ class MainActivity : AppCompatActivity() {
}
private fun cancelFetch() {
runBlocking {
galleryIDs?.cancelAndJoin()
loadingJob?.cancelAndJoin()
}
galleryIDs?.cancel()
loadingJob?.cancel()
}
private fun clearGalleries() {
@@ -992,7 +993,7 @@ class MainActivity : AppCompatActivity() {
query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
galleryIDs
else ->
galleryIDs.slice(currentPage*perPage until Math.min(currentPage*perPage+perPage, galleryIDs.size))
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size))
}.chunked(5).let { chunks ->
for (chunk in chunks)
chunk.map { galleryID ->
@@ -1009,8 +1010,8 @@ class MainActivity : AppCompatActivity() {
getGalleryBlock(galleryID).apply {
this ?: return@apply
if (!cache.parentFile.exists())
cache.parentFile.mkdirs()
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, this))
}
@@ -1024,8 +1025,8 @@ class MainActivity : AppCompatActivity() {
if (!exists())
try {
with(URL(galleryBlock.thumbnails[0]).openConnection() as HttpsURLConnection) {
if (!this@apply.parentFile.exists())
this@apply.parentFile.mkdirs()
if (this@apply.parentFile?.exists() == false)
this@apply.parentFile!!.mkdirs()
inputStream.copyTo(FileOutputStream(this@apply))
}
@@ -0,0 +1,46 @@
package xyz.quaver.pupil.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.andrognito.patternlockview.PatternLockView
import com.andrognito.patternlockview.listener.PatternLockViewListener
import com.andrognito.patternlockview.utils.PatternLockUtils
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
import kotlinx.android.synthetic.main.fragment_pattern_lock.view.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.hash
class PatternLockFragment : Fragment(), PatternLockViewListener {
var onPatternDrawn: ((String) -> Unit)? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_pattern_lock, container, false).apply {
lock_pattern_view.addPatternLockListener(this@PatternLockFragment)
}
}
override fun onCleared() {
}
override fun onComplete(pattern: MutableList<PatternLockView.Dot>?) {
val password = PatternLockUtils.patternToMD5(lock_pattern_view, pattern)
onPatternDrawn?.invoke(hash(password))
}
override fun onProgress(progressPattern: MutableList<PatternLockView.Dot>?) {
}
override fun onStarted() {
}
}
@@ -1,14 +1,14 @@
package xyz.quaver.pupil
package xyz.quaver.pupil.ui
import android.app.Application
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.preference.PreferenceManager
import androidx.core.content.ContextCompat
import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.Histories
import java.io.File
@@ -1,4 +1,4 @@
package xyz.quaver.pupil
package xyz.quaver.pupil.ui
import android.content.Intent
import android.graphics.drawable.Animatable
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.crashlytics.android.Crashlytics
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.*
@@ -27,6 +28,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories
@@ -71,6 +73,8 @@ class ReaderActivity : AppCompatActivity() {
handleIntent(intent)
Crashlytics.setInt("GalleryID", galleryBlock.id)
if (!::galleryBlock.isInitialized) {
onBackPressed()
return
@@ -116,7 +120,7 @@ class ReaderActivity : AppCompatActivity() {
} else {
galleryBlock = Json(JsonConfiguration.Stable).parse(
GalleryBlock.serializer(),
intent.getStringExtra("galleryblock")
intent.getStringExtra("galleryblock")!!
)
}
}
@@ -1,8 +1,6 @@
package xyz.quaver.pupil
package xyz.quaver.pupil.ui
import android.os.Bundle
import android.os.Environment
import android.preference.PreferenceManager
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
@@ -15,7 +13,10 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import io.fabric.sdk.android.BuildConfig
import kotlinx.android.synthetic.main.dialog_default_query.view.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tags
import java.io.File
@@ -58,7 +59,7 @@ class SettingsActivity : AppCompatActivity() {
"TB" //really?
)
private fun getCacheSize(dir: File) : String {
private fun getDirSize(dir: File) : String {
var size = dir.walk().map { it.length() }.sum()
var suffixIndex = 0
@@ -67,20 +68,53 @@ class SettingsActivity : AppCompatActivity() {
suffixIndex++
}
return getString(R.string.settings_clear_downloads_summary, size, suffix[suffixIndex])
return getString(R.string.settings_clear_summary, size, suffix[suffixIndex])
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
with(findPreference<Preference>("app_version")) {
this ?: return@with
val manager = context.packageManager
val info = manager.getPackageInfo(context.packageName, 0)
summary = info.versionName
}
with(findPreference<Preference>("delete_cache")) {
this ?: return@with
val dir = File(context.cacheDir, "imageCache")
summary = getDirSize(dir)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_cache_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
if (dir.exists())
dir.deleteRecursively()
summary = getDirSize(dir)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
true
}
}
with(findPreference<Preference>("delete_downloads")) {
this ?: return@with
val dir = File(Environment.getExternalStorageDirectory(), "Pupil")
val dir = context.getExternalFilesDir("Pupil") ?: return@with
summary = getCacheSize(dir)
summary = getDirSize(dir)
setOnPreferenceClickListener {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_downloads_alert_message)
@@ -92,7 +126,7 @@ class SettingsActivity : AppCompatActivity() {
downloads.clear()
summary = getCacheSize(dir)
summary = getDirSize(dir)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
@@ -107,7 +141,7 @@ class SettingsActivity : AppCompatActivity() {
summary = getString(R.string.settings_clear_history_summary, histories.size)
setOnPreferenceClickListener {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_history_alert_message)
@@ -139,7 +173,7 @@ class SettingsActivity : AppCompatActivity() {
val excludeBL = "-male:yaoi"
val excludeGuro = listOf("-female:guro", "-male:guro")
setOnPreferenceClickListener {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
val dialogView = LayoutInflater.from(context).inflate(
R.layout.dialog_default_query,
LinearLayout(context),
@@ -154,7 +188,7 @@ class SettingsActivity : AppCompatActivity() {
with(dialogView.default_query_dialog_language_selector) {
adapter =
ArrayAdapter<String>(
ArrayAdapter(
context,
android.R.layout.simple_spinner_dropdown_item,
arrayListOf(
@@ -237,6 +271,9 @@ class SettingsActivity : AppCompatActivity() {
true
}
}
with(findPreference<Preference>("app_lock")) {
}
}
}
@@ -4,7 +4,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.os.Environment
import android.util.Log
import android.util.SparseArray
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
@@ -18,9 +18,9 @@ import kotlinx.serialization.list
import xyz.quaver.hitomi.*
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.ui.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.ReaderActivity
import xyz.quaver.pupil.ui.ReaderActivity
import java.io.File
import java.io.FileOutputStream
import java.net.URL
@@ -52,7 +52,7 @@ class GalleryDownloader(
cache.deleteRecursively()
}
if (!reader.isActive && downloadJob?.isActive != true)
if (reader?.isActive == false && downloadJob?.isActive != true)
field = false
downloads.add(galleryBlock.id)
@@ -63,7 +63,7 @@ class GalleryDownloader(
onNotifyChangedHandler?.invoke(value)
}
private val reader: Deferred<Reader>
private val reader: Deferred<Reader>?
private var downloadJob: Job? = null
private lateinit var notificationBuilder: NotificationCompat.Builder
@@ -126,8 +126,8 @@ class GalleryDownloader(
if (reader.isNotEmpty()) {
//Save cache
if (!cache.parentFile.exists())
cache.parentFile.mkdirs()
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, reader))
}
@@ -140,7 +140,7 @@ class GalleryDownloader(
fun start() {
downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader.await()
val reader = reader!!.await()
if (reader.isEmpty())
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
@@ -183,8 +183,8 @@ class GalleryDownloader(
} else
setRequestProperty("Referer", getReferer(galleryBlock.id))
if (!cache.parentFile.exists())
cache.parentFile.mkdirs()
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
inputStream.copyTo(FileOutputStream(cache))
}
@@ -209,8 +209,6 @@ class GalleryDownloader(
}
}
onCompleteHandler?.invoke()
Timer(false).schedule(1000) {
notificationBuilder
.setContentTitle(galleryBlock.title)
@@ -220,7 +218,7 @@ class GalleryDownloader(
if (download) {
File(cacheDir, "imageCache/${galleryBlock.id}").let {
if (it.exists()) {
val target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id}")
val target = File(getExternalFilesDir("Pupil"), galleryBlock.id.toString())
if (!target.exists())
target.mkdirs()
@@ -231,9 +229,11 @@ class GalleryDownloader(
}
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
}
download = false
onCompleteHandler?.invoke()
download = false
}
}
remove(galleryBlock.id)
@@ -254,7 +254,7 @@ class GalleryDownloader(
fun invokeOnReaderLoaded() {
CoroutineScope(Dispatchers.Default).launch {
onReaderLoadedHandler?.invoke(reader.await())
onReaderLoadedHandler?.invoke(reader?.await() ?: return@launch)
}
}
@@ -2,10 +2,11 @@ package xyz.quaver.pupil.util
import android.content.Context
import android.os.Environment
import android.provider.MediaStore
import java.io.File
fun getCachedGallery(context: Context, galleryID: Int): File {
return File(Environment.getExternalStorageDirectory(), "Pupil/$galleryID").let {
return File(context.getExternalFilesDir("Pupil"), galleryID.toString()).let {
when {
it.exists() -> it
else -> File(context.cacheDir, "imageCache/$galleryID")
@@ -0,0 +1,39 @@
package xyz.quaver.pupil.util
import android.content.Context
import android.content.ContextWrapper
import java.security.MessageDigest
fun hash(password: String): String {
val bytes = password.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
return md.digest(bytes).fold("") { str, it -> str + "%02x".format(it) }
}
// Ret1: SHA-256 Hash
// Ret2: Hash salt
fun hashWithSalt(password: String): Pair<String, String> {
val salt = (0 until 12).map { source.random() }.joinToString()
return Pair(hash(password+salt), salt)
}
val source = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
data class Lock(private val type: Type, private val hash: String, private val salt: String) {
enum class Type {
PATTERN
}
fun match(password: String): Boolean {
return hash(password+salt) == hash
}
}
class LockManager(base: Context): ContextWrapper(base) {
}
@@ -1,5 +1,6 @@
package xyz.quaver.pupil.util
import android.util.Log
import kotlinx.serialization.json.*
import java.net.URL
@@ -19,8 +20,20 @@ fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
if (releases.isEmpty())
return null
if (currentVersion != releases[0].jsonObject["tag_name"]?.content)
return releases[0].jsonObject
val latestVersion = releases[0].jsonObject["tag_name"]?.content
return null
return when {
currentVersion.split('-').size == 1 -> {
when {
currentVersion != latestVersion -> releases[0].jsonObject
else -> null
}
}
else -> {
when {
(currentVersion.split('-')[0] == latestVersion) -> releases[0].jsonObject
else -> null
}
}
}
}