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