diff --git a/.idea/misc.xml b/.idea/misc.xml index 39b64357..7bfef59d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + 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 d4cf3be9..4db90b6f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,9 +4,6 @@ package="xyz.quaver.pupil"> - 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/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..291c7753 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt @@ -44,8 +44,10 @@ import kotlinx.serialization.parseList import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.R import xyz.quaver.pupil.types.Tags +import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog import xyz.quaver.pupil.util.Lock import xyz.quaver.pupil.util.LockManager +import xyz.quaver.pupil.util.byteToString import xyz.quaver.pupil.util.getDownloadDirectory import java.io.File import java.nio.charset.Charset @@ -85,14 +87,6 @@ class SettingsActivity : AppCompatActivity() { class SettingsFragment : PreferenceFragmentCompat() { - private val suffix = listOf( - "B", - "kB", - "MB", - "GB", - "TB" //really? - ) - override fun onResume() { super.onResume() @@ -112,15 +106,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?) { @@ -208,6 +196,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 05deb0fc..87b38bb6 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -24,6 +24,7 @@ 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) = @@ -37,4 +38,33 @@ fun String.wordCapitalize() : String { 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 c42c13d8..13266ae6 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 { @@ -58,14 +74,127 @@ 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 + } +} + +val UPDATE_NOTIFICATION_ID = 384823 +fun checkUpdate(context: AppCompatActivity) { + + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0) + + if (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.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(R.string.ignore_update) { _, _ -> + preferences.edit() + .putLong("ignore_update_until", System.currentTimeMillis() + 604800000) + .apply() + } + } + + launch(Dispatchers.Main) { + dialog.show() + } } } diff --git a/app/src/main/res/layout/dialog_dl_location.xml b/app/src/main/res/layout/dialog_dl_location.xml new file mode 100644 index 00000000..fde6c7d9 --- /dev/null +++ b/app/src/main/res/layout/dialog_dl_location.xml @@ -0,0 +1,25 @@ + + + + \ 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/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 3614852e..625fcd2b 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 デフォルトキーワード 一回にロードするギャラリー数 検索設定 @@ -110,4 +110,10 @@ 確認 復元に失敗しました %1$d項目を復元しました + ダウンロード場所 + 内部ストレージ + 外部SDカード + %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..5c2ecdde 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 한 번에 로드할 갤러리 수 검색 설정 설정 @@ -110,4 +110,10 @@ 확인 복원에 실패했습니다 %1$d개 항목을 복원했습니다 + 다운로드 위치 + 내부 저장공간 + 외부 SD카드 + %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..afd7d1fd 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,38 @@ Help + + Settings + App version 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 +159,8 @@ Restore failed %1$d entries restored + + None Pattern PIN @@ -150,6 +172,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..4cf82f4e 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -39,6 +39,10 @@ app:title="@string/settings_clear_history" app:key="clear_history"/> + + + println("%.1f%%".format(downloaded*100.0/fileSize)) + } } }