diff --git a/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt b/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt
index 8847cb601..058e53c09 100644
--- a/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt
+++ b/app/src/debug/kotlin/org/koitharu/kotatsu/StrictModeNotifier.kt
@@ -9,11 +9,12 @@ import android.os.Build
import android.os.StrictMode
import android.os.strictmode.Violation
import androidx.annotation.RequiresApi
+import androidx.core.app.PendingIntentCompat
import androidx.core.content.getSystemService
import androidx.fragment.app.strictmode.FragmentStrictMode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
-import org.koitharu.kotatsu.core.ErrorReporterReceiver
+import org.koitharu.kotatsu.core.util.ShareHelper
import kotlin.math.absoluteValue
import androidx.fragment.app.strictmode.Violation as FragmentViolation
@@ -42,7 +43,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(
@@ -51,7 +52,15 @@ class StrictModeNotifier(
.setSummaryText(violation.message)
.bigText(violation.stackTraceToString()),
).setShowWhen(true)
- .setContentIntent(ErrorReporterReceiver.getPendingIntent(context, violation))
+ .setContentIntent(
+ PendingIntentCompat.getActivity(
+ context,
+ 0,
+ ShareHelper(context).getShareTextIntent(violation.stackTraceToString()),
+ 0,
+ false,
+ ),
+ )
.setAutoCancel(true)
.setGroup(CHANNEL_ID)
.build()
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 83318f8ea..5ac7421e6 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
@@ -28,7 +28,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)
@@ -41,8 +41,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/exceptions/resolve/DialogErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
index 1edf3b662..8d2e34b9f 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/DialogErrorObserver.kt
@@ -8,6 +8,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
+import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.parsers.exception.ParseException
class DialogErrorObserver(
@@ -32,7 +33,7 @@ class DialogErrorObserver(
dialogBuilder.setPositiveButton(ExceptionResolver.getResolveStringId(value), listener)
} else if (value is ParseException) {
val fm = fragmentManager
- if (fm != null) {
+ if (fm != null && value.isSerializable()) {
dialogBuilder.setPositiveButton(R.string.details) { _, _ ->
ErrorDetailsDialog.show(fm, value, value.url)
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
index e39897cfc..d5b55b750 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/SnackbarErrorObserver.kt
@@ -7,6 +7,7 @@ import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
+import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -33,7 +34,7 @@ class SnackbarErrorObserver(
}
} else if (value is ParseException) {
val fm = fragmentManager
- if (fm != null) {
+ if (fm != null && value.isSerializable()) {
snackbar.setAction(R.string.details) {
ErrorDetailsDialog.show(fm, value, value.url)
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt
new file mode 100644
index 000000000..d02cdf97b
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/io/NullOutputStream.kt
@@ -0,0 +1,13 @@
+package org.koitharu.kotatsu.core.io
+
+import java.io.OutputStream
+import java.util.Objects
+
+class NullOutputStream : OutputStream() {
+
+ override fun write(b: Int) = Unit
+
+ override fun write(b: ByteArray, off: Int, len: Int) {
+ Objects.checkFromIndexSize(off, len, b.size)
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt
index fc486cb4f..3fa1b6e3f 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ShareHelper.kt
@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.util
import android.content.Context
+import android.content.Intent
import android.net.Uri
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
@@ -75,11 +76,9 @@ class ShareHelper(private val context: Context) {
.startChooser()
}
- fun shareText(text: String) {
- ShareCompat.IntentBuilder(context)
- .setText(text)
- .setType(TYPE_TEXT)
- .setChooserTitle(R.string.share)
- .startChooser()
- }
+ fun getShareTextIntent(text: String): Intent = ShareCompat.IntentBuilder(context)
+ .setText(text)
+ .setType(TYPE_TEXT)
+ .setChooserTitle(R.string.share)
+ .createChooserIntent()
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
index 34f3f440e..0a90ef83a 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt
@@ -20,6 +20,10 @@ inline fun Bundle.getParcelableCompat(key: String): T?
return BundleCompat.getParcelable(this, key, T::class.java)
}
+inline fun Bundle.requireParcelable(key: String): T = checkNotNull(getParcelableCompat(key)) {
+ "Parcelable of type \"${T::class.java.name}\" not found at \"$key\""
+}
+
inline fun Intent.getParcelableExtraCompat(key: String): T? {
return IntentCompat.getParcelableExtra(this, key, T::class.java)
}
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 41a51ce63..afb2117fe 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
@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
+import org.koitharu.kotatsu.core.io.NullOutputStream
import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED
import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED
import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED
@@ -35,6 +36,7 @@ import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.exception.TooManyRequestExceptions
import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredException
+import java.io.ObjectOutputStream
import java.net.SocketTimeoutException
import java.net.UnknownHostException
@@ -181,3 +183,9 @@ fun Throwable.isWebViewUnavailable(): Boolean {
@Suppress("FunctionName")
fun NoSpaceLeftException() = IOException(MSG_NO_SPACE_LEFT)
+
+fun Throwable.isSerializable() = runCatching {
+ val oos = ObjectOutputStream(NullOutputStream())
+ oos.writeObject(this)
+ oos.flush()
+}.isSuccess
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt
index 482618a0a..745703330 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsErrorObserver.kt
@@ -8,6 +8,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.isNetworkError
+import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -38,7 +39,7 @@ class DetailsErrorObserver(
value is ParseException -> {
val fm = fragmentManager
- if (fm != null) {
+ if (fm != null && value.isSerializable()) {
snackbar.setAction(R.string.details) {
ErrorDetailsDialog.show(fm, value, value.url)
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt
index 21a0d5639..e901d9079 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
+import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.parsers.util.ifZero
import org.koitharu.kotatsu.reader.domain.PageLoader
@@ -154,6 +155,7 @@ open class PageHolder(
bindingInfo.buttonRetry.setText(
ExceptionResolver.getResolveStringId(e).ifZero { R.string.try_again },
)
+ bindingInfo.buttonErrorDetails.isVisible = e.isSerializable()
bindingInfo.layoutError.isVisible = true
bindingInfo.progressBar.hide()
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt
index e2629e4a1..31501e97d 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt
@@ -11,6 +11,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.util.GoneOnInvisibleListener
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
+import org.koitharu.kotatsu.core.util.ext.isSerializable
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
import org.koitharu.kotatsu.parsers.util.ifZero
import org.koitharu.kotatsu.reader.domain.PageLoader
@@ -128,6 +129,7 @@ class WebtoonHolder(
bindingInfo.buttonRetry.setText(
ExceptionResolver.getResolveStringId(e).ifZero { R.string.try_again },
)
+ bindingInfo.buttonErrorDetails.isVisible = e.isSerializable()
bindingInfo.layoutError.isVisible = true
bindingInfo.progressBar.hide()
}
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" />
+
+