diff --git a/.gitignore b/.gitignore
index 9a319865..d582af87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,4 @@
/gh-pages
#Private files
-/app/google-services.json
\ No newline at end of file
+**/google-services.json
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 02530dd6..d9f5c3f1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,9 +3,12 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
-apply plugin: 'com.google.gms.google-services'
-apply plugin: 'io.fabric'
-apply plugin: 'com.google.firebase.firebase-perf'
+
+if (file("google-services.json").exists()) {
+ apply plugin: 'com.google.gms.google-services'
+ apply plugin: 'io.fabric'
+ apply plugin: 'com.google.firebase.firebase-perf'
+}
android {
compileSdkVersion 29
@@ -14,7 +17,7 @@ android {
minSdkVersion 16
targetSdkVersion 29
versionCode 31
- versionName "4.2-beta1"
+ versionName "4.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
@@ -25,7 +28,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
buildTypes.each {
- it.buildConfigField('boolean', 'PRERELEASE', 'false')
it.buildConfigField('boolean', 'CENSOR', 'false')
}
}
@@ -36,6 +38,7 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
+ buildToolsVersion = '29.0.2'
}
dependencies {
@@ -55,12 +58,12 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation "androidx.biometric:biometric:1.0.0"
+ implementation "androidx.biometric:biometric:1.0.1"
implementation 'com.android.support:multidex:1.0.3'
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
- implementation 'com.google.android.material:material:1.2.0-alpha02'
+ implementation 'com.google.android.material:material:1.2.0-alpha03'
implementation 'com.google.firebase:firebase-core:17.2.1'
- implementation 'com.google.firebase:firebase-perf:19.0.3'
+ implementation 'com.google.firebase:firebase-perf:19.0.4'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4'
diff --git a/app/release/output.json b/app/release/output.json
index 6014db92..4abc5e8a 100644
--- a/app/release/output.json
+++ b/app/release/output.json
@@ -1 +1 @@
-[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":31,"versionName":"4.2-beta1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
+[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":31,"versionName":"4.2","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
\ No newline at end of file
diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
index 663d6fc3..6d4686e3 100644
--- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
@@ -20,8 +20,8 @@
package xyz.quaver.pupil
-import android.content.Intent
import android.util.Log
+import androidx.core.content.ContextCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
@@ -64,7 +64,9 @@ class ExampleInstrumentedTest {
val activityTestRule = ActivityTestRule(LockActivity::class.java)
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- activityTestRule.launchActivity(Intent())
+ ContextCompat.getExternalFilesDirs(appContext, null).forEachIndexed { index, file ->
+ Log.i("PUPILD", "$index: ${file?.absolutePath}")
+ }
}
@Test
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ac068015..4db90b6f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,10 +4,8 @@
package="xyz.quaver.pupil">
-
-
-
+
+
+ android:resource="@xml/file_paths" />
-
+
-
-
-
-
-
-
-
-
@@ -99,20 +86,9 @@
-
-
-
-
-
-
-
-
+ android:host="hiyobi.me"
+ android:scheme="http"
+ android:pathPrefix="/reader" />
diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
index 52b9a947..efc52b3b 100644
--- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt
+++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt
@@ -62,19 +62,15 @@ class Pupil : MultiDexApplication() {
e.printStackTrace()
}
- if (!preference.getBoolean("channel_created", false)) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
- description = getString(R.string.channel_download_description)
- enableLights(false)
- enableVibration(false)
- lockscreenVisibility = Notification.VISIBILITY_SECRET
- }
- manager.createNotificationChannel(channel)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
+ description = getString(R.string.channel_download_description)
+ enableLights(false)
+ enableVibration(false)
+ lockscreenVisibility = Notification.VISIBILITY_SECRET
}
-
- preference.edit().putBoolean("channel_created", true).apply()
+ manager.createNotificationChannel(channel)
}
AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) {
diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
index a177925c..1a65a3e4 100644
--- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
+++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
@@ -95,7 +95,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
if (BuildConfig.CENSOR)
override(5, 8)
}
- .fitCenter()
.into(galleryblock_thumbnail)
}
diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt
index 8e076d07..36d29dc3 100644
--- a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt
+++ b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt
@@ -49,17 +49,21 @@ class ReaderAdapter(private val glide: RequestManager,
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.view as ImageView
+ if (isFullScreen)
+ holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
+ else
+ holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
+
glide
.load(File(getCachedGallery(holder.view.context, galleryID), images[position]))
- .dontTransform()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.error(R.drawable.image_broken_variant)
+ .dontTransform()
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
- .fitCenter()
.into(holder.view)
}
diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/ThumbnailAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/ThumbnailAdapter.kt
index 759f7523..db85c79d 100644
--- a/app/src/main/java/xyz/quaver/pupil/adapters/ThumbnailAdapter.kt
+++ b/app/src/main/java/xyz/quaver/pupil/adapters/ThumbnailAdapter.kt
@@ -39,7 +39,6 @@ class ThumbnailAdapter(private val glide: RequestManager, private val thumbnails
if (BuildConfig.CENSOR)
override(5, 8)
}
- .fitCenter()
.into(holder.view)
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
index f268d9a0..a536dd36 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
@@ -18,17 +18,11 @@
package xyz.quaver.pupil.ui
-import android.Manifest
import android.app.Activity
-import android.app.DownloadManager
-import android.content.BroadcastReceiver
-import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
import android.graphics.drawable.Animatable
import android.net.Uri
import android.os.Bundle
-import android.os.Environment
import android.text.*
import android.text.style.AlignmentSpan
import android.view.KeyEvent
@@ -42,7 +36,6 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
-import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat
@@ -60,11 +53,8 @@ import kotlinx.coroutines.*
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
-import kotlinx.serialization.json.JsonObject
-import kotlinx.serialization.json.content
import kotlinx.serialization.list
import kotlinx.serialization.stringify
-import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.*
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
@@ -72,6 +62,7 @@ import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.types.Tags
+import xyz.quaver.pupil.ui.dialog.GalleryDialog
import xyz.quaver.pupil.util.*
import java.io.File
import java.io.FileOutputStream
@@ -143,8 +134,6 @@ class MainActivity : AppCompatActivity() {
if (lockManager.isNotEmpty())
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK)
- checkPermissions()
-
val preference = PreferenceManager.getDefaultSharedPreferences(this)
if (Locale.getDefault().language == "ko") {
@@ -167,7 +156,7 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
- checkUpdate()
+ checkUpdate(this)
initView()
}
@@ -256,125 +245,6 @@ class MainActivity : AppCompatActivity() {
}
}
- private fun checkUpdate() {
-
- val preferences = PreferenceManager.getDefaultSharedPreferences(this)
- val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0)
-
- if (ignoreUpdateUntil > System.currentTimeMillis())
- return
-
- fun extractReleaseNote(update: JsonObject, locale: String) : String {
- val markdown = update["body"]!!.content
-
- val target = when(locale) {
- "ko" -> "한국어"
- "ja" -> "日本語"
- else -> "English"
- }
-
- val releaseNote = Regex("^# Release Note.+$")
- val language = Regex("^## $target$")
- val end = Regex("^#.+$")
-
- var releaseNoteFlag = false
- var languageFlag = false
-
- val result = StringBuilder()
-
- for(line in markdown.lines()) {
- if (releaseNote.matches(line)) {
- releaseNoteFlag = true
- continue
- }
-
- if (releaseNoteFlag) {
- if (language.matches(line)) {
- languageFlag = true
- continue
- }
- }
-
- if (languageFlag) {
- if (end.matches(line))
- break
-
- result.append(line+"\n")
- }
- }
-
- return getString(R.string.update_release_note, update["tag_name"]?.content, result.toString())
- }
-
- CoroutineScope(Dispatchers.Default).launch {
- val update =
- checkUpdate(getString(R.string.release_url)) ?: return@launch
-
- val (url, fileName) = getApkUrl(update) ?: return@launch
- fileName ?: return@launch
-
- val dialog = AlertDialog.Builder(this@MainActivity).apply {
- setTitle(R.string.update_title)
- val msg = extractReleaseNote(update, Locale.getDefault().language)
- setMessage(Markwon.create(context).toMarkdown(msg))
- setPositiveButton(android.R.string.yes) { _, _ ->
- if (!this@MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- AlertDialog.Builder(this@MainActivity).apply {
- setTitle(R.string.warning)
- setMessage(R.string.update_no_permission)
- setPositiveButton(android.R.string.ok) { _, _ -> }
- }.show()
-
- return@setPositiveButton
- }
-
- val request = DownloadManager.Request(Uri.parse(url)).apply {
- setDescription(getString(R.string.update_notification_description))
- setTitle(getString(R.string.app_name))
- setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
- }
-
- val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
- val id = manager.enqueue(request)
-
- registerReceiver(object: BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- try {
- val install = Intent(Intent.ACTION_VIEW).apply {
- flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
- setDataAndType(manager.getUriForDownloadedFile(id), manager.getMimeTypeForDownloadedFile(id))
- }
-
- startActivity(install)
- unregisterReceiver(this)
- } catch (e: Exception) {
- AlertDialog.Builder(this@MainActivity).apply {
- setTitle(R.string.update_failed)
- setMessage(R.string.update_failed_message)
- setPositiveButton(android.R.string.ok) { _, _ -> }
- }.show()
- }
- }
- }, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
- }
- setNegativeButton(R.string.ignore_update) { _, _ ->
- preferences.edit()
- .putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
- .apply()
- }
- }
-
- launch(Dispatchers.Main) {
- dialog.show()
- }
- }
- }
-
- private fun checkPermissions() {
- if (!hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE))
- ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 13489)
- }
-
private fun initView() {
var prevP1 = 0
main_appbar_layout.addOnOffsetChangedListener(
@@ -613,7 +483,10 @@ class MainActivity : AppCompatActivity() {
val galleryID = galleries[position].first.id
- GalleryDialog(this@MainActivity, galleryID).apply {
+ GalleryDialog(
+ this@MainActivity,
+ galleryID
+ ).apply {
onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
index 938228b0..5aabc92d 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
@@ -36,6 +36,7 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics
import com.google.android.material.snackbar.Snackbar
+import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.*
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
@@ -95,7 +96,8 @@ class ReaderActivity : AppCompatActivity() {
handleIntent(intent)
- Crashlytics.setInt("GalleryID", galleryID)
+ if (Fabric.isInitialized())
+ Crashlytics.setInt("GalleryID", galleryID)
if (galleryID == 0) {
onBackPressed()
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
index 60f9a724..61b0fe96 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt
@@ -44,9 +44,8 @@ import kotlinx.serialization.parseList
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tags
-import xyz.quaver.pupil.util.Lock
-import xyz.quaver.pupil.util.LockManager
-import xyz.quaver.pupil.util.getDownloadDirectory
+import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog
+import xyz.quaver.pupil.util.*
import java.io.File
import java.nio.charset.Charset
import java.util.*
@@ -85,14 +84,6 @@ class SettingsActivity : AppCompatActivity() {
class SettingsFragment : PreferenceFragmentCompat() {
- private val suffix = listOf(
- "B",
- "kB",
- "MB",
- "GB",
- "TB" //really?
- )
-
override fun onResume() {
super.onResume()
@@ -112,15 +103,9 @@ class SettingsActivity : AppCompatActivity() {
}
private fun getDirSize(dir: File) : String {
- var size = dir.walk().map { it.length() }.sum()
- var suffixIndex = 0
+ val size = dir.walk().map { it.length() }.sum()
- while (size >= 1024) {
- size /= 1024
- suffixIndex++
- }
-
- return getString(R.string.settings_clear_summary, size, suffix[suffixIndex])
+ return getString(R.string.settings_clear_summary, byteToString(size))
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -132,7 +117,13 @@ class SettingsActivity : AppCompatActivity() {
val manager = context.packageManager
val info = manager.getPackageInfo(context.packageName, 0)
- summary = info.versionName
+ summary = context.getString(R.string.settings_app_version_description, info.versionName)
+
+ setOnPreferenceClickListener {
+ checkUpdate(activity as SettingsActivity, true)
+
+ true
+ }
}
with(findPreference("delete_cache")) {
@@ -208,6 +199,25 @@ class SettingsActivity : AppCompatActivity() {
}
}
+ with(findPreference("dl_location")) {
+ this!!
+
+ summary = getDownloadDirectory(context).absolutePath
+
+ onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ DownloadLocationDialog(context).apply {
+ onDownloadLocationChangedListener = { value ->
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putInt(key, value)
+ .apply()
+ summary = getDownloadDirectory(context).absolutePath
+ }
+ }.show()
+
+ true
+ }
+ }
+
with(findPreference("default_query")) {
this!!
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt
new file mode 100644
index 00000000..3fcae22a
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt
@@ -0,0 +1,78 @@
+/*
+ * 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 .
+ */
+
+package xyz.quaver.pupil.ui.dialog
+
+import android.annotation.SuppressLint
+import android.app.Dialog
+import android.content.Context
+import android.widget.LinearLayout
+import android.widget.RadioButton
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.preference.PreferenceManager
+import kotlinx.android.synthetic.main.item_dl_location.view.*
+import xyz.quaver.pupil.R
+import xyz.quaver.pupil.util.byteToString
+
+@SuppressLint("InflateParams")
+class DownloadLocationDialog(context: Context) : AlertDialog(context) {
+
+ private val preference = PreferenceManager.getDefaultSharedPreferences(context)
+ private val buttons = mutableListOf()
+ var onDownloadLocationChangedListener : ((Int) -> (Unit))? = null
+
+ init {
+ val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout
+
+ ContextCompat.getExternalFilesDirs(context, null).forEachIndexed { index, dir ->
+
+ dir ?: return@forEachIndexed
+
+ view.addView(layoutInflater.inflate(R.layout.item_dl_location, view, false).apply {
+ location_type.text = context.getString(when (index) {
+ 0 -> R.string.settings_dl_location_internal
+ else -> R.string.settings_dl_location_removable
+ })
+ location_available.text = context.getString(
+ R.string.settings_dl_location_available,
+ byteToString(dir.freeSpace)
+ )
+ setOnClickListener {
+ buttons.forEach { button ->
+ button.isChecked = false
+ }
+ button.performClick()
+ onDownloadLocationChangedListener?.invoke(index)
+ }
+ buttons.add(button)
+ })
+ }
+
+ buttons[preference.getInt("dl_location", 0)].isChecked = true
+
+ setTitle(R.string.settings_dl_location)
+
+ setView(view)
+
+ setButton(Dialog.BUTTON_POSITIVE, context.getText(android.R.string.ok)) { _, _ ->
+ dismiss()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/GalleryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
similarity index 97%
rename from app/src/main/java/xyz/quaver/pupil/ui/GalleryDialog.kt
rename to app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
index aa02bbea..8896a4f8 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/GalleryDialog.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
@@ -1,6 +1,6 @@
/*
* 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
* it under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package xyz.quaver.pupil.ui
+package xyz.quaver.pupil.ui.dialog
import android.app.Dialog
import android.content.Context
@@ -46,6 +46,7 @@ import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.adapters.ThumbnailAdapter
import xyz.quaver.pupil.types.Tag
+import xyz.quaver.pupil.ui.ReaderActivity
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.wordCapitalize
@@ -256,7 +257,10 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
(context.applicationContext as Pupil).histories.add(galleries[position].first.id)
}
.setOnItemLongClickListener { _, position, _ ->
- GalleryDialog(context, galleries[position].first.id).apply {
+ GalleryDialog(
+ context,
+ galleries[position].first.id
+ ).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
}
diff --git a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
index 07538401..2eb58b08 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
@@ -163,8 +163,6 @@ class GalleryDownloader(
}
}
- private fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
-
fun start() {
downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader!!.await() ?: return@launch
diff --git a/app/src/main/java/xyz/quaver/pupil/util/file.kt b/app/src/main/java/xyz/quaver/pupil/util/file.kt
index 84342718..dd38730b 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/file.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/file.kt
@@ -19,9 +19,10 @@
package xyz.quaver.pupil.util
import android.content.Context
-import android.os.Build
-import android.os.Environment
+import androidx.core.content.ContextCompat
+import androidx.preference.PreferenceManager
import java.io.File
+import java.net.URL
fun getCachedGallery(context: Context, galleryID: Int): File {
return File(getDownloadDirectory(context), galleryID.toString()).let {
@@ -32,10 +33,32 @@ fun getCachedGallery(context: Context, galleryID: Int): File {
}
}
-@Suppress("DEPRECATION")
fun getDownloadDirectory(context: Context): File {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
- context.getExternalFilesDir("Pupil")!!
- else
- File(Environment.getExternalStorageDirectory(), "Pupil")
+ val dlLocation = PreferenceManager.getDefaultSharedPreferences(context).getInt("dl_location", 0)
+
+ return ContextCompat.getExternalFilesDirs(context, null)[dlLocation]
+}
+
+fun URL.download(to: File, onDownloadProgress: ((Long, Long) -> Unit)? = null) {
+ to.outputStream().use { out ->
+
+ with(openConnection()) {
+ val fileSize = contentLength.toLong()
+
+ getInputStream().use {
+
+ var bytesCopied: Long = 0
+ val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
+ var bytes = it.read(buffer)
+ while (bytes >= 0) {
+ out.write(buffer, 0, bytes)
+ bytesCopied += bytes
+ onDownloadProgress?.invoke(bytesCopied, fileSize)
+ bytes = it.read(buffer)
+ }
+
+ }
+ }
+
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/util/misc.kt b/app/src/main/java/xyz/quaver/pupil/util/misc.kt
index a8b1dbad..87b38bb6 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt
@@ -20,16 +20,51 @@ package xyz.quaver.pupil.util
import android.content.Context
import android.content.pm.PackageManager
+import android.os.Build
import androidx.core.content.ContextCompat
+import java.util.*
+import kotlin.collections.ArrayList
+import kotlin.math.round
+//Android Q+ uses scoped storage thus not requiring permission
fun Context.hasPermission(permission: String) =
- ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q || ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
+@UseExperimental(ExperimentalStdlibApi::class)
fun String.wordCapitalize() : String {
val result = ArrayList()
for (word in this.split(" "))
- result.add(word.capitalize())
+ result.add(word.capitalize(Locale.getDefault()))
return result.joinToString(" ")
+}
+
+
+//https://discuss.kotlinlang.org/t/how-do-you-round-a-number-to-n-decimal-places/8843(fvasco)
+fun Double.round(decimals: Int): Double {
+ var multiplier = 1.0
+ repeat(decimals) { multiplier *= 10 }
+ return round(this * multiplier) / multiplier
+}
+
+fun byteToString(byte: Long, precision : Int = 1) : String {
+
+ val suffix = listOf(
+ "B",
+ "kB",
+ "MB",
+ "GB",
+ "TB" //really?
+ )
+ var size = byte.toDouble()
+ var suffixIndex = 0
+
+ while (size >= 1024) {
+ size /= 1024
+ suffixIndex++
+ }
+
+ return "${size.round(precision)} ${suffix[suffixIndex]}"
+
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt
index 150e958c..a206be15 100644
--- a/app/src/main/java/xyz/quaver/pupil/util/update.kt
+++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt
@@ -18,15 +18,31 @@
package xyz.quaver.pupil.util
+import android.app.PendingIntent
import android.content.Context
+import android.content.Intent
+import android.webkit.MimeTypeMap
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.content.FileProvider
+import androidx.lifecycle.Lifecycle
+import androidx.preference.PreferenceManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.internal.EnumSerializer
import kotlinx.serialization.json.*
+import ru.noties.markwon.Markwon
import xyz.quaver.availableInHiyobi
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig
+import xyz.quaver.pupil.R
import java.io.File
import java.net.URL
+import java.util.*
fun getReleases(url: String) : JsonArray {
return try {
@@ -38,18 +54,17 @@ fun getReleases(url: String) : JsonArray {
}
}
-fun checkUpdate(url: String) : JsonObject? {
+fun checkUpdate(context: Context, url: String) : JsonObject? {
val releases = getReleases(url)
if (releases.isEmpty())
return null
return releases.firstOrNull {
- if (BuildConfig.PRERELEASE) {
+ if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("beta", false))
true
- } else {
+ else
it.jsonObject["prerelease"]?.boolean == false
- }
}?.let {
if (it.jsonObject["tag_name"]?.content == BuildConfig.VERSION_NAME)
null
@@ -58,14 +73,128 @@ fun checkUpdate(url: String) : JsonObject? {
}
}
-fun getApkUrl(releases: JsonObject) : Pair? {
+fun getApkUrl(releases: JsonObject) : String? {
return releases["assets"]?.jsonArray?.firstOrNull {
Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "")
}.let {
- if (it == null)
- null
- else
- Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
+ it?.jsonObject?.get("browser_download_url")?.content
+ }
+}
+
+const val UPDATE_NOTIFICATION_ID = 384823
+fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
+
+ val preferences = PreferenceManager.getDefaultSharedPreferences(context)
+ val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0)
+
+ if (!force && ignoreUpdateUntil > System.currentTimeMillis())
+ return
+
+ fun extractReleaseNote(update: JsonObject, locale: Locale) : String {
+ val markdown = update["body"]!!.content
+
+ val target = when(locale) {
+ Locale.KOREAN -> "한국어"
+ Locale.JAPANESE -> "日本語"
+ else -> "English"
+ }
+
+ val releaseNote = Regex("^# Release Note.+$")
+ val language = Regex("^## $target$")
+ val end = Regex("^#.+$")
+
+ var releaseNoteFlag = false
+ var languageFlag = false
+
+ val result = StringBuilder()
+
+ for(line in markdown.lines()) {
+ if (releaseNote.matches(line)) {
+ releaseNoteFlag = true
+ continue
+ }
+
+ if (releaseNoteFlag) {
+ if (language.matches(line)) {
+ languageFlag = true
+ continue
+ }
+ }
+
+ if (languageFlag) {
+ if (end.matches(line))
+ break
+
+ result.append(line+"\n")
+ }
+ }
+
+ return context.getString(R.string.update_release_note, update["tag_name"]?.content, result.toString())
+ }
+
+ CoroutineScope(Dispatchers.Default).launch {
+ val update =
+ checkUpdate(context, context.getString(R.string.release_url)) ?: return@launch
+
+ val url = getApkUrl(update) ?: return@launch
+
+ val dialog = AlertDialog.Builder(context).apply {
+ setTitle(R.string.update_title)
+ val msg = extractReleaseNote(update, Locale.getDefault())
+ setMessage(Markwon.create(context).toMarkdown(msg))
+ setPositiveButton(android.R.string.yes) { _, _ ->
+
+ val notificationManager = NotificationManagerCompat.from(context)
+ val builder = NotificationCompat.Builder(context, "download").apply {
+ setContentTitle(context.getString(R.string.update_notification_description))
+ setSmallIcon(android.R.drawable.stat_sys_download)
+ priority = NotificationCompat.PRIORITY_LOW
+ }
+
+ CoroutineScope(Dispatchers.IO).launch {
+ val target = File(getDownloadDirectory(context), "Pupil.apk")
+
+ URL(url).download(target) { progress, fileSize ->
+ builder.setProgress(fileSize.toInt(), progress.toInt(), false)
+ notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
+ }
+
+ val install = Intent(Intent.ACTION_VIEW).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
+ setDataAndType(FileProvider.getUriForFile(
+ context,
+ context.applicationContext.packageName + ".fileprovider",
+ target
+ ), MimeTypeMap.getSingleton().getExtensionFromMimeType(".apk"))
+ }
+
+ builder.apply {
+ setContentIntent(PendingIntent.getActivity(context, 0, install, 0))
+ setProgress(0, 0, false)
+ setSmallIcon(android.R.drawable.stat_sys_download_done)
+ setContentTitle(context.getString(R.string.update_download_completed))
+ setContentText(context.getString(R.string.update_download_completed_description))
+ }
+
+ notificationManager.cancel(UPDATE_NOTIFICATION_ID)
+
+ if (context.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED))
+ context.startActivity(install)
+ else
+ notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
+ }
+ }
+ setNegativeButton(if (force) android.R.string.no else R.string.ignore_update) { _, _ ->
+ if (!force)
+ preferences.edit()
+ .putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
+ .apply()
+ }
+ }
+
+ launch(Dispatchers.Main) {
+ dialog.show()
+ }
}
}
@@ -81,9 +210,14 @@ fun getOldReaderGalleries(context: Context) : List {
if (!readerFile.exists())
return@let
- Json(JsonConfiguration.Stable).parseJson(readerFile.readText()).jsonObject.let { reader ->
- if (!reader.contains("code"))
- oldGallery.add(gallery)
+ try {
+ Json(JsonConfiguration.Stable).parseJson(readerFile.readText())
+ .jsonObject.let { reader ->
+ if (!reader.contains("code"))
+ oldGallery.add(gallery)
+ }
+ } catch (e: Exception) {
+ // do nothing
}
}
}
@@ -109,7 +243,8 @@ fun updateOldReaderGalleries(context: Context) {
reader["code"] = when {
(File(gallery, "images").list()?.
all { !it.endsWith("webp") } ?: return@forEach) &&
- availableInHiyobi(gallery.name.toInt()) -> json.toJson(codeSerializer, Reader.Code.HIYOBI)
+ availableInHiyobi(gallery.name.toIntOrNull() ?: return@forEach)
+ -> json.toJson(codeSerializer, Reader.Code.HIYOBI)
else -> json.toJson(codeSerializer, Reader.Code.HITOMI)
}
diff --git a/app/src/main/res/layout/activity_reader.xml b/app/src/main/res/layout/activity_reader.xml
index 3c79bab3..24cfc787 100644
--- a/app/src/main/res/layout/activity_reader.xml
+++ b/app/src/main/res/layout/activity_reader.xml
@@ -26,18 +26,11 @@
android:background="@color/dark_gray"
tools:context=".ui.ReaderActivity">
-
-
-
-
-
+ android:layout_height="match_parent"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_dl_location.xml b/app/src/main/res/layout/item_dl_location.xml
new file mode 100644
index 00000000..50335e91
--- /dev/null
+++ b/app/src/main/res/layout/item_dl_location.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_reader.xml b/app/src/main/res/layout/item_reader.xml
index 39b050a8..de2c4621 100644
--- a/app/src/main/res/layout/item_reader.xml
+++ b/app/src/main/res/layout/item_reader.xml
@@ -22,6 +22,4 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="100dp"
- android:paddingBottom="8dp"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"/>
\ No newline at end of file
+ android:paddingBottom="8dp"/>
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 3614852e..74bf6715 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -9,7 +9,7 @@
ギャラリー検索
キャッシュクリア
キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?
- サイズ: %1$d%2$s
+ サイズ: %s
デフォルトキーワード
一回にロードするギャラリー数
検索設定
@@ -69,7 +69,7 @@
ディスコード
アプリロック
アップロックの種類
- バージョン
+ バージョン(アップデート確認)
生体認識
ロック確認のためもう一回入力してください。
有効
@@ -110,4 +110,12 @@
確認
復元に失敗しました
%1$d項目を復元しました
+ ダウンロード場所
+ 内部ストレージ
+ 外部SDカード
+ %s 使用可能
+ ダウンロードが完了しました
+ ここをクリックしてアップデートを行えます
+ ベータチャンネルでアップデートを受信
+ v%s
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index e93e36cf..bbaa7d97 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -8,7 +8,7 @@
기본 검색어
캐시 정리하기
캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?
- 사용량: %1$d%2$s
+ 사용량: %s
한 번에 로드할 갤러리 수
검색 설정
설정
@@ -69,7 +69,7 @@
디스코드
앱 잠금
앱 잠금 종류
- 앱 버전
+ 앱 버전(업데이트 확인)
생체 인식
잠금 확인을 위해 한번 더 입력해주세요
사용 중
@@ -110,4 +110,12 @@
확인
복원에 실패했습니다
%1$d개 항목을 복원했습니다
+ 다운로드 위치
+ 내부 저장공간
+ 외부 SD카드
+ %s 사용 가능
+ 다운로드가 완료되었습니다
+ 여기를 클릭해서 업데이트를 진행할 수 있습니다
+ 베타 채널에서 업데이트
+ v%s
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5f695d13..d2852e5b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -76,7 +76,9 @@
Update available
Download started
- Downloading apk…
+ Download Completed
+ Click here to update
+ Downloading update…
# Release Note(v%1$s)\n%2$s
Search galleries
@@ -96,6 +98,8 @@
Type: %1$s
Language: %1$s
+
+
Loading
Go to page
Fullscreen
@@ -107,22 +111,43 @@
Help
+
+
Settings
- App version
+
+ App version(Click to check update)
+ v%s
+ Update from beta channel
+
+
+
Search Settings
Galleries per page
Default query
+
+
+
Storage
Clear cache
Deleting cache can affect image loading speed. Do you want to continue?
- Currently using %1$d%2$s
+ Currently using %s
Clear downloads
Delete all downloaded galleries.\nDo you want to continue?
Clear history
Do you want to clear histories?
%1$d histories saved
+ Download directory
+ Removable Storage
+ Internal Storage
+ %s available
+
+
+
App lock
App lock type
+
+
+
Miscellaneous
Use hiyobi.me
Load images from hiyobi.me to improve loading speed (if available)
@@ -139,6 +164,8 @@
Restore failed
%1$d entries restored
+
+
None
Pattern
PIN
@@ -150,6 +177,8 @@
Do you want to remove lock?
Lock is different from last one. Please try again.
+
+
Set default query
Language:
Filter BL
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index 244a015f..17e0b78c 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -6,6 +6,10 @@
app:title="@string/settings_app_version_title"
app:key="app_version"/>
+
+
@@ -39,6 +43,10 @@
app:title="@string/settings_clear_history"
app:key="clear_history"/>
+
+
-
-
-
-
diff --git a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
index 2aef6c6b..795f7983 100644
--- a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
+++ b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt
@@ -27,12 +27,19 @@ package xyz.quaver.pupil
*/
import org.junit.Test
+import xyz.quaver.pupil.util.download
+import java.io.File
+import java.net.URL
class ExampleUnitTest {
@Test
fun test() {
-
+ URL("https://github.om/tom5079/Pupil/releases/download/4.2-beta2-hotfix2/Pupil-v4.2-beta2-hotfix2.apk").download(
+ File(System.getenv("USERPROFILE"), "Pupil.apk")
+ ) { downloaded, fileSize ->
+ println("%.1f%%".format(downloaded*100.0/fileSize))
+ }
}
}
diff --git a/libpupil/build.gradle b/libpupil/build.gradle
index 055ae4dd..2f748f9e 100644
--- a/libpupil/build.gradle
+++ b/libpupil/build.gradle
@@ -7,7 +7,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
- implementation 'org.jsoup:jsoup:1.11.3'
+ implementation 'org.jsoup:jsoup:1.12.1'
testImplementation 'junit:junit:4.12'
}