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" /> + +