From 90f0846fb453af8d5553f07e1c5428420dba034e Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 27 Oct 2024 16:28:11 +0200 Subject: [PATCH] Small fixes --- .../koitharu/kotatsu/StrictModeNotifier.kt | 2 +- .../debug/res/drawable-anydpi-v24/ic_bug.xml | 15 +++ app/src/debug/res/drawable-hdpi/ic_bug.png | Bin 0 -> 417 bytes app/src/debug/res/drawable-mdpi/ic_bug.png | Bin 0 -> 308 bytes app/src/debug/res/drawable-xhdpi/ic_bug.png | Bin 0 -> 480 bytes app/src/debug/res/drawable-xxhdpi/ic_bug.png | Bin 0 -> 792 bytes .../browser/cloudflare/CaptchaNotifier.kt | 6 +- .../external/ExternalMangaRepository.kt | 2 +- .../external/ExternalPluginContentSource.kt | 1 + .../kotatsu/core/util/ext/Throwable.kt | 15 ++- .../koitharu/kotatsu/core/zip/ZipOutput.kt | 94 ++++++++++-------- .../kotatsu/local/ui/LocalListFragment.kt | 2 +- .../kotatsu/local/ui/LocalListMenuProvider.kt | 8 ++ app/src/main/res/menu/opt_local.xml | 8 +- app/src/main/res/values/strings.xml | 1 + 15 files changed, 103 insertions(+), 51 deletions(-) create mode 100644 app/src/debug/res/drawable-anydpi-v24/ic_bug.xml create mode 100644 app/src/debug/res/drawable-hdpi/ic_bug.png create mode 100644 app/src/debug/res/drawable-mdpi/ic_bug.png create mode 100644 app/src/debug/res/drawable-xhdpi/ic_bug.png create mode 100644 app/src/debug/res/drawable-xxhdpi/ic_bug.png diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt index 8847cb601..0fccd4e06 100644 --- a/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt +++ b/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt @@ -42,7 +42,7 @@ class StrictModeNotifier( override fun onViolation(violation: FragmentViolation) = showNotification(violation) private fun showNotification(violation: Throwable) = Notification.Builder(context, CHANNEL_ID) - .setSmallIcon(android.R.drawable.stat_notify_error) + .setSmallIcon(R.drawable.ic_bug) .setContentTitle(context.getString(R.string.strict_mode)) .setContentText(violation.message) .setStyle( diff --git a/app/src/debug/res/drawable-anydpi-v24/ic_bug.xml b/app/src/debug/res/drawable-anydpi-v24/ic_bug.xml new file mode 100644 index 000000000..dc7fd8955 --- /dev/null +++ b/app/src/debug/res/drawable-anydpi-v24/ic_bug.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/debug/res/drawable-hdpi/ic_bug.png b/app/src/debug/res/drawable-hdpi/ic_bug.png new file mode 100644 index 0000000000000000000000000000000000000000..b4aa5f3c7668c6ccde4029bfff1698e52e64b757 GIT binary patch literal 417 zcmV;S0bc%zP)5bKg|3bQeVG^1yvlkp(=^9&}`N8|G!WIV+EQ8PE5*FA4;_ zhS?+7=U|`ZjeRqNuy;;3u+PCh%Ln_Sknolb!t4zH=J?9PWTabGmYEK;4sBU=!@LYP zXXRo36x@7+`=St?ms;!MVk#A2mJ}00000 LNkvXXu0mjfv5~q4 literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-mdpi/ic_bug.png b/app/src/debug/res/drawable-mdpi/ic_bug.png new file mode 100644 index 0000000000000000000000000000000000000000..726da962cd9619a328dee868d1107adb60a94e56 GIT binary patch literal 308 zcmV-40n7f0P)(Nj9bU!89{(rqGZ? z(a&RyX|X3DYqJ*w1}4x8N(!>XW@FB3gYw4Y%%FkQRQYS}1%ZK;8e^7xAB4$&A|Gis zH(wxQTJuf)E>xF_`bLHF%j<;4%8WCTlNpC+s z?>SI07~XTh&D@F+zbJo*&wE}>*ne>2+zXEPR{Ru+FVdXE=RF512E%&}xLGLGCF}5B zDJb+!^}DG0^-u-|y3mn^Qw~sv?;C@$7#f-H_aNr{Ph;L-@i~8nIl(pn0000sa42*cGuk3hHNr7 zDJrnbMkUx`-dIqP4x0_Z19Ot25=++m%8ZITyU5&RSnp>rlrD3Vpurbx62Lk0qoMFC zPD{uo`;^A=0zu>jexcANr})rM=m#hCM@K{MIn{@LLO-~rKRO!n#Hl{?6Z%1i{^;1G zEnt)W5L;d#h$suRrB6?T@V)&s2;bXgpI~g#9%5osu)rOs`p{442TA_OjyTnaenLMu zq(2y&f?uJ$Kmbt|a4)8w`zSWdcWSXuG!%a3PYUh&=_vz!?MW$QLkUW zL+ihxO5x$3PFve@KK+VgK0Mvhk(Iq-K7XP7YJuq$@j!K!ehxVw<>WuTpY!;!(tDtB z_UAo0{z}+AZf2e5eE)|+Vc+jGR;HNeE`l>3*F`odh+9Omr0w+Irf|mDPrUK>9d+v? ze|N_}P7(ac(p!8#N8o>{w@=v4e6a&Rg8e2m(DIxe~zW!l{OJPW{?n7A2&JOj{ar zSUvVO%hyAuA>kJO*&d&>=kJX<+`jJVS;oBL-gAvBRm&t5o;HZ>bbEf_iZz&huK*-2 z3Sk4)2ue?2KD&J3Wzno(SC2_*UjN{E-OO=s0q<24pv^Z*18i@#>`rUp)o1`C2W@66 zCfU^&o;OVlv`~C1W%u%y+KbD4>lRv_sf==dW3@tVBjZ)(;QX7Z4O5t3J>7dlXgAM< zB8Rw!Mk literal 0 HcmV?d00001 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt index 6df1955f2..33439e8b0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt @@ -29,7 +29,7 @@ class CaptchaNotifier( return } val manager = NotificationManagerCompat.from(context) - val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT) + val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW) .setName(context.getString(R.string.captcha_required)) .setShowBadge(true) .setVibrationEnabled(false) @@ -42,8 +42,8 @@ class CaptchaNotifier( .setData(exception.url.toUri()) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle(channel.name) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setDefaults(NotificationCompat.DEFAULT_SOUND) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setDefaults(0) .setSmallIcon(android.R.drawable.stat_notify_error) .setGroup(GROUP_CAPTCHA) .setAutoCancel(true) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalMangaRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalMangaRepository.kt index 05083c982..dece02536 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalMangaRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalMangaRepository.kt @@ -42,7 +42,7 @@ class ExternalMangaRepository( override var defaultSortOrder: SortOrder get() = capabilities?.availableSortOrders?.firstOrNull() ?: SortOrder.ALPHABETICAL - set(value) = Unit + set(_) = Unit override suspend fun getFilterOptions(): MangaListFilterOptions = filterOptions.get() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt index bc7280336..a0ca968e2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/external/ExternalPluginContentSource.kt @@ -8,6 +8,7 @@ import androidx.core.net.toUri import org.jetbrains.annotations.Blocking import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty +import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.model.ContentRating import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.Demographic diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt index d6d389718..8f06f8487 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt @@ -42,7 +42,10 @@ import java.net.UnknownHostException private const val MSG_NO_SPACE_LEFT = "No space left on device" private const val IMAGE_FORMAT_NOT_SUPPORTED = "Image format not supported" -fun Throwable.getDisplayMessage(resources: Resources): String = when (this) { +fun Throwable.getDisplayMessage(resources: Resources): String = getDisplayMessageOrNull(resources) + ?: resources.getString(R.string.error_occurred) + +private fun Throwable.getDisplayMessageOrNull(resources: Resources): String? = when (this) { is ScrobblerAuthRequiredException -> resources.getString( R.string.scrobbler_auth_required, resources.getString(scrobbler.titleResId), @@ -88,7 +91,11 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) { ) is NoDataReceivedException -> resources.getString(R.string.error_no_data_received) - is IncompatiblePluginException -> resources.getString(R.string.plugin_incompatible) + is IncompatiblePluginException -> { + cause?.getDisplayMessageOrNull(resources)?.let { + resources.getString(R.string.plugin_incompatible_with_cause, it) + } ?: resources.getString(R.string.plugin_incompatible) + } is WrongPasswordException -> resources.getString(R.string.wrong_password) is NotFoundException -> resources.getString(R.string.not_found_404) is UnsupportedSourceException -> resources.getString(R.string.unsupported_source) @@ -97,9 +104,7 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) { is HttpStatusException -> getHttpDisplayMessage(statusCode, resources) else -> getDisplayMessage(message, resources) ?: message -}.ifNullOrEmpty { - resources.getString(R.string.error_occurred) -} +}.takeUnless { it.isNullOrBlank() } @DrawableRes fun Throwable.getDisplayIcon() = when (this) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/zip/ZipOutput.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/zip/ZipOutput.kt index 1f5c34766..509ff3287 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/zip/ZipOutput.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/zip/ZipOutput.kt @@ -2,11 +2,13 @@ package org.koitharu.kotatsu.core.zip import androidx.annotation.WorkerThread import androidx.collection.ArraySet +import okhttp3.internal.closeQuietly import okio.Closeable +import org.jetbrains.annotations.Blocking import org.koitharu.kotatsu.core.util.ext.withChildren import java.io.File import java.io.FileInputStream -import java.util.concurrent.atomic.AtomicBoolean +import java.io.FileOutputStream import java.util.zip.Deflater import java.util.zip.ZipEntry import java.util.zip.ZipFile @@ -14,27 +16,23 @@ import java.util.zip.ZipOutputStream class ZipOutput( val file: File, - compressionLevel: Int = Deflater.DEFAULT_COMPRESSION, + private val compressionLevel: Int = Deflater.DEFAULT_COMPRESSION, ) : Closeable { private val entryNames = ArraySet() - private val isClosed = AtomicBoolean(false) - private val output = ZipOutputStream(file.outputStream()).apply { - setLevel(compressionLevel) - // FIXME: Deflater has been closed + private var cachedOutput: ZipOutputStream? = null + + @Blocking + fun put(name: String, file: File): Boolean = withOutput { output -> + output.appendFile(file, name) } - @WorkerThread - fun put(name: String, file: File): Boolean { - return output.appendFile(file, name) + @Blocking + fun put(name: String, content: String): Boolean = withOutput { output -> + output.appendText(content, name) } - @WorkerThread - fun put(name: String, content: String): Boolean { - return output.appendText(content, name) - } - - @WorkerThread + @Blocking fun addDirectory(name: String): Boolean { val entry = if (name.endsWith("/")) { ZipEntry(name) @@ -42,24 +40,8 @@ class ZipOutput( ZipEntry("$name/") } return if (entryNames.add(entry.name)) { - output.putNextEntry(entry) - output.closeEntry() - true - } else { - false - } - } - - @WorkerThread - fun copyEntryFrom(other: ZipFile, entry: ZipEntry): Boolean { - return if (entryNames.add(entry.name)) { - val zipEntry = ZipEntry(entry.name) - output.putNextEntry(zipEntry) - try { - other.getInputStream(entry).use { input -> - input.copyTo(output) - } - } finally { + withOutput { output -> + output.putNextEntry(entry) output.closeEntry() } true @@ -68,15 +50,35 @@ class ZipOutput( } } - fun finish() { - output.finish() - output.flush() + @Blocking + fun copyEntryFrom(other: ZipFile, entry: ZipEntry): Boolean { + return if (entryNames.add(entry.name)) { + val zipEntry = ZipEntry(entry.name) + withOutput { output -> + output.putNextEntry(zipEntry) + try { + other.getInputStream(entry).use { input -> + input.copyTo(output) + } + } finally { + output.closeEntry() + } + } + true + } else { + false + } } + @Blocking + fun finish() = withOutput { output -> + output.finish() + } + + @Synchronized override fun close() { - if (isClosed.compareAndSet(false, true)) { - output.close() - } + cachedOutput?.close() + cachedOutput = null } @WorkerThread @@ -128,4 +130,18 @@ class ZipOutput( } return true } + + @Synchronized + private fun withOutput(block: (ZipOutputStream) -> T): T { + val output = cachedOutput ?: newOutput(append = false) + val res = block(output) + output.flush() + return res + } + + private fun newOutput(append: Boolean) = ZipOutputStream(FileOutputStream(file, append)).also { + it.setLevel(compressionLevel) + cachedOutput?.closeQuietly() + cachedOutput = it + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt index d374e3360..170e87ec3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListFragment.kt @@ -62,7 +62,7 @@ class LocalListFragment : MangaListFragment(), FilterCoordinator.Owner { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { super.onViewBindingCreated(binding, savedInstanceState) - addMenuProvider(LocalListMenuProvider(binding.root.context, this::onEmptyActionClick)) + addMenuProvider(LocalListMenuProvider(binding.root.context, childFragmentManager, this::onEmptyActionClick)) addMenuProvider(MangaSearchMenuProvider(filterCoordinator, viewModel)) viewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListMenuProvider.kt index 882ea4646..88b560370 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListMenuProvider.kt @@ -5,11 +5,14 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import androidx.core.view.MenuProvider +import androidx.fragment.app.FragmentManager import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.filter.ui.sheet.FilterSheetFragment import org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity class LocalListMenuProvider( private val context: Context, + private val fragmentManager: FragmentManager, private val onImportClick: Function0, ) : MenuProvider { @@ -29,6 +32,11 @@ class LocalListMenuProvider( true } + R.id.action_filter -> { + FilterSheetFragment.show(fragmentManager) + true + } + else -> false } } diff --git a/app/src/main/res/menu/opt_local.xml b/app/src/main/res/menu/opt_local.xml index e00b4b7d3..0403421a9 100644 --- a/app/src/main/res/menu/opt_local.xml +++ b/app/src/main/res/menu/opt_local.xml @@ -9,9 +9,15 @@ android:title="@string/_import" app:showAsAction="never" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5d9314ded..b6b3cd656 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -679,6 +679,7 @@ Chapters left External/plugin Incompatible plugin or internal error. Make sure you are using the latest version of the plugin and Kotatsu + Plugin error: %s\n Make sure you are using the latest version of the plugin and Kotatsu Connection is OK Invalid proxy configuration Show quick filters