Ability to report non-fatal exceptions
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@
|
|||||||
/.idea/kotlinScripting.xml
|
/.idea/kotlinScripting.xml
|
||||||
/.idea/deploymentTargetDropDown.xml
|
/.idea/deploymentTargetDropDown.xml
|
||||||
/.idea/androidTestResultsUserPreferences.xml
|
/.idea/androidTestResultsUserPreferences.xml
|
||||||
|
/.idea/render.experimental.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
|||||||
6
.idea/render.experimental.xml
generated
6
.idea/render.experimental.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RenderSettings">
|
|
||||||
<option name="quality" value="0.25" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -92,6 +92,7 @@ class KotatsuApp : Application() {
|
|||||||
ReportField.PHONE_MODEL,
|
ReportField.PHONE_MODEL,
|
||||||
ReportField.CRASH_CONFIGURATION,
|
ReportField.CRASH_CONFIGURATION,
|
||||||
ReportField.STACK_TRACE,
|
ReportField.STACK_TRACE,
|
||||||
|
ReportField.CUSTOM_DATA,
|
||||||
ReportField.SHARED_PREFERENCES,
|
ReportField.SHARED_PREFERENCES,
|
||||||
)
|
)
|
||||||
dialog {
|
dialog {
|
||||||
|
|||||||
@@ -17,19 +17,28 @@
|
|||||||
package org.koitharu.kotatsu.base.ui.widgets
|
package org.koitharu.kotatsu.base.ui.widgets
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.TextView
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
import androidx.core.view.postDelayed
|
import androidx.core.view.postDelayed
|
||||||
import org.koitharu.kotatsu.R
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
|
import com.google.android.material.shape.ShapeAppearanceModel
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.koitharu.kotatsu.databinding.FadingSnackbarLayoutBinding
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
private const val ENTER_DURATION = 300L
|
private const val ENTER_DURATION = 300L
|
||||||
private const val EXIT_DURATION = 200L
|
private const val EXIT_DURATION = 200L
|
||||||
private const val SHORT_DURATION = 1_500L
|
private const val SHORT_DURATION_MS = 1_500L
|
||||||
private const val LONG_DURATION = 2_750L
|
private const val LONG_DURATION_MS = 2_750L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom snackbar implementation allowing more control over placement and entry/exit animations.
|
* A custom snackbar implementation allowing more control over placement and entry/exit animations.
|
||||||
*
|
*
|
||||||
@@ -40,16 +49,13 @@ private const val LONG_DURATION = 2_750L
|
|||||||
class FadingSnackbar @JvmOverloads constructor(
|
class FadingSnackbar @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0,
|
||||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
private val message: TextView
|
private val binding = FadingSnackbarLayoutBinding.inflate(LayoutInflater.from(context), this)
|
||||||
private val action: Button
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val view = LayoutInflater.from(context).inflate(R.layout.fading_snackbar_layout, this, true)
|
binding.snackbarLayout.background = createThemedBackground()
|
||||||
message = view.findViewById(R.id.snackbar_text)
|
|
||||||
action = view.findViewById(R.id.snackbar_action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dismiss() {
|
fun dismiss() {
|
||||||
@@ -62,33 +68,66 @@ class FadingSnackbar @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun show(
|
fun show(
|
||||||
messageText: CharSequence? = null,
|
messageText: CharSequence?,
|
||||||
@StringRes actionId: Int? = null,
|
@StringRes actionId: Int = 0,
|
||||||
longDuration: Boolean = true,
|
duration: Int = Snackbar.LENGTH_SHORT,
|
||||||
actionClick: () -> Unit = { dismiss() },
|
onActionClick: (FadingSnackbar.() -> Unit)? = null,
|
||||||
dismissListener: () -> Unit = { }
|
onDismiss: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
message.text = messageText
|
binding.snackbarText.text = messageText
|
||||||
if (actionId != null) {
|
if (actionId != 0) {
|
||||||
action.run {
|
with(binding.snackbarAction) {
|
||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
text = context.getString(actionId)
|
text = context.getString(actionId)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
actionClick()
|
onActionClick?.invoke(this@FadingSnackbar) ?: dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
action.visibility = GONE
|
binding.snackbarAction.visibility = GONE
|
||||||
}
|
}
|
||||||
alpha = 0f
|
alpha = 0f
|
||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
animate()
|
animate()
|
||||||
.alpha(1f)
|
.alpha(1f)
|
||||||
.duration = ENTER_DURATION
|
.duration = ENTER_DURATION
|
||||||
val showDuration = ENTER_DURATION + if (longDuration) LONG_DURATION else SHORT_DURATION
|
if (duration == Snackbar.LENGTH_INDEFINITE) {
|
||||||
postDelayed(showDuration) {
|
return
|
||||||
|
}
|
||||||
|
val durationMs = ENTER_DURATION + if (duration == Snackbar.LENGTH_LONG) LONG_DURATION_MS else SHORT_DURATION_MS
|
||||||
|
postDelayed(durationMs) {
|
||||||
dismiss()
|
dismiss()
|
||||||
dismissListener()
|
onDismiss?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createThemedBackground(): Drawable {
|
||||||
|
val backgroundColor = MaterialColors.layer(this, materialR.attr.colorSurface, materialR.attr.colorOnSurface, 1f)
|
||||||
|
val shapeAppearanceModel = ShapeAppearanceModel.builder(
|
||||||
|
context,
|
||||||
|
materialR.style.ShapeAppearance_Material3_Corner_ExtraSmall,
|
||||||
|
0
|
||||||
|
).build()
|
||||||
|
val background = createMaterialShapeDrawableBackground(
|
||||||
|
backgroundColor,
|
||||||
|
shapeAppearanceModel,
|
||||||
|
)
|
||||||
|
val backgroundTint = context.getThemeColorStateList(materialR.attr.colorSurfaceInverse)
|
||||||
|
return if (backgroundTint != null) {
|
||||||
|
val wrappedDrawable = DrawableCompat.wrap(background)
|
||||||
|
DrawableCompat.setTintList(wrappedDrawable, backgroundTint)
|
||||||
|
wrappedDrawable
|
||||||
|
} else {
|
||||||
|
DrawableCompat.wrap(background)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMaterialShapeDrawableBackground(
|
||||||
|
@ColorInt backgroundColor: Int,
|
||||||
|
shapeAppearanceModel: ShapeAppearanceModel,
|
||||||
|
): MaterialShapeDrawable {
|
||||||
|
val background = MaterialShapeDrawable(shapeAppearanceModel)
|
||||||
|
background.fillColor = ColorStateList.valueOf(backgroundColor)
|
||||||
|
return background
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,9 +21,11 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.acra.ktx.sendWithAcra
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
@@ -37,6 +39,7 @@ import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
|||||||
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
||||||
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
|
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
|
||||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||||
|
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||||
@@ -81,7 +84,7 @@ class DetailsActivity :
|
|||||||
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved)
|
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved)
|
||||||
viewModel.onError.observe(this, ::onError)
|
viewModel.onError.observe(this, ::onError)
|
||||||
viewModel.onShowToast.observe(this) {
|
viewModel.onShowToast.observe(this) {
|
||||||
binding.snackbar.show(messageText = getString(it), longDuration = false)
|
binding.snackbar.show(messageText = getString(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
registerReceiver(downloadReceiver, IntentFilter(DownloadService.ACTION_DOWNLOAD_COMPLETE))
|
registerReceiver(downloadReceiver, IntentFilter(DownloadService.ACTION_DOWNLOAD_COMPLETE))
|
||||||
@@ -114,6 +117,21 @@ class DetailsActivity :
|
|||||||
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
}
|
}
|
||||||
|
e is ParseException || e is IllegalArgumentException || e is IllegalStateException -> {
|
||||||
|
binding.snackbar.show(
|
||||||
|
messageText = e.getDisplayMessage(resources),
|
||||||
|
actionId = R.string.report,
|
||||||
|
duration = if (viewModel.manga.value?.chapters == null) {
|
||||||
|
Snackbar.LENGTH_INDEFINITE
|
||||||
|
} else {
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
},
|
||||||
|
onActionClick = {
|
||||||
|
e.sendWithAcra()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
binding.snackbar.show(e.getDisplayMessage(resources))
|
binding.snackbar.show(e.getDisplayMessage(resources))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData
|
|||||||
import androidx.lifecycle.asFlow
|
import androidx.lifecycle.asFlow
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import java.io.IOException
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@@ -30,6 +29,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
|||||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class DetailsViewModel(
|
class DetailsViewModel(
|
||||||
intent: MangaIntent,
|
intent: MangaIntent,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.details.ui
|
|||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import org.acra.ACRA
|
||||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||||
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
|
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
|
||||||
@@ -13,6 +14,7 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
|||||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||||
|
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
@@ -20,6 +22,7 @@ import org.koitharu.kotatsu.parsers.util.mapToSet
|
|||||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||||
import org.koitharu.kotatsu.utils.ext.iterator
|
import org.koitharu.kotatsu.utils.ext.iterator
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
|
import org.koitharu.kotatsu.utils.ext.setCurrentManga
|
||||||
|
|
||||||
class MangaDetailsDelegate(
|
class MangaDetailsDelegate(
|
||||||
private val intent: MangaIntent,
|
private val intent: MangaIntent,
|
||||||
@@ -32,6 +35,7 @@ class MangaDetailsDelegate(
|
|||||||
private val mangaData = MutableStateFlow(intent.manga)
|
private val mangaData = MutableStateFlow(intent.manga)
|
||||||
|
|
||||||
val selectedBranch = MutableStateFlow<String?>(null)
|
val selectedBranch = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
// Remote manga for saved and saved for remote
|
// Remote manga for saved and saved for remote
|
||||||
val relatedManga = MutableStateFlow<Manga?>(null)
|
val relatedManga = MutableStateFlow<Manga?>(null)
|
||||||
val manga: StateFlow<Manga?>
|
val manga: StateFlow<Manga?>
|
||||||
@@ -41,6 +45,7 @@ class MangaDetailsDelegate(
|
|||||||
suspend fun doLoad() {
|
suspend fun doLoad() {
|
||||||
var manga = mangaDataRepository.resolveIntent(intent)
|
var manga = mangaDataRepository.resolveIntent(intent)
|
||||||
?: throw MangaNotFoundException("Cannot find manga")
|
?: throw MangaNotFoundException("Cannot find manga")
|
||||||
|
ACRA.setCurrentManga(manga)
|
||||||
mangaData.value = manga
|
mangaData.value = manga
|
||||||
manga = MangaRepository(manga.source).getDetails(manga)
|
manga = MangaRepository(manga.source).getDetails(manga)
|
||||||
// find default branch
|
// find default branch
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.acra.ktx.sendWithAcra
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
@@ -214,6 +215,8 @@ class ReaderActivity :
|
|||||||
val resolveTextId = ExceptionResolver.getResolveStringId(e)
|
val resolveTextId = ExceptionResolver.getResolveStringId(e)
|
||||||
if (resolveTextId != 0) {
|
if (resolveTextId != 0) {
|
||||||
dialog.setPositiveButton(resolveTextId, listener)
|
dialog.setPositiveButton(resolveTextId, listener)
|
||||||
|
} else {
|
||||||
|
dialog.setPositiveButton(R.string.report, listener)
|
||||||
}
|
}
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
@@ -368,7 +371,11 @@ class ReaderActivity :
|
|||||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||||
dialog?.dismiss()
|
dialog?.dismiss()
|
||||||
tryResolve(exception)
|
if (ExceptionResolver.canResolve(exception)) {
|
||||||
|
tryResolve(exception)
|
||||||
|
} else {
|
||||||
|
exception.sendWithAcra()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onCancel(dialog)
|
onCancel(dialog)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import androidx.activity.result.ActivityResultLauncher
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import java.util.*
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import org.acra.ACRA
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||||
@@ -32,6 +32,8 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
|||||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||||
|
import org.koitharu.kotatsu.utils.ext.setCurrentManga
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
private const val BOUNDS_PAGE_OFFSET = 2
|
private const val BOUNDS_PAGE_OFFSET = 2
|
||||||
private const val PAGES_TRIM_THRESHOLD = 120
|
private const val PAGES_TRIM_THRESHOLD = 120
|
||||||
@@ -257,6 +259,7 @@ class ReaderViewModel(
|
|||||||
private fun loadImpl() {
|
private fun loadImpl() {
|
||||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
var manga = dataRepository.resolveIntent(intent) ?: throw MangaNotFoundException("Cannot find manga")
|
var manga = dataRepository.resolveIntent(intent) ?: throw MangaNotFoundException("Cannot find manga")
|
||||||
|
ACRA.setCurrentManga(manga)
|
||||||
mangaData.value = manga
|
mangaData.value = manga
|
||||||
val repo = MangaRepository(manga.source)
|
val repo = MangaRepository(manga.source)
|
||||||
manga = repo.getDetails(manga)
|
manga = repo.getDetails(manga)
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package org.koitharu.kotatsu.utils.ext
|
package org.koitharu.kotatsu.utils.ext
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import org.acra.ACRA
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||||
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
|
|
||||||
@@ -20,4 +22,6 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
|
|||||||
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
||||||
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
||||||
else -> localizedMessage ?: resources.getString(R.string.error_occurred)
|
else -> localizedMessage ?: resources.getString(R.string.error_occurred)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ACRA.setCurrentManga(manga: Manga?) = errorReporter.putCustomData("manga", manga?.publicUrl.toString())
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
~ Copyright 2018 Google LLC
|
~ Copyright 2018 Google LLC
|
||||||
~
|
~
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -22,15 +21,17 @@
|
|||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/snackbar_layout"
|
||||||
|
style="?attr/snackbarStyle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="@dimen/margin_small"
|
android:layout_margin="@dimen/margin_small"
|
||||||
android:background="@drawable/fading_snackbar_background"
|
android:background="@drawable/design_snackbar_background"
|
||||||
android:theme="@style/ThemeOverlay.Kotatsu"
|
|
||||||
android:elevation="8dp">
|
android:elevation="8dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/snackbar_text"
|
android:id="@+id/snackbar_text"
|
||||||
|
style="?attr/snackbarTextViewStyle"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical|start"
|
android:layout_gravity="center_vertical|start"
|
||||||
@@ -39,22 +40,22 @@
|
|||||||
android:maxLines="4"
|
android:maxLines="4"
|
||||||
android:padding="@dimen/margin_normal"
|
android:padding="@dimen/margin_normal"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
|
android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
tools:text="Look at all the wonderful snack bar text..." />
|
tools:text="Look at all the wonderful snack bar text..." />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/snackbar_action"
|
android:id="@+id/snackbar_action"
|
||||||
style="?borderlessButtonStyle"
|
style="?attr/snackbarButtonStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_vertical|end"
|
android:layout_gravity="center_vertical|end"
|
||||||
android:paddingEnd="@dimen/margin_normal"
|
|
||||||
android:paddingStart="@dimen/margin_normal"
|
android:paddingStart="@dimen/margin_normal"
|
||||||
|
android:paddingEnd="@dimen/margin_normal"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
tools:targetApi="o"
|
||||||
tools:text="Action"
|
tools:text="Action"
|
||||||
tools:visibility="visible"
|
tools:visibility="visible" />
|
||||||
tools:targetApi="o" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -304,4 +304,5 @@
|
|||||||
<string name="use_fingerprint">Use fingerprint if available</string>
|
<string name="use_fingerprint">Use fingerprint if available</string>
|
||||||
<string name="appwidget_shelf_description">Manga from your favourites</string>
|
<string name="appwidget_shelf_description">Manga from your favourites</string>
|
||||||
<string name="appwidget_recent_description">Your recently read manga</string>
|
<string name="appwidget_recent_description">Your recently read manga</string>
|
||||||
|
<string name="report">Report</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user