[Errors] Update error details dialog, implement error reporting.

This commit is contained in:
Kuba Szczodrzyński 2019-12-31 12:44:15 +01:00
parent f350a86946
commit 6f95eb3c3f
6 changed files with 110 additions and 8 deletions

View File

@ -424,6 +424,11 @@ fun CharSequence?.asItalicSpannable(): Spannable {
spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
fun CharSequence?.asBoldSpannable(): Spannable {
val spannable = SpannableString(this)
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
return spannable
}
/**
* Returns a new read-only list only of those given elements, that are not empty.

View File

@ -8,6 +8,8 @@ import android.content.Context
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.stackTraceString
class ApiError(val tag: String, val errorCode: Int) {
var profileId: Int? = null
@ -52,6 +54,15 @@ class ApiError(val tag: String, val errorCode: Int) {
)
}
fun getStringText(context: Context): String {
return context.resources.getIdentifier("error_${errorCode}", "string", context.packageName).let {
if (it != 0)
context.getString(it)
else
"?"
}
}
fun getStringReason(context: Context): String {
return context.resources.getIdentifier("error_${errorCode}_reason", "string", context.packageName).let {
if (it != 0)
@ -65,5 +76,31 @@ class ApiError(val tag: String, val errorCode: Int) {
return "ApiError(tag='$tag', errorCode=$errorCode, profileId=$profileId, throwable=$throwable, apiResponse=$apiResponse, request=$request, response=$response, isCritical=$isCritical)"
}
fun toReportableError(context: Context): ErrorReportRequest.Error {
val requestString = request?.let {
it.method() + " " + it.url() + "\n" + it.headers()
}
val responseString = response?.let {
if (it.parserErrorBody == null) {
try {
it.parserErrorBody = it.raw().body()?.string()
} catch (e: Exception) {
it.parserErrorBody = e.stackTraceString
}
}
"HTTP "+it.code()+" "+it.message()+"\n" + it.headers() + "\n\n" + it.parserErrorBody
}
return ErrorReportRequest.Error(
tag = tag,
errorCode = errorCode,
errorText = getStringText(context),
errorReason = getStringReason(context),
stackTrace = throwable?.stackTraceString,
request = requestString,
response = responseString,
apiResponse = apiResponse,
isCritical = isCritical
)
}
}

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
@ -164,4 +165,11 @@ class SzkolnyApi(val app: App) {
return response?.data?.browsers ?: emptyList()
}
fun errorReport(errors: List<ErrorReportRequest.Error>): ApiResponse<Nothing>? {
return api.errorReport(ErrorReportRequest(
deviceId = app.deviceId,
errors = errors
)).execute().body()
}
}

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.data.api.szkolny
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
@ -24,4 +25,7 @@ interface SzkolnyService {
@POST("webPush")
fun webPush(@Body request: WebPushRequest): Call<ApiResponse<WebPushResponse>>
@POST("errorReport")
fun errorReport(@Body request: ErrorReportRequest): Call<ApiResponse<Nothing>>
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-31.
*/
package pl.szczodrzynski.edziennik.data.api.szkolny.request
data class ErrorReportRequest(
val deviceId: String,
val errors: List<Error>
) {
data class Error(
val tag: String,
val errorCode: Int,
val errorText: String?,
val errorReason: String?,
val stackTrace: String?,
val request: String?,
val response: String?,
val apiResponse: String?,
val isCritical: Boolean
)
}

View File

@ -5,18 +5,20 @@
package pl.szczodrzynski.edziennik.ui.modules.error
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.ColorUtils
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.stackTraceString
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.navlib.getColorFromAttr
import kotlin.coroutines.CoroutineContext
class ErrorSnackbar(val activity: AppCompatActivity) {
class ErrorSnackbar(val activity: AppCompatActivity) : CoroutineScope {
companion object {
private const val TAG = "ErrorSnackbar"
}
@ -25,16 +27,26 @@ class ErrorSnackbar(val activity: AppCompatActivity) {
private lateinit var coordinator: CoordinatorLayout
private val errors = mutableListOf<ApiError>()
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val api by lazy { SzkolnyApi(activity.applicationContext as App) }
fun setCoordinator(coordinatorLayout: CoordinatorLayout, showAbove: View? = null) {
this.coordinator = coordinatorLayout
snackbar = Snackbar.make(coordinator, R.string.snackbar_error_text, Snackbar.LENGTH_INDEFINITE)
snackbar?.setAction(R.string.more) {
if (errors.isNotEmpty()) {
val message = errors.map {
if (App.devMode) it.throwable?.stackTraceString
?: it.throwable?.localizedMessage ?: ""
else it.throwable?.localizedMessage
}.joinToString("\n")
listOf(
it.getStringReason(activity).asBoldSpannable(),
if (App.devMode)
it.throwable?.stackTraceString ?: it.throwable?.localizedMessage
else
it.throwable?.localizedMessage
).concat("\n")
}.concat("\n\n")
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_error_details_title)
@ -43,6 +55,20 @@ class ErrorSnackbar(val activity: AppCompatActivity) {
errors.clear()
dialog.dismiss()
}
.setNeutralButton(R.string.report) { dialog, _ ->
launch {
val response = withContext(Dispatchers.Default) {
api.errorReport(errors.map { it.toReportableError(activity) })
}
response?.errors?.ifNotEmpty {
Toast.makeText(activity, "Error: " + it[0].reason, Toast.LENGTH_SHORT).show()
return@launch
}
errors.clear()
dialog.dismiss()
}
}
.show()
}
}