diff --git a/app/build.gradle b/app/build.gradle index 5df53c31..89f7dced 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,13 +14,13 @@ if (file("google-services.json").exists()) { } android { - compileSdkVersion 29 + compileSdkVersion 28 defaultConfig { applicationId "xyz.quaver.pupil" minSdkVersion 16 - targetSdkVersion 29 + targetSdkVersion 28 versionCode 37 - versionName "5.3-beta4" + versionName "5.3-hotfix1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true vectorDrawables.useSupportLibrary = true @@ -60,9 +60,6 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.gridlayout:gridlayout:1.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' - implementation 'androidx.legacy:legacy-support-v4: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" @@ -72,7 +69,8 @@ dependencies { 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' - implementation 'com.github.bumptech.glide:glide:4.10.0' + implementation 'com.github.bumptech.glide:glide:4.11.0' + kapt 'com.github.bumptech.glide:compiler:4.11.0' implementation('com.github.bumptech.glide:recyclerview-integration:4.11.0') { transitive = false } @@ -81,7 +79,6 @@ dependencies { implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'com.andrognito.patternlockview:patternlockview:1.0.0' implementation "ru.noties.markwon:core:${markwonVersion}" - kapt 'com.github.bumptech.glide:compiler:4.10.0' testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:rules:1.2.0' diff --git a/app/release/output.json b/app/release/output.json index 88f6ce43..cc3bb359 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":37,"versionName":"5.3-beta4","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":37,"versionName":"5.3-hotfix1","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 680e9e3a..92c22553 100644 --- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt @@ -26,11 +26,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule import kotlinx.coroutines.runBlocking -import kotlinx.serialization.ImplicitReflectionSerializer -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonConfiguration -import kotlinx.serialization.json.JsonObject -import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import xyz.quaver.hiyobi.cookie @@ -40,9 +35,6 @@ import xyz.quaver.hiyobi.user_agent import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.util.download.Cache import xyz.quaver.pupil.util.download.DownloadWorker -import xyz.quaver.pupil.util.getDownloadDirectory -import xyz.quaver.pupil.util.updateOldReaderGalleries -import java.io.File import java.net.URL import javax.net.ssl.HttpsURLConnection @@ -58,8 +50,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - Log.i("PUPILD", getDownloadDirectory(appContext).absolutePath ?: "") - assertEquals("xyz.quaver.pupil", appContext.packageName) } @Test @@ -88,40 +78,6 @@ class ExampleInstrumentedTest { Log.d("Pupil", data.size.toString()) } - @UseExperimental(ImplicitReflectionSerializer::class) - @Test - fun test_deleteCodeFromReader() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - - val json = Json(JsonConfiguration.Stable) - - listOf( - getDownloadDirectory(context), - File(context.cacheDir, "imageCache") - ).forEach { root -> - root.listFiles()?.forEach gallery@{ gallery -> - val reader = json.parseJson(File(gallery, "reader.json").apply { - if (!exists()) - return@gallery - }.readText()) - .jsonObject.toMutableMap() - - Log.d("PUPILD", gallery.name) - - reader.remove("code") - - File(gallery, "reader.json").writeText(JsonObject(reader).toString()) - } - } - } - - @Test - fun test_updateOldReader() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - - updateOldReaderGalleries(context) - } - @Test fun test_downloadWorker() { val context = InstrumentationRegistry.getInstrumentation().targetContext diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 09fe339b..d445644f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,8 +7,7 @@ + android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - Regex("^[0-9]+.+\$").matches(file.name!!) - } ?: 0 + progress = cache.listFiles()?.count { file -> + Regex("^[0-9]+.+\$").matches(file.name) + } ?: 0 - if (visibility == View.GONE) { - visibility = View.VISIBLE - max = reader.galleryInfo.size - } + if (visibility == View.GONE) { + visibility = View.VISIBLE + max = reader.galleryInfo.size + } - if (progress == max) { - if (completeFlag.get(galleryID, false)) { - with(view.galleryblock_progress_complete) { - setImageResource(R.drawable.ic_progressbar) - visibility = View.VISIBLE - } - } else { - with(view.galleryblock_progress_complete) { - setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply { - this?.start() - }) - visibility = View.VISIBLE + if (progress == max) { + if (completeFlag.get(galleryID, false)) { + with(view.galleryblock_progress_complete) { + setImageResource(R.drawable.ic_progressbar) + visibility = View.VISIBLE + } + } else { + with(view.galleryblock_progress_complete) { + setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply { + this?.start() + }) + visibility = View.VISIBLE + } + completeFlag.put(galleryID, true) } - completeFlag.put(galleryID, true) - } - } else - view.galleryblock_progress_complete.visibility = View.INVISIBLE + } else + view.galleryblock_progress_complete.visibility = View.INVISIBLE + } } } @@ -152,10 +154,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List() { - var isFullScreen = false + //region Glide.RecyclerView + inner class SizeProvider : ListPreloader.PreloadSizeProvider { + + override fun getPreloadSize(item: File, adapterPosition: Int, itemPosition: Int): IntArray? { + return Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.getOrNull(itemPosition)?.let { + arrayOf(it.width, it.height).toIntArray() + } + } + + } + + inner class ModelProvider : ListPreloader.PreloadModelProvider { + + override fun getPreloadItems(position: Int): MutableList { + return listOf(Cache(context).getImages(galleryID)?.get(position)).filterNotNullTo(mutableListOf()) + } + + override fun getPreloadRequestBuilder(item: File): RequestBuilder<*>? { + return glide + .load(item) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .error(R.drawable.image_broken_variant) + .apply { + if (BuildConfig.CENSOR) + override(5, 8) + } + } + + } + //endregion var reader: Reader? = null - private val glide = Glide.with(context) + val glide = Glide.with(context) val timer = Timer() + val sizeProvider = SizeProvider() + val modelProvider = ModelProvider() + val preloader = RecyclerViewPreloader(glide, modelProvider, sizeProvider, 10) + + var isFullScreen = false + var onItemClickListener : ((Int) -> (Unit))? = null init { @@ -92,46 +132,47 @@ class ReaderAdapter(private val context: Context, holder.view.reader_index.text = (position+1).toString() - val images = Cache(context).getImages(galleryID) - - if (images?.get(position) != null) { - glide - .load(images[position]?.uri) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(true) - .error(R.drawable.image_broken_variant) - .apply { - if (BuildConfig.CENSOR) - override(5, 8) - } - .into(holder.view.image) - } else { - val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position) - - if (progress?.isNaN() == true) { - - if (Fabric.isInitialized()) - Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position)) - - glide - .load(R.drawable.image_broken_variant) - .into(holder.view.image) - - return - } - - holder.view.reader_item_progressbar.progress = - if (progress?.isInfinite() == true) - 100 - else - progress?.roundToInt() ?: 0 - - holder.view.image.setImageDrawable(null) - + CoroutineScope(Dispatchers.IO).launch { + val images = Cache(context).getImages(galleryID) - timer.schedule(1000) { - CoroutineScope(Dispatchers.Main).launch { - notifyItemChanged(position) + launch(Dispatchers.Main) { + if (images?.get(position) != null) { + glide + .load(images[position]) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .error(R.drawable.image_broken_variant) + .apply { + if (BuildConfig.CENSOR) + override(5, 8) + } + .into(holder.view.image) + } else { + val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position) + + if (progress?.isNaN() == true) { + + if (Fabric.isInitialized()) + Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position)) + + glide + .load(R.drawable.image_broken_variant) + .into(holder.view.image) + } else { + holder.view.reader_item_progressbar.progress = + if (progress?.isInfinite() == true) + 100 + else + progress?.roundToInt() ?: 0 + + holder.view.image.setImageDrawable(null) + } + + timer.schedule(1000) { + CoroutineScope(Dispatchers.Main).launch { + notifyItemChanged(position) + } + } } } } 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 54df43d2..d5f33096 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -429,12 +429,7 @@ class MainActivity : AppCompatActivity() { CoroutineScope(Dispatchers.Default).launch { DownloadWorker.getInstance(context).cancel(galleryID) - var cache = Cache(context).getCachedGallery(galleryID) - - while (cache != null) { - cache.deleteRecursively() - cache = Cache(context).getCachedGallery(galleryID) - } + Cache(context).getCachedGallery(galleryID).deleteRecursively() histories.remove(galleryID) @@ -969,11 +964,11 @@ class MainActivity : AppCompatActivity() { } } Mode.DOWNLOAD -> { - val downloads = getDownloadDirectory(this@MainActivity).listFiles().filter { file -> - file.isDirectory && (file.name!!.toIntOrNull() != null) && file.findFile(".metadata") != null - }.map { - it.name!!.toInt() - } + val downloads = getDownloadDirectory(this@MainActivity).listFiles()?.filter { file -> + file.isDirectory && (file.name.toIntOrNull() != null) && File(file, ".metadata").exists() + }?.map { + it.name.toInt() + } ?: emptyList() when { query.isEmpty() -> downloads.apply { 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 ee2f7b3d..7af22aec 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -239,7 +239,7 @@ class ReaderActivity : AppCompatActivity() { queue.add(galleryID) } - timer.schedule(0, 1000) { + timer.schedule(1000, 1000) { if (worker.progress.indexOfKey(galleryID) < 0) //loading return@schedule @@ -296,6 +296,7 @@ class ReaderActivity : AppCompatActivity() { } } + //addOnScrollListener((adapter as ReaderAdapter).preloader) addOnScrollListener(object: RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) 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 617650d9..aa09d8a4 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt @@ -18,15 +18,15 @@ package xyz.quaver.pupil.ui +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent -import android.net.Uri +import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.view.MenuItem import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity -import androidx.documentfile.provider.DocumentFile import androidx.preference.PreferenceManager import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.settings_activity.* @@ -38,10 +38,7 @@ import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.R import xyz.quaver.pupil.ui.fragment.LockFragment import xyz.quaver.pupil.ui.fragment.SettingsFragment -import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER -import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER_OLD -import xyz.quaver.pupil.util.REQUEST_LOCK -import xyz.quaver.pupil.util.REQUEST_RESTORE +import xyz.quaver.pupil.util.* import java.io.File import java.nio.charset.Charset @@ -124,16 +121,23 @@ class SettingsActivity : AppCompatActivity() { REQUEST_DOWNLOAD_FOLDER -> { 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) + 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) - if (DocumentFile.fromTreeUri(this, uri)?.canWrite() == false) - Snackbar.make(settings, R.string.settings_dl_location_not_writable, Snackbar.LENGTH_LONG).show() + val file = uri.toFile(this) + + if (file?.canWrite() != true) + Snackbar.make( + settings, + R.string.settings_dl_location_not_writable, + Snackbar.LENGTH_LONG + ).show() else PreferenceManager.getDefaultSharedPreferences(this).edit() - .putString("dl_location", uri.toString()) + .putString("dl_location", file.canonicalPath) .apply() } } @@ -143,14 +147,33 @@ class SettingsActivity : AppCompatActivity() { val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!! if (!File(directory).canWrite()) - Snackbar.make(settings, R.string.settings_dl_location_not_writable, Snackbar.LENGTH_LONG).show() + Snackbar.make( + settings, + R.string.settings_dl_location_not_writable, + Snackbar.LENGTH_LONG + ).show() else PreferenceManager.getDefaultSharedPreferences(this).edit() - .putString("dl_location", Uri.fromFile(File(directory)).toString()) + .putString("dl_location", File(directory).canonicalPath) .apply() } } else -> super.onActivityResult(requestCode, resultCode, data) } } + + @SuppressLint("InlinedApi") + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + when (requestCode) { + REQUEST_WRITE_PERMISSION_AND_SAF -> { + if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { + putExtra("android.content.extra.SHOW_ADVANCED", true) + } + + startActivityForResult(intent, REQUEST_DOWNLOAD_FOLDER) + } + } + } + } } \ No newline at end of file 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 index d6c2b8b1..3f24f0b0 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt @@ -18,16 +18,18 @@ package xyz.quaver.pupil.ui.dialog +import android.Manifest import android.annotation.SuppressLint import android.app.Activity import android.app.Dialog import android.content.Intent -import android.net.Uri +import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.widget.LinearLayout import android.widget.RadioButton import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import kotlinx.android.synthetic.main.item_dl_location.view.* @@ -36,13 +38,15 @@ import net.rdrei.android.dirchooser.DirectoryChooserConfig import xyz.quaver.pupil.R import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER_OLD +import xyz.quaver.pupil.util.REQUEST_WRITE_PERMISSION_AND_SAF import xyz.quaver.pupil.util.byteToString +import java.io.File @SuppressLint("InflateParams") class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { private val preference = PreferenceManager.getDefaultSharedPreferences(context) - private val buttons = mutableListOf>() + private val buttons = mutableListOf>() override fun onCreate(savedInstanceState: Bundle?) { val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout @@ -67,9 +71,9 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { pair.first.isChecked = false } button.performClick() - preference.edit().putString("dl_location", Uri.fromFile(dir).toString()).apply() + preference.edit().putString("dl_location", dir.canonicalPath).apply() } - buttons.add(button to Uri.fromFile(dir)) + buttons.add(button to dir) }) } @@ -82,11 +86,16 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { button.performClick() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - putExtra("android.content.extra.SHOW_ADVANCED", true) - } - activity.startActivityForResult(intent, REQUEST_DOWNLOAD_FOLDER) + 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_DOWNLOAD_FOLDER) + } dismiss() } else { // Can't use SAF on old Androids! @@ -106,9 +115,9 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { buttons.add(button to null) }) - val pref = Uri.parse(preference.getString("dl_location", null)) + val pref = preference.getString("dl_location", null) val index = externalFilesDirs.indexOfFirst { - Uri.fromFile(it).toString() == pref.toString() + it.canonicalPath == pref } if (index < 0) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt index 98e29b6f..f3d88dad 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt @@ -24,7 +24,6 @@ import android.os.Bundle import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.ContextCompat -import androidx.documentfile.provider.DocumentFile import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat @@ -71,7 +70,7 @@ class SettingsFragment : } } - private fun getDirSize(dir: DocumentFile) : String { + private fun getDirSize(dir: File) : String { val size = dir.walk().map { it.length() }.sum() return getString(R.string.settings_clear_summary, byteToString(size)) @@ -86,7 +85,7 @@ class SettingsFragment : checkUpdate(activity as SettingsActivity, true) } "delete_cache" -> { - val dir = DocumentFile.fromFile(File(context.cacheDir, "imageCache")) + val dir = File(context.cacheDir, "imageCache") AlertDialog.Builder(context).apply { setTitle(R.string.warning) @@ -149,13 +148,8 @@ class SettingsFragment : } "backup" -> { File(ContextCompat.getDataDir(context), "favorites.json").copyTo( - context, - getDownloadDirectory(context).let { - if (it.findFile("favorites.json") != null) - it - else - it.createFile("null", "favorites.json")!! - } + File(getDownloadDirectory(context), "favorites.json"), + true ) Snackbar.make(this@SettingsFragment.listView, R.string.settings_backup_snackbar, Snackbar.LENGTH_LONG) @@ -197,7 +191,7 @@ class SettingsFragment : override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { when (key) { "dl_location" -> { - findPreference(key)?.summary = getDownloadDirectory(context!!).uri.path + findPreference(key)?.summary = getDownloadDirectory(context!!).canonicalPath } } } @@ -228,7 +222,7 @@ class SettingsFragment : onPreferenceClickListener = this@SettingsFragment } "delete_cache" -> { - val dir = DocumentFile.fromFile(File(context.cacheDir, "imageCache")) + val dir = File(context.cacheDir, "imageCache") summary = getDirSize(dir) onPreferenceClickListener = this@SettingsFragment @@ -246,7 +240,7 @@ class SettingsFragment : onPreferenceClickListener = this@SettingsFragment } "dl_location" -> { - summary = getDownloadDirectory(context).uri.path + summary = getDownloadDirectory(context).canonicalPath onPreferenceClickListener = this@SettingsFragment } diff --git a/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt b/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt index 807c1379..fecea791 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt @@ -21,4 +21,5 @@ package xyz.quaver.pupil.util const val REQUEST_LOCK = 38238 const val REQUEST_RESTORE = 16546 const val REQUEST_DOWNLOAD_FOLDER = 3874 -const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425 \ No newline at end of file +const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425 +const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900 \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/FileUtils.java b/app/src/main/java/xyz/quaver/pupil/util/FileUtils.java deleted file mode 100644 index 414da1c8..00000000 --- a/app/src/main/java/xyz/quaver/pupil/util/FileUtils.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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.util; - -/* - * Copyright (C) 2007-2008 OpenIntents.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.provider.DocumentsContract; -import android.provider.MediaStore; - -/** - * @version 2009-07-03 - * @author Peli - * @version 2013-12-11 - * @author paulburke (ipaulpro) - */ -public class FileUtils { - /** - * Get a file path from a Uri. This will get the the path for Storage Access - * Framework Documents, as well as the _data field for the MediaStore and - * other file-based ContentProviders. - * - * @param context The context. - * @param uri The Uri to query. - * @author paulburke - */ - public static String getPath(final Context context, final Uri uri) { - - // DocumentProvider - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - if ("primary".equalsIgnoreCase(type)) { - return context.getExternalFilesDir(null).getParentFile().getParentFile().getParentFile().getParent() + "/" + split[1]; - } - - // TODO handle non-primary volumes - } - // DownloadsProvider - else if (isDownloadsDocument(uri)) { - - final String id = DocumentsContract.getDocumentId(uri); - final Uri contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - - return getDataColumn(context, contentUri, null, null); - } - // MediaProvider - else if (isMediaDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = "_id=?"; - final String[] selectionArgs = new String[] { - split[1] - }; - - return getDataColumn(context, contentUri, selection, selectionArgs); - } - } - // MediaStore (and general) - else if ("content".equalsIgnoreCase(uri.getScheme())) { - return getDataColumn(context, uri, null, null); - } - // File - else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - - return null; - } - - /** - * Get the value of the data column for this Uri. This is useful for - * MediaStore Uris, and other file-based ContentProviders. - * - * @param context The context. - * @param uri The Uri to query. - * @param selection (Optional) Filter used in the query. - * @param selectionArgs (Optional) Selection arguments used in the query. - * @return The value of the _data column, which is typically a file path. - */ - public static String getDataColumn(Context context, Uri uri, String selection, - String[] selectionArgs) { - - Cursor cursor = null; - final String column = "_data"; - final String[] projection = { - column - }; - - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, - null); - if (cursor != null && cursor.moveToFirst()) { - final int column_index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(column_index); - } - } finally { - if (cursor != null) - cursor.close(); - } - return null; - } - - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - public static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - public static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - public static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } -} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt index 0224c36d..6c75b55d 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt @@ -21,7 +21,7 @@ package xyz.quaver.pupil.util.download import android.content.Context import android.content.ContextWrapper import android.util.Base64 -import androidx.documentfile.provider.DocumentFile +import android.util.Log import androidx.preference.PreferenceManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -34,7 +34,8 @@ import kotlinx.serialization.stringify import xyz.quaver.Code import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.Reader -import xyz.quaver.pupil.util.* +import xyz.quaver.pupil.util.getCachedGallery +import xyz.quaver.pupil.util.getDownloadDirectory import java.io.File import java.net.URL @@ -44,29 +45,20 @@ class Cache(context: Context) : ContextWrapper(context) { // Search in this order // Download -> Cache - fun getCachedGallery(galleryID: Int) : DocumentFile? { - var file = getDownloadDirectory(this).findFile(galleryID.toString()) - - if (file?.exists() == true) - return file - - file = DocumentFile.fromFile(File(cacheDir, "imageCache/$galleryID")) - - return if (file.exists()) - file - else - null + fun getCachedGallery(galleryID: Int) = getCachedGallery(this, galleryID).also { + if (!it.exists()) + it.mkdirs() } @UseExperimental(ImplicitReflectionSerializer::class) fun getCachedMetadata(galleryID: Int) : Metadata? { - val file = (getCachedGallery(galleryID) ?: return null).findFile(".metadata") + val file = File(getCachedGallery(galleryID), ".metadata") - if (file?.exists() != true) + if (!file.exists()) return null return try { - Json.parse(file.readText(this)) + Json.parse(file.readText()) } catch (e: Exception) { //File corrupted file.delete() @@ -76,13 +68,12 @@ class Cache(context: Context) : ContextWrapper(context) { @UseExperimental(ImplicitReflectionSerializer::class) fun setCachedMetadata(galleryID: Int, metadata: Metadata) { - val file = getCachedGallery(galleryID)?.findFile(".metadata") ?: - DocumentFile.fromFile(File(cacheDir, "imageCache/$galleryID").also { + val file = File(getCachedGallery(galleryID), ".metadata").also { if (!it.exists()) - it.mkdirs() - }).createFile("null", ".metadata") ?: return + it.createNewFile() + } - file.writeText(this, Json.stringify(metadata)) + file.writeText(Json.stringify(metadata)) } suspend fun getThumbnail(galleryID: Int): String? { @@ -186,46 +177,39 @@ class Cache(context: Context) : ContextWrapper(context) { return reader } - fun getImages(galleryID: Int): List? { - val gallery = getCachedGallery(galleryID) ?: return null + fun getImages(galleryID: Int): List? { + val started = System.currentTimeMillis() + val gallery = getCachedGallery(galleryID) val reader = getReaderOrNull(galleryID) ?: return null - val images = gallery.listFiles() + val images = gallery.listFiles() ?: return null + Log.i("PUPILD", "${System.currentTimeMillis() - started} ms") return reader.galleryInfo.indices.map { index -> - images.firstOrNull { file -> file.name?.startsWith("%05d".format(index)) == true } + images.firstOrNull { file -> file.name.startsWith("%05d".format(index)) } } } fun putImage(galleryID: Int, name: String, data: ByteArray) { - val cache = getCachedGallery(galleryID) ?: - DocumentFile.fromFile(File(cacheDir, "imageCache/$galleryID").also { + val cache = File(getCachedGallery(galleryID), name).also { if (!it.exists()) - it.mkdirs() - }) ?: return + it.createNewFile() + } if (!Regex("""^[0-9]+.+$""").matches(name)) throw IllegalArgumentException("File name is not a number") - cache.let { - if (it.findFile(name) != null) - it - else - it.createFile("null", name) - }?.writeBytes(this, data) + cache.writeBytes(data) } fun moveToDownload(galleryID: Int) { - val cache = getCachedGallery(galleryID) - - if (cache != null) { - val download = getDownloadDirectory(this) + val cache = getCachedGallery(galleryID).also { + if (!it.exists()) + return + } + val download = File(getDownloadDirectory(this), galleryID.toString()) - if (!download.isParentOf(cache)) { - cache.copyRecursively(this, download) - cache.deleteRecursively() - } - } else - getDownloadDirectory(this).createDirectory(galleryID.toString()) + cache.copyRecursively(download, true) + cache.deleteRecursively() } fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt index 647571b3..aa5dda63 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt @@ -214,28 +214,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont fun isCompleted(galleryID: Int) = progress[galleryID]?.all { !it.isFinite() } == true private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) { - val cache = Cache(this@DownloadWorker).getImages(galleryID) val lowQuality = preferences.getBoolean("low_quality", false) - //Cache exists :P - cache?.get(index)?.let { - progress[galleryID]?.set(index, Float.POSITIVE_INFINITY) - - notify(galleryID) - - if (isCompleted(galleryID)) { - with(Cache(this@DownloadWorker)) { - if (isDownloading(galleryID)) { - moveToDownload(galleryID) - setDownloading(galleryID, false) - } - } - nRunners-- - } - - return - } - val request = Request.Builder().apply { when (reader.code) { Code.HITOMI -> { @@ -276,7 +256,14 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont return@launch } - progress.put(galleryID, reader.galleryInfo.map { 0F }.toMutableList()) + val cache = Cache(this@DownloadWorker).getImages(galleryID) + + progress.put(galleryID, reader.galleryInfo.indices.map { index -> + if (cache?.get(index) != null) + Float.POSITIVE_INFINITY + else + 0F + }.toMutableList()) exception.put(galleryID, reader.galleryInfo.map { null }.toMutableList()) if (notification[galleryID] == null) @@ -285,6 +272,18 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont notification[galleryID].setContentTitle(reader.title) notify(galleryID) + if (isCompleted(galleryID)) { + with(Cache(this@DownloadWorker)) { + if (isDownloading(galleryID)) { + moveToDownload(galleryID) + setDownloading(galleryID, false) + } + } + nRunners-- + + return@launch + } + for (i in reader.galleryInfo.indices) { val callback = object : Callback { override fun onFailure(call: Call, e: IOException) { @@ -297,10 +296,11 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont notify(galleryID) if (isCompleted(galleryID)) { - val cache = Cache(this@DownloadWorker) - if (cache.isDownloading(galleryID)) { - cache.moveToDownload(galleryID) - cache.setDownloading(galleryID, false) + with(Cache(this@DownloadWorker)) { + if (isDownloading(galleryID)) { + moveToDownload(galleryID) + setDownloading(galleryID, false) + } } nRunners-- } @@ -319,17 +319,19 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont notify(galleryID) if (isCompleted(galleryID)) { - val cache = Cache(this@DownloadWorker) - if (cache.isDownloading(galleryID)) { - cache.moveToDownload(galleryID) - cache.setDownloading(galleryID, false) + with(Cache(this@DownloadWorker)) { + if (isDownloading(galleryID)) { + moveToDownload(galleryID) + setDownloading(galleryID, false) + } } nRunners-- } } } - queueDownload(galleryID, reader, i, callback) + if (progress[galleryID]?.get(i)?.isFinite() == true) + queueDownload(galleryID, reader, i, callback) } } 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 a27629c0..8a25c4b8 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/file.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/file.kt @@ -18,43 +18,45 @@ package xyz.quaver.pupil.util +import android.annotation.TargetApi import android.content.Context import android.net.Uri -import androidx.core.content.FileProvider -import androidx.documentfile.provider.DocumentFile +import android.os.Build +import android.os.storage.StorageManager +import android.provider.DocumentsContract +import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import java.io.File +import java.io.FileOutputStream +import java.lang.reflect.Array import java.net.URL -import java.nio.charset.Charset -import java.util.* + fun getCachedGallery(context: Context, galleryID: Int) = - getDownloadDirectory(context).findFile(galleryID.toString()) ?: - DocumentFile.fromFile(File(context.cacheDir, "imageCache/$galleryID")) + File(getDownloadDirectory(context), galleryID.toString()).let { + if (it.exists()) + it + else + File(context.cacheDir, "imageCache/$galleryID") + } -fun getDownloadDirectory(context: Context) : DocumentFile { - val uri = PreferenceManager.getDefaultSharedPreferences(context).getString("dl_location", null).let { - if (it != null) - Uri.parse(it) +fun getDownloadDirectory(context: Context) = + PreferenceManager.getDefaultSharedPreferences(context).getString("dl_location", null).let { + if (it != null && !it.startsWith("content")) + File(it) else - Uri.fromFile(context.getExternalFilesDir(null)) + context.getExternalFilesDir(null)!! } - return if (uri.toString().startsWith("file")) - DocumentFile.fromFile(File(uri.path!!)) - else - DocumentFile.fromTreeUri(context, uri) ?: DocumentFile.fromFile(context.getExternalFilesDir(null)!!) -} +fun URL.download(to: File, onDownloadProgress: ((Long, Long) -> Unit)? = null) { + + if (to.parentFile?.exists() == false) + to.parentFile!!.mkdirs() -fun convertUpdateUri(context: Context, uri: Uri) : Uri = - if (uri.toString().startsWith("file")) - FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", File(uri.path!!.substringAfter("file:///"))) - else - uri + if (!to.exists()) + to.createNewFile() -fun URL.download(context: Context, to: DocumentFile, onDownloadProgress: ((Long, Long) -> Unit)? = null) { - context.contentResolver.openOutputStream(to.uri).use { out -> - out!! + FileOutputStream(to).use { out -> with(openConnection()) { val fileSize = contentLength.toLong() @@ -78,74 +80,135 @@ fun URL.download(context: Context, to: DocumentFile, onDownloadProgress: ((Long, } } -fun DocumentFile.isParentOf(file: DocumentFile?) : Boolean { - var parent = file?.parentFile - while (parent != null) { - if (this.uri.path == parent.uri.path) - return true - - parent = parent.parentFile +fun getExtSdCardPaths(context: Context) = + ContextCompat.getExternalFilesDirs(context, null).drop(1).map { + it.absolutePath.substringBeforeLast("/Android/data").let { path -> + runCatching { + File(path).canonicalPath + }.getOrElse { + path + } + } } - return false -} +const val PRIMARY_VOLUME_NAME = "primary" +fun getVolumePath(context: Context, volumeID: String?): String? { + return runCatching { + val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager + val storageVolumeClass = Class.forName("android.os.storage.StorageVolume") -fun DocumentFile.reader(context: Context, charset: Charset = Charsets.UTF_8) = context.contentResolver.openInputStream(uri)!!.reader(charset) -fun DocumentFile.readBytes(context: Context) = context.contentResolver.openInputStream(uri)!!.readBytes() -fun DocumentFile.readText(context: Context, charset: Charset = Charsets.UTF_8) = reader(context, charset).use { it.readText() } - -fun DocumentFile.writeBytes(context: Context, array: ByteArray) = context.contentResolver.openOutputStream(uri)!!.write(array) -fun DocumentFile.writeText(context: Context, text: String, charset: Charset = Charsets.UTF_8) = writeBytes(context, text.toByteArray(charset)) - -fun DocumentFile.copyRecursively( - context: Context, - target: DocumentFile -) { - if (!exists()) - throw Exception("The source file doesn't exist.") - - if (this.isFile) { - target.let { - if (it.findFile(name!!) != null) - it - else - createFile("null", name!!)!! - }.writeBytes( - context, - readBytes(context) - ) - } else if (this.isDirectory) { - target.createDirectory(name!!).also { newTarget -> - listFiles().forEach { child -> - child.copyRecursively(context, newTarget!!) + val getVolumeList = storageVolumeClass.javaClass.getMethod("getVolumeList") + val getUUID = storageVolumeClass.getMethod("getUuid") + val getPath = storageVolumeClass.getMethod("getPath") + val isPrimary = storageVolumeClass.getMethod("isPrimary") + + val result = getVolumeList.invoke(storageManager)!! + + val length = Array.getLength(result) + + for (i in 0 until length) { + val storageVolumeElement = Array.get(result, i) + val uuid = getUUID.invoke(storageVolumeElement) as? String + val primary = isPrimary.invoke(storageVolumeElement) as? Boolean + + // primary volume? + if (primary == true && volumeID == PRIMARY_VOLUME_NAME) + return@runCatching getPath.invoke(storageVolumeElement) as? String + + // other volumes? + if (volumeID == uuid) { + return@runCatching getPath.invoke(storageVolumeElement) as? String } } - } + return@runCatching null + }.getOrNull() } -fun DocumentFile.deleteRecursively() { - - if (this.isDirectory) - listFiles().forEach { - it.deleteRecursively() - } +// Credits go to https://stackoverflow.com/questions/34927748/android-5-0-documentfile-from-tree-uri/36162691#36162691 +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +fun getVolumeIdFromTreeUri(uri: Uri) = + DocumentsContract.getTreeDocumentId(uri).split(':').let { + if (it.isNotEmpty()) + it[0] + else + null + } - this.delete() -} +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +fun getDocumentPathFromTreeUri(uri: Uri) = + DocumentsContract.getTreeDocumentId(uri).split(':').let { + if (it.size >= 2) + it[1] + else + File.separator + } -fun DocumentFile.walk(state: LinkedList = LinkedList()) : Queue { - if (state.isEmpty()) - state.push(this) +fun getFullPathFromTreeUri(context: Context, uri: Uri) : String? { + val volumePath = getVolumePath(context, getVolumeIdFromTreeUri(uri) ?: return null).let { + it ?: return File.separator - listFiles().forEach { - state.push(it) + if (it.endsWith(File.separator)) + it.dropLast(1) + else + it + } - if (it.isDirectory) { - it.walk(state) - } + val documentPath = getDocumentPathFromTreeUri(uri).let { + if (it.endsWith(File.separator)) + it.dropLast(1) + else + it } - return state + return if (documentPath.isNotEmpty()) { + if (documentPath.startsWith(File.separator)) + volumePath + documentPath + else + volumePath + File.separator + documentPath + } else + volumePath } -fun File.copyTo(context: Context, target: DocumentFile) = target.writeBytes(context, this.readBytes()) \ No newline at end of file +// Huge thanks to avluis(https://github.com/avluis) +// This code is originated from Hentoid(https://github.com/avluis/Hentoid) under Apache-2.0 license. +fun Uri.toFile(context: Context): File? { + val path = this.path ?: return null + + val pathSeparator = path.indexOf(':') + val folderName = path.substring(pathSeparator+1) + + // Determine whether the designated file is + // - on a removable media (e.g. SD card, OTG) + // or + // - on the internal phone memory + val removableMediaFolderRoots = getExtSdCardPaths(context) + + /* First test is to compare root names with known roots of removable media + * In many cases, the SD card root name is shared between pre-SAF (File) and SAF (DocumentFile) frameworks + * (e.g. /storage/3437-3934 vs. /tree/3437-3934) + * This is what the following block is trying to do + */ + for (s in removableMediaFolderRoots) { + val sRoot = s.substring(s.lastIndexOf(File.separatorChar)) + val root = path.substring(0, pathSeparator).let { + it.substring(it.lastIndexOf(File.separatorChar)) + } + + if (sRoot.equals(root, true)) { + return File(s + File.separatorChar + folderName) + } + } + /* In some other cases, there is no common name (e.g. /storage/sdcard1 vs. /tree/3437-3934) + * We can use a slower method to translate the Uri obtained with SAF into a pre-SAF path + * and compare it to the known removable media volume names + */ + val root = getFullPathFromTreeUri(context, this) + + for (s in removableMediaFolderRoots) { + if (root?.startsWith(s) == true) { + return File(root) + } + } + + return File(context.getExternalFilesDir(null)?.canonicalPath?.substringBeforeLast("/Android/data") ?: return null, folderName) +} \ 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 51847f41..1d0c9091 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -26,6 +26,7 @@ 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 @@ -35,6 +36,7 @@ import kotlinx.serialization.json.* import ru.noties.markwon.Markwon import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.R +import java.io.File import java.net.URL import java.util.* @@ -146,15 +148,10 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) { } CoroutineScope(Dispatchers.IO).launch io@{ - val target = getDownloadDirectory(context).let { - if (it.findFile("Pupil.apk") != null) - it - else - it.createFile("null", "Pupil.apk")!! - } + val target = File(getDownloadDirectory(context), "Pupil.apk") try { - URL(url).download(context, target) { progress, fileSize -> + URL(url).download(target) { progress, fileSize -> builder.setProgress(fileSize.toInt(), progress.toInt(), false) notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build()) } @@ -173,7 +170,7 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) { val install = Intent(Intent.ACTION_VIEW).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION - setDataAndType(convertUpdateUri(context, target.uri), MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk")) + setDataAndType(FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", target), MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk")) } builder.apply {