diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index 48076d0b..d9e8a6d4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -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. diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt index 78785ef7..16b7e716 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/ApiError.kt @@ -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 + ) + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt index 2da2ed93..f5521520 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt @@ -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): ApiResponse? { + return api.errorReport(ErrorReportRequest( + deviceId = app.deviceId, + errors = errors + )).execute().body() + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt index b57dd99b..7d5f73e5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt @@ -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> + + @POST("errorReport") + fun errorReport(@Body request: ErrorReportRequest): Call> } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/ErrorReportRequest.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/ErrorReportRequest.kt new file mode 100644 index 00000000..6c049da1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/ErrorReportRequest.kt @@ -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 +) { + 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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/error/ErrorSnackbar.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/error/ErrorSnackbar.kt index 66dcd766..f4696ac9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/error/ErrorSnackbar.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/error/ErrorSnackbar.kt @@ -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() + 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() } }