From b3feee6d9d0f1757195bc3af7923fe39215a039f Mon Sep 17 00:00:00 2001 From: pupil Date: Wed, 25 Mar 2020 10:29:05 -0700 Subject: [PATCH] Fast scroll Added Not able to download after finished downloading to cache Fixed Trying to move files from different threads causing exceptions Fixed Import old galleries Added --- app/build.gradle | 4 +- app/release/output.json | 2 +- .../quaver/pupil/ExampleInstrumentedTest.kt | 8 + .../xyz/quaver/pupil/BroadcastReciever.kt | 10 ++ app/src/main/java/xyz/quaver/pupil/Pupil.kt | 7 + .../java/xyz/quaver/pupil/ui/MainActivity.kt | 27 +-- .../xyz/quaver/pupil/ui/SettingsActivity.kt | 37 ++++ .../quaver/pupil/ui/dialog/GalleryDialog.kt | 14 +- .../pupil/ui/fragment/SettingsFragment.kt | 34 ++++ .../java/xyz/quaver/pupil/util/ConstValues.kt | 7 + .../xyz/quaver/pupil/util/download/Cache.kt | 48 +++-- .../pupil/util/download/DownloadWorker.kt | 11 +- .../main/java/xyz/quaver/pupil/util/update.kt | 167 +++++++++++++++++- .../res/drawable-hdpi/ic_notification.png | Bin 0 -> 255 bytes .../res/drawable-mdpi/ic_notification.png | Bin 0 -> 183 bytes .../res/drawable-xhdpi/ic_notification.png | Bin 0 -> 311 bytes .../res/drawable-xxhdpi/ic_notification.png | Bin 0 -> 432 bytes .../res/drawable-xxxhdpi/ic_notification.png | Bin 0 -> 605 bytes app/src/main/res/drawable/thumb.xml | 34 ++++ app/src/main/res/drawable/thumb_drawable.xml | 27 +++ app/src/main/res/drawable/track.xml | 30 ++++ app/src/main/res/drawable/track_drawable.xml | 27 +++ .../main/res/layout/activity_main_content.xml | 6 +- app/src/main/res/layout/activity_reader.xml | 5 + app/src/main/res/values-ja/strings.xml | 6 + app/src/main/res/values-ko/strings.xml | 6 + app/src/main/res/values/strings.xml | 10 ++ app/src/main/res/xml/root_preferences.xml | 4 + .../java/xyz/quaver/pupil/ExampleUnitTest.kt | 2 - 29 files changed, 485 insertions(+), 48 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-mdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_notification.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_notification.png create mode 100644 app/src/main/res/drawable/thumb.xml create mode 100644 app/src/main/res/drawable/thumb_drawable.xml create mode 100644 app/src/main/res/drawable/track.xml create mode 100644 app/src/main/res/drawable/track_drawable.xml diff --git a/app/build.gradle b/app/build.gradle index e64656a5..0370e738 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,7 +46,7 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - buildToolsVersion = '29.0.2' + buildToolsVersion = '29.0.3' } dependencies { @@ -66,7 +66,7 @@ dependencies { implementation 'androidx.multidex:multidex:2.0.1' implementation "com.daimajia.swipelayout:library:1.2.0@aar" implementation 'com.google.android.material:material:1.2.0-alpha05' - implementation 'com.google.firebase:firebase-core:17.2.2' + implementation 'com.google.firebase:firebase-core:17.2.3' implementation 'com.google.firebase:firebase-perf:19.0.5' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.github.arimorty:floatingsearchview:2.1.1' diff --git a/app/release/output.json b/app/release/output.json index f27b570e..5c7bd789 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":50,"versionName":"4.14","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":51,"versionName":"4.15","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}] \ 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 2d73549c..d747e481 100644 --- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt @@ -28,6 +28,7 @@ import androidx.test.rule.ActivityTestRule import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith +import xyz.quaver.hitomi.getGalleryIDsFromNozomi import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.createImgList import xyz.quaver.hiyobi.getReader @@ -62,6 +63,13 @@ class ExampleInstrumentedTest { } } + @Test + fun test_nozomi() { + val nozomi = getGalleryIDsFromNozomi(null, "index", "all") + + Log.i("PUPILD", nozomi.size.toString()) + } + @Test fun test_doSearch() { val reader = getReader( 1426382) diff --git a/app/src/main/java/xyz/quaver/pupil/BroadcastReciever.kt b/app/src/main/java/xyz/quaver/pupil/BroadcastReciever.kt index e251524b..47b55176 100644 --- a/app/src/main/java/xyz/quaver/pupil/BroadcastReciever.kt +++ b/app/src/main/java/xyz/quaver/pupil/BroadcastReciever.kt @@ -30,10 +30,17 @@ import androidx.core.app.NotificationManagerCompat import androidx.core.content.FileProvider import androidx.preference.PreferenceManager import xyz.quaver.pupil.util.NOTIFICATION_ID_UPDATE +import xyz.quaver.pupil.util.cancelImport import java.io.File class BroadcastReciever : BroadcastReceiver() { + companion object { + const val ACTION_CANCEL_IMPORT = "ACTION_CANCEL_IMPORT" + + const val EXTRA_IMPORT_NOTIFICATION_ID = "EXTRA_IMPORT_NOTIFICATION_ID" + } + override fun onReceive(context: Context?, intent: Intent?) { context ?: return @@ -87,6 +94,9 @@ class BroadcastReciever : BroadcastReceiver() { notificationManager.notify(NOTIFICATION_ID_UPDATE, notification) } + ACTION_CANCEL_IMPORT -> { + cancelImport = true + } } } diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index 9ec3d664..54543259 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -89,6 +89,13 @@ class Pupil : MultiDexApplication() { enableVibration(true) lockscreenVisibility = Notification.VISIBILITY_SECRET }) + + manager.createNotificationChannel(NotificationChannel("import", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply { + description = getString(R.string.channel_update_description) + enableLights(false) + enableVibration(false) + lockscreenVisibility = Notification.VISIBILITY_SECRET + }) } AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) 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 10417457..bf97d3a3 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -414,18 +414,14 @@ class MainActivity : AppCompatActivity() { } onDownloadClickedHandler = { position -> val galleryID = galleries[position].id + val worker = DownloadWorker.getInstance(context) - if (!completeFlag.get(galleryID, false)) { - val worker = DownloadWorker.getInstance(context) + if (Cache(context).isDownloading(galleryID)) //download in progress + worker.cancel(galleryID) + else { + Cache(context).setDownloading(galleryID, true) - if (Cache(context).isDownloading(galleryID)) //download in progress - worker.cancel(galleryID) - else { - Cache(context).setDownloading(galleryID, true) - - if (!worker.queue.contains(galleryID)) - worker.queue.add(galleryID) - } + worker.queue.add(galleryID) } closeAllItems() @@ -477,6 +473,7 @@ class MainActivity : AppCompatActivity() { GalleryDialog( this@MainActivity, + Glide.with(this@MainActivity), galleryID ).apply { onChipClickedHandler.add { @@ -731,6 +728,16 @@ class MainActivity : AppCompatActivity() { favoritesFile.writeText(json.stringify(serializer, Tags(listOf()))) } + setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener { + override fun onMenuOpened() { + (this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems() + } + + override fun onMenuClosed() { + //Do Nothing + } + }) + setOnMenuItemClickListener { when(it.itemId) { R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS) 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 ebcdbb77..fecb401c 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt @@ -156,6 +156,43 @@ class SettingsActivity : AppCompatActivity() { .apply() } } + REQUEST_IMPORT_OLD_GALLERIES -> { + if (resultCode == Activity.RESULT_OK) { + data?.data?.also { uri -> + val takeFlags: Int = + intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + contentResolver.takePersistableUriPermission(uri, takeFlags) + + val file = uri.toFile(this) + + if (file?.canRead() != true) + Snackbar.make( + settings, + resources.getText(R.string.import_old_galleries_folder_not_readable), + Snackbar.LENGTH_LONG + ).show() + else + importOldGalleries(this, file) + } + } + } + REQUEST_IMPORT_OLD_GALLERIES_OLD -> { + if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { + val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!! + + if (!File(directory).canRead()) + Snackbar.make( + settings, + resources.getText(R.string.import_old_galleries_folder_not_readable), + Snackbar.LENGTH_LONG + ).show() + else { + importOldGalleries(this, File(directory)) + } + } + } else -> super.onActivityResult(requestCode, resultCode, data) } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt index e72fda2e..06c87882 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt @@ -18,6 +18,7 @@ package xyz.quaver.pupil.ui.dialog +import android.app.Activity import android.app.Dialog import android.content.Context import android.content.Intent @@ -29,7 +30,7 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import com.google.android.material.chip.Chip import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.dialog_gallery.* @@ -53,7 +54,7 @@ import xyz.quaver.pupil.util.ItemClickSupport import xyz.quaver.pupil.util.download.Cache import xyz.quaver.pupil.util.wordCapitalize -class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) { +class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) { private val languages = context.resources.getStringArray(R.array.languages).map { it.split("|").let { split -> @@ -61,8 +62,6 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte } }.toMap() - private val glide = Glide.with(context) - val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>() override fun onCreate(savedInstanceState: Bundle?) { @@ -90,7 +89,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte try { val gallery = getGallery(galleryID) - launch(Dispatchers.Main) { + gallery_cover.post { gallery_progressbar.visibility = View.GONE gallery_title.text = gallery.title gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() } @@ -112,7 +111,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte } } - Glide.with(context) + glide .load(gallery.cover) .apply { if (BuildConfig.CENSOR) @@ -226,7 +225,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte val inflater = LayoutInflater.from(context) val galleries = ArrayList() - val adapter = GalleryBlockAdapter(Glide.with(ownerActivity ?: return), galleries).apply { + val adapter = GalleryBlockAdapter(glide, galleries).apply { onChipClickedHandler.add { tag -> this@GalleryDialog.onChipClickedHandler.forEach { handler -> handler.invoke(tag) @@ -264,6 +263,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte .setOnItemLongClickListener { _, position, _ -> GalleryDialog( context, + glide, galleries[position].id ).apply { onChipClickedHandler.add { tag -> 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 3a3bcdbd..7f6b8004 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 @@ -18,17 +18,23 @@ package xyz.quaver.pupil.ui.fragment +import android.Manifest import android.content.Intent import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.google.android.material.snackbar.Snackbar +import net.rdrei.android.dirchooser.DirectoryChooserActivity +import net.rdrei.android.dirchooser.DirectoryChooserConfig import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.R import xyz.quaver.pupil.ui.LockActivity @@ -168,6 +174,31 @@ class SettingsFragment : activity?.startActivityForResult(intent, REQUEST_RESTORE) } + "old_import_galleries" -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + + if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) + ActivityCompat.requestPermissions(activity!!, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_PERMISSION_AND_SAF) + else { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { + putExtra("android.content.extra.SHOW_ADVANCED", true) + } + + activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES) + } + } else { // Can't use SAF on old Androids! + val config = DirectoryChooserConfig.builder() + .newDirectoryName("Pupil") + .allowNewDirectoryNameModification(true) + .build() + + val intent = Intent(context, DirectoryChooserActivity::class.java).apply { + putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config) + } + + activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES_OLD) + } + } else -> return false } } @@ -297,6 +328,9 @@ class SettingsFragment : "restore" -> { onPreferenceClickListener = this@SettingsFragment } + "old_import_galleries" -> { + onPreferenceClickListener = this@SettingsFragment + } } } 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 d70c8807..f1388871 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt @@ -20,9 +20,16 @@ package xyz.quaver.pupil.util import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration +import okhttp3.Dispatcher +import okhttp3.OkHttpClient +import xyz.quaver.proxy +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit const val REQUEST_LOCK = 38238 const val REQUEST_RESTORE = 16546 +const val REQUEST_IMPORT_OLD_GALLERIES = 6458 +const val REQUEST_IMPORT_OLD_GALLERIES_OLD = 5946 const val REQUEST_DOWNLOAD_FOLDER = 3874 const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425 const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900 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 14a0e7a1..97edd6f1 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 @@ -38,11 +38,16 @@ import xyz.quaver.pupil.util.json import java.io.File import java.io.FileOutputStream import java.net.URL +import java.util.concurrent.Executors import java.util.concurrent.locks.Lock import java.util.concurrent.locks.ReentrantLock class Cache(context: Context) : ContextWrapper(context) { + companion object { + private val moving = mutableListOf() + } + private val locks = SparseArray() private fun lock(galleryID: Int) { synchronized(locks) { @@ -245,27 +250,32 @@ class Cache(context: Context) : ContextWrapper(context) { } } - fun moveToDownload(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch { - val cache = getCachedGallery(galleryID).also { - if (!it.exists()) + fun moveToDownload(galleryID: Int) { + if (moving.contains(galleryID)) + return + + CoroutineScope(Dispatchers.IO).launch { + val cache = getCachedGallery(galleryID).also { + if (!it.exists()) + return@launch + } + val download = File(getDownloadDirectory(this@Cache), galleryID.toString()) + + if (download.isParentOf(cache)) return@launch + + Log.i("PUPILD", "MOVING ${cache.canonicalPath} --> ${download.canonicalPath}") + + cache.copyRecursively(download, true) { file, err -> + Log.i("PUPILD", "MOVING ERROR ${file.canonicalPath} ${err.message}") + OnErrorAction.SKIP + } + Log.i("PUPILD", "MOVED ${cache.canonicalPath}") + + Log.i("PUPILD", "DELETING ${cache.canonicalPath}") + cache.deleteRecursively() + Log.i("PUPILD", "DELETED ${cache.canonicalPath}") } - val download = File(getDownloadDirectory(this@Cache), galleryID.toString()) - - if (download.isParentOf(cache)) - return@launch - - Log.i("PUPILD", "MOVING ${cache.canonicalPath} --> ${download.canonicalPath}") - - cache.copyRecursively(download, true) { file, err -> - Log.i("PUPILD", "MOVING ERROR ${file.canonicalPath} ${err.message}") - OnErrorAction.SKIP - } - Log.i("PUPILD", "MOVED ${cache.canonicalPath}") - - Log.i("PUPILD", "DELETING ${cache.canonicalPath}") - cache.deleteRecursively() - Log.i("PUPILD", "DELETED ${cache.canonicalPath}") } fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true 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 cd957af8..eb05cc19 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 @@ -158,10 +158,11 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont .body(ProgressResponseBody(request.tag(), response.body(), progressListener)) .build() } + val client = OkHttpClient.Builder() - .addInterceptor(interceptor) .connectTimeout(0, TimeUnit.SECONDS) + .addInterceptor(interceptor) .readTimeout(0, TimeUnit.SECONDS) .dispatcher(Dispatcher(Executors.newFixedThreadPool(4))) .proxy(proxy) @@ -178,7 +179,11 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont worker[galleryID]?.cancel() } - client.dispatcher().cancelAll() + client.dispatcher().queuedCalls().filter { + it.request().tag() is Pair<*, *> + }.forEach { + it.cancel() + } progress.clear() exception.clear() @@ -411,7 +416,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont val galleryID = queue.peek() ?: continue if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading! - continue + cancel(galleryID) if (notification[galleryID] == null) initNotification(galleryID) 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 7a687ba8..f56a0540 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -18,24 +18,40 @@ package xyz.quaver.pupil.util +import android.annotation.SuppressLint import android.app.DownloadManager +import android.app.PendingIntent import android.content.Context +import android.content.Intent import android.net.Uri +import android.util.Base64 import androidx.appcompat.app.AlertDialog +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import androidx.preference.PreferenceManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.boolean import kotlinx.serialization.json.content +import okhttp3.* import ru.noties.markwon.Markwon +import xyz.quaver.hitomi.GalleryBlock +import xyz.quaver.hitomi.Reader +import xyz.quaver.hitomi.getGalleryBlock +import xyz.quaver.hitomi.getReader +import xyz.quaver.proxy +import xyz.quaver.pupil.BroadcastReciever import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.R +import xyz.quaver.pupil.util.download.Cache +import xyz.quaver.pupil.util.download.Metadata import java.io.File +import java.io.IOException import java.net.URL import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit fun getReleases(url: String) : JsonArray { return try { @@ -172,4 +188,149 @@ fun checkUpdate(context: Context, force: Boolean = false) { dialog.show() } } +} + +var cancelImport = false +@SuppressLint("RestrictedApi") +fun importOldGalleries(context: Context, folder: File) = CoroutineScope(Dispatchers.IO).launch { + val client = OkHttpClient.Builder() + .connectTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .proxy(proxy) + .build() + + val cancelIntent = Intent(context, BroadcastReciever::class.java).apply { + action = BroadcastReciever.ACTION_CANCEL_IMPORT + putExtra(BroadcastReciever.EXTRA_IMPORT_NOTIFICATION_ID, 0) + } + val pendingIntent = PendingIntent.getBroadcast(context, 0, cancelIntent, 0) + + val notificationManager = NotificationManagerCompat.from(context) + val notificationBuilder = NotificationCompat.Builder(context, "import").apply { + setContentTitle(context.getText(R.string.import_old_galleries_notification)) + setProgress(0, 0, true) + setSmallIcon(R.drawable.ic_notification) + addAction(0, context.getText(android.R.string.cancel), pendingIntent) + setOngoing(true) + } + + notificationManager.notify(0, notificationBuilder.build()) + + if (!folder.isDirectory) + return@launch + + val galleryRegex = Regex("""[0-9]+$""") + val imageRegex = Regex("""^[0-9]+\..+$""") + var size = 0 + fun setProgress(progress: Int) { + notificationBuilder.apply { + setContentText( + context.getString( + R.string.import_old_galleries_notification_text, + progress, + size + ) + ) + setProgress(size, progress, false) + } + + notificationManager.notify(0, notificationBuilder.build()) + } + + folder.listFiles { _, name -> + galleryRegex.matches(name) + }?.also { + size = it.size + setProgress(0) + }?.forEachIndexed { index, gallery -> + if (cancelImport) + return@forEachIndexed + + setProgress(index) + + val galleryID = gallery.name.toIntOrNull() ?: return@forEachIndexed + + File(getDownloadDirectory(context), galleryID.toString()).mkdirs() + + val reader = async { + kotlin.runCatching { + json.parse(Reader.serializer(), File(gallery, "reader.json").readText()) + }.getOrElse { + getReader(galleryID) + } + } + val galleryBlock = async { + kotlin.runCatching { + json.parse(GalleryBlock.serializer(), File(gallery, "galleryBlock.json").readText()) + }.getOrElse { + getGalleryBlock(galleryID) + } + } + @Suppress("NAME_SHADOWING") + val thumbnail = async thumbnail@{ + val galleryBlock = galleryBlock.await() + + Base64.encodeToString(try { + File(gallery, "thumbnail.jpg").readBytes() + } catch (e: Exception) { + val url = galleryBlock?.thumbnails?.firstOrNull() + + if (url == null) + null + else { + val request = Request.Builder().url(url).build() + + var done = false + var result: ByteArray? = null + client.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call?, e: IOException?) { + done = true + } + + override fun onResponse(call: Call?, response: Response?) { + result = response?.body()?.use { + it.bytes() + } + done = true + } + }) + + if (!done) + yield() + + result + } + } ?: return@thumbnail null, Base64.DEFAULT) + } + + Cache(context).setCachedMetadata(galleryID, + Metadata( + thumbnail.await(), + galleryBlock.await(), + reader.await() + ) + ) + + File(gallery, "images").listFiles { _, name -> + imageRegex.matches(name) + }?.forEach { + if (cancelImport) + return@forEach + + @Suppress("NAME_SHADOWING") + val index = it.nameWithoutExtension.toIntOrNull() ?: return@forEach + + Cache(context).putImage(galleryID, index, it.extension, it.inputStream()) + } + } + + notificationBuilder.apply { + setContentText(context.getText(R.string.import_old_galleries_notification_done)) + setProgress(0, 0, false) + setOngoing(false) + mActions.clear() + } + notificationManager.notify(0, notificationBuilder.build()) + + cancelImport = false } \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_notification.png b/app/src/main/res/drawable-hdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b20dfbcdba99b18b2c53b59dd8195705bce3e6 GIT binary patch literal 255 zcmV{aME{ddmEWhE9!u24*EZ}HZr1SRD%CVG=FSmW`TPw)lJb#OPFitOe!>#s^t_V zD9471^yOG}a7*}d2FvnyvF@`z5(8^H!5f^(K zdTfWp<&dl@6L$}Zys5>#Wg=gDas80Uj!|q+ttiTy`2gQz}rHT&8*hbhfeRR_J z*P*(D$7ACH#_oq&9-p)#bTSo=HZ9GVC7u+xS-|(~CC608v&|jbCi;9%@tWS__IXj0 gUh7>A2{{G^BXN7_KNgDWK$kFhy85}Sb4q9e06nHaM*si- literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_notification.png b/app/src/main/res/drawable-xhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..35d6cbc19338225375e68c52b1abb6704b092256 GIT binary patch literal 311 zcmV-70m%M|P)*ny6khB=VvG6ieg4vfGiH%6wQN^$ZT$;Ih(KNd#_@x4!aO(4b&iQ}Xw z00000U z4qRB(i~f~7`-YFQV@oNZ8TsmijY|0^niv7}iVr)59jCC5xq=ia7jPNsi}zh;V&rDo1v^cEh+6lsZk+;>RKH&OdVx1|Z&Q{&kg zf5cT(it=yf&GoaZaqiAVgR7mkg?CXAF@K?BEu1_U^kUpO#f%t*aVJ$C40<&zyfnlq z4RIO|2K9!7oEzfghQ!^cXi&Q#R*OLxgh3cYlAgElhR6Kx3$G{?X*Fn8Iqu-V%<+L4 zN3I;&^V)|OhmK6*)pLYNTwaLWNnC%4$VptFiYx)Rf)`nFbBQyu!Q~n-0000000000 axZ?voOBcEMStjlP0000U{djPaSW-r^>&VZ-XQ~#_LWDv zEF6p&%uk9+Ot>*Y>Z#aGtAl4$cW|dBy=0xVkvS=0#c!p`Ow|yk^+(SCw0mj(@wonb z^_v^#zdZ;v5(W;Ko!dFxEoIT-CzDx$V!xSxd6)=3UG!G|)ZcRVly@7K_`4Qg^xIPY z_U7;VX8#RU7jIXuQ;X`>TJHYzf3aKJdUZR|T$ioC?X&K?ovL~M+*xFi|96{N+q&+} zR%q;ZeZS6n*0$rdZ;QkhHNT#x*5-PD(teS-u@)v9b$(CzGnu0<)yytHYJ(bBV zf5LvJnR~wT6z1aN`POUi_a%APJ$w^*WYV86CnxKiJsBm{%qX2c-Is?udut+d)@AOB zI@U{8*{5IsoLXe0w`lPSk+pRzzs!%Fa(j)>HQ^dXm&yErWzM^2{$Hn7+|BkzMEHZ6 zSJaldKkjF2+&t~i#kr|RvQNt26FeMnv1RW{sm=Ob^_r#DCz%UXy6#>)GKq1$7jxb+ z0q8ZIt}$R>2B8K=3FZpk+Ac;*;f?R6 zD{TpLi0n!*Ja;2W=JZ986pa%BlXwoZbbXSQKCo>HgHJ+o*m1TEriKi{H|9t;aOU{G zzrnzl!IHq_K84Ak(YdN|#sR~t47-&1ve*p_ibEY0KC^6|UV0>{>E!FIB$FoY*-Wd{ m3sNH$(h4%=VbKVsw>*}P*m}e6!G}9yAQ4YjKbLh*2~7a8b^>q! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/thumb.xml b/app/src/main/res/drawable/thumb.xml new file mode 100644 index 00000000..ef41b168 --- /dev/null +++ b/app/src/main/res/drawable/thumb.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/thumb_drawable.xml b/app/src/main/res/drawable/thumb_drawable.xml new file mode 100644 index 00000000..d472382f --- /dev/null +++ b/app/src/main/res/drawable/thumb_drawable.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/track.xml b/app/src/main/res/drawable/track.xml new file mode 100644 index 00000000..0cad74f5 --- /dev/null +++ b/app/src/main/res/drawable/track.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/track_drawable.xml b/app/src/main/res/drawable/track_drawable.xml new file mode 100644 index 00000000..12b80220 --- /dev/null +++ b/app/src/main/res/drawable/track_drawable.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_content.xml b/app/src/main/res/layout/activity_main_content.xml index aa8350ee..3bb527b7 100644 --- a/app/src/main/res/layout/activity_main_content.xml +++ b/app/src/main/res/layout/activity_main_content.xml @@ -71,7 +71,11 @@ android:layout_height="match_parent" android:paddingTop="64dp" android:clipToPadding="false" - android:scrollbars="vertical" + app:fastScrollEnabled="true" + app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable" + app:fastScrollVerticalTrackDrawable="@drawable/track_drawable" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> すべてのダウンロードキャンセル アップデート アップデートの進行状態を表示 + インポート + インポート状態を表示 + 旧ギャラリーインポート + フォルダを読めません + 旧ギャラリーインポート中… + インポート完了 \ 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 394dbff5..fa220ba8 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -134,4 +134,10 @@ 다운로드 모두 취소 업데이트 업데이트 진행상황 표시 + 가져오기 + 가져오기 상태 표시 + 이전 버전 갤러리 가져오기 + 폴더를 읽을 수 없습니다 + 이전 버전 갤러리 가져오는 중… + 가져오기 완료 \ 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 519bc5ae..6dc09043 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,9 @@ Update Shows update progress + Import + Shows progress of import + Unable to connect to hitomi.la Lock file corrupted! Please re-install Pupil @@ -173,6 +176,7 @@ Restore favorites Restore failed %1$d entries restored + Import old galleries @@ -205,4 +209,10 @@ Wrong value server + + This folder is not readable + Importing old galleries… + %1$d/%2$d + Importing completed + diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index eb898b09..3572ada9 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -99,6 +99,10 @@ app:key="restore" app:title="@string/settings_restore_title"/> + + diff --git a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt index 5c097bd2..7cb04979 100644 --- a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt +++ b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt @@ -34,8 +34,6 @@ class ExampleUnitTest { @Test fun test() { val arr = SparseArray() - - print(arr.indexOfKey(34)) } }