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 000000000..b4aa5f3c7
Binary files /dev/null and b/app/src/debug/res/drawable-hdpi/ic_bug.png differ
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 000000000..726da962c
Binary files /dev/null and b/app/src/debug/res/drawable-mdpi/ic_bug.png differ
diff --git a/app/src/debug/res/drawable-xhdpi/ic_bug.png b/app/src/debug/res/drawable-xhdpi/ic_bug.png
new file mode 100644
index 000000000..f71348751
Binary files /dev/null and b/app/src/debug/res/drawable-xhdpi/ic_bug.png differ
diff --git a/app/src/debug/res/drawable-xxhdpi/ic_bug.png b/app/src/debug/res/drawable-xxhdpi/ic_bug.png
new file mode 100644
index 000000000..99ce95508
Binary files /dev/null and b/app/src/debug/res/drawable-xxhdpi/ic_bug.png differ
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
- @WorkerThread
- fun put(name: String, file: File): Boolean {
- return output.appendFile(file, name)
+ @Blocking
+ fun put(name: String, file: File): Boolean = withOutput { output ->
+ output.appendFile(file, name)
}
- @WorkerThread
- fun put(name: String, content: String): Boolean {
- return output.appendText(content, name)
+ @Blocking
+ fun put(name: String, content: String): Boolean = withOutput { output ->
+ output.appendText(content, name)
}
- @WorkerThread
+ @Blocking
fun addDirectory(name: String): Boolean {
val entry = if (name.endsWith("/")) {
ZipEntry(name)
@@ -42,25 +40,29 @@ class ZipOutput(
ZipEntry("$name/")
}
return if (entryNames.add(entry.name)) {
- output.putNextEntry(entry)
- output.closeEntry()
+ withOutput { output ->
+ output.putNextEntry(entry)
+ output.closeEntry()
+ }
true
} else {
false
}
}
- @WorkerThread
+ @Blocking
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)
+ withOutput { output ->
+ output.putNextEntry(zipEntry)
+ try {
+ other.getInputStream(entry).use { input ->
+ input.copyTo(output)
+ }
+ } finally {
+ output.closeEntry()
}
- } finally {
- output.closeEntry()
}
true
} else {
@@ -68,15 +70,15 @@ class ZipOutput(
}
}
- fun finish() {
+ @Blocking
+ fun finish() = withOutput { output ->
output.finish()
- output.flush()
}
+ @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