Add details to error message (#186)

This commit is contained in:
Rafał Borcz 2018-11-25 15:03:47 +01:00 committed by Mikołaj Pich
parent 7a3c0de7ad
commit 7686228e01
13 changed files with 193 additions and 26 deletions

View File

@ -2,29 +2,30 @@ package io.github.wulkanowy.data
import android.content.res.Resources
import io.github.wulkanowy.R
import io.github.wulkanowy.api.interceptor.ServiceUnavailableException
import io.github.wulkanowy.api.login.NotLoggedInException
import timber.log.Timber
import java.io.IOException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.inject.Inject
open class ErrorHandler @Inject constructor(protected val resources: Resources) {
var showErrorMessage: (String) -> Unit = {}
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
open fun proceed(error: Throwable) {
Timber.e(error, "An exception occurred while the Wulkanowy was running")
showErrorMessage((when (error) {
is UnknownHostException -> resources.getString(R.string.all_no_internet)
is SocketTimeoutException -> resources.getString(R.string.all_timeout)
is NotLoggedInException, is IOException -> resources.getString(R.string.all_login_failed)
else -> error.localizedMessage
}))
is UnknownHostException -> resources.getString(R.string.error_no_internet)
is SocketTimeoutException -> resources.getString(R.string.error_timeout)
is NotLoggedInException -> resources.getString(R.string.error_login_failed)
is ServiceUnavailableException -> resources.getString(R.string.error_service_unavaible)
else -> resources.getString(R.string.error_unknown)
}), error)
}
open fun clear() {
showErrorMessage = {}
showErrorMessage = { _, _ -> }
}
}

View File

@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatDelegate
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import dagger.android.support.DaggerAppCompatActivity
import io.github.wulkanowy.R
abstract class BaseActivity : DaggerAppCompatActivity(), BaseView {
@ -16,6 +17,12 @@ abstract class BaseActivity : DaggerAppCompatActivity(), BaseView {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
}
override fun showError(text: String, error: Throwable) {
Snackbar.make(messageContainer, text, LENGTH_LONG).setAction(R.string.all_details) {
ErrorDialog.newInstance(error).show(supportFragmentManager, error.toString())
}.show()
}
override fun showMessage(text: String) {
Snackbar.make(messageContainer, text, LENGTH_LONG).show()
}

View File

@ -2,15 +2,24 @@ package io.github.wulkanowy.ui.base
import android.view.View
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import dagger.android.support.DaggerFragment
import io.github.wulkanowy.R
abstract class BaseFragment : DaggerFragment(), BaseView {
protected var messageContainer: View? = null
override fun showError(text: String, error: Throwable) {
if (messageContainer == null) (activity as? BaseActivity)?.showError(text, error)
else messageContainer?.also {
Snackbar.make(it, text, Snackbar.LENGTH_LONG).setAction(R.string.all_details) {
ErrorDialog.newInstance(error).show(fragmentManager, error.toString())
}.show()
}
}
override fun showMessage(text: String) {
if (messageContainer == null) (activity as? BaseActivity)?.showMessage(text)
else messageContainer?.also { Snackbar.make(it, text, LENGTH_LONG).show() }
else messageContainer?.also { Snackbar.make(it, text, Snackbar.LENGTH_LONG).show() }
}
}

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.ui.base
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.ui.modules.main.MainView
import io.reactivex.disposables.CompositeDisposable
open class BasePresenter<T : BaseView>(private val errorHandler: ErrorHandler) {
@ -12,7 +11,7 @@ open class BasePresenter<T : BaseView>(private val errorHandler: ErrorHandler) {
open fun onAttachView(view: T) {
this.view = view
errorHandler.showErrorMessage = { view.showMessage(it) }
errorHandler.showErrorMessage = { text, error -> view.showError(text, error) }
}
open fun onDetachView() {

View File

@ -2,5 +2,7 @@ package io.github.wulkanowy.ui.base
interface BaseView {
fun showError(text: String, error: Throwable)
fun showMessage(text: String)
}

View File

@ -0,0 +1,60 @@
package io.github.wulkanowy.ui.base
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import kotlinx.android.synthetic.main.dialog_error.*
import java.io.PrintWriter
import java.io.StringWriter
class ErrorDialog : DialogFragment() {
private lateinit var error: Throwable
companion object {
private const val ARGUMENT_KEY = "Data"
fun newInstance(error: Throwable): ErrorDialog {
return ErrorDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, error) }
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
error = getSerializable(ARGUMENT_KEY) as Throwable
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_error, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
StringWriter().let { writer ->
error.printStackTrace(PrintWriter(writer))
errorDialogContent.text = writer.toString()
errorDialogCopy.setOnClickListener {
ClipData.newPlainText("wulkanowyError", writer.toString()).let { clip ->
(activity?.getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager)?.primaryClip = clip
}
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
}
}
errorDialogCancel.setOnClickListener { dismiss() }
}
}

View File

@ -59,6 +59,10 @@ class AccountDialog : DaggerAppCompatDialogFragment(), AccountView {
accountAdapter.updateDataSet(data)
}
override fun showError(text: String, error: Throwable) {
showMessage(text)
}
override fun showMessage(text: String) {
Toast.makeText(context, text, LENGTH_LONG).show()
}

View File

@ -10,10 +10,12 @@ class LoginErrorHandler(resources: Resources) : ErrorHandler(resources) {
var onBadCredentials: () -> Unit = {}
var onStudentDuplicate: (String) -> Unit = {}
override fun proceed(error: Throwable) {
when (error) {
is BadCredentialsException -> onBadCredentials()
is SQLiteConstraintException -> showErrorMessage(resources.getString(R.string.login_duplicate_student))
is SQLiteConstraintException -> onStudentDuplicate(resources.getString(R.string.login_duplicate_student))
else -> super.proceed(error)
}
}

View File

@ -20,7 +20,10 @@ class LoginOptionsPresenter @Inject constructor(
override fun onAttachView(view: LoginOptionsView) {
super.onAttachView(view)
view.initView()
view.run {
initView()
errorHandler.onStudentDuplicate = { showMessage(it) }
}
}
fun onParentViewLoadData() {

View File

@ -3,11 +3,11 @@ package io.github.wulkanowy.ui.modules.settings
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import com.takisoft.preferencex.PreferenceFragmentCompat
import dagger.android.support.AndroidSupportInjection
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
@ -54,8 +54,12 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
}
}
override fun showError(text: String, error: Throwable) {
(activity as? BaseActivity)?.showError(text, error)
}
override fun showMessage(text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
(activity as? BaseActivity)?.showMessage(text)
}
override fun onResume() {

View File

@ -0,0 +1,70 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="300dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="ifContentScrolls"
tools:ignore="UselessParent">
<HorizontalScrollView
android:layout_width="350dp"
android:layout_height="wrap_content"
android:paddingLeft="24dp"
android:paddingTop="24dp"
android:paddingRight="24dp">
<TextView
android:id="@+id/errorDialogContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollHorizontally="true"
android:textIsSelectable="true"
android:textSize="12sp" />
</HorizontalScrollView>
</ScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="end"
android:minHeight="52dp"
android:orientation="horizontal">
<Button
android:id="@+id/errorDialogCancel"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:gravity="center"
android:text="@android:string/cancel"
android:textColor="@color/colorPrimary" />
<Button
android:id="@+id/errorDialogCopy"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:gravity="center"
android:text="@android:string/copy"
android:textColor="@color/colorPrimary" />
</LinearLayout>
</LinearLayout>

View File

@ -205,9 +205,12 @@
<!--Others-->
<string name="all_no_internet">Brak połączenia z internetem</string>
<string name="all_sync_finish">Synchronizacja zakończona</string>
<string name="all_sync_fail">Podczas synchronizacji wystąpił błąd</string>
<string name="all_timeout">Zbyt długie oczekiwanie na połączenie</string>
<string name="all_login_failed">Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację</string>
<string name="all_copied">Skopiowano</string>
<!--Errors-->
<string name="error_no_internet">Brak połączenia z internetem</string>
<string name="error_timeout">Zbyt długie oczekiwanie na połączenie</string>
<string name="error_login_failed">Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację</string>
<string name="error_service_unavaible">Dziennik jest niedostępny. Spróbuj ponownie później</string>
<string name="error_unknown">Wystąpił nieoczekiwany błąd</string>
</resources>

View File

@ -190,9 +190,12 @@
<!--Others-->
<string name="all_no_internet">No internet connection</string>
<string name="all_sync_finish">Synchronization complete</string>
<string name="all_sync_fail">There was an error during synchronization</string>
<string name="all_timeout">Too long wait for connection</string>
<string name="all_login_failed">Login is failed. Try again or restart the app</string>
<string name="all_copied">Copied</string>
<!--Errors-->
<string name="error_no_internet">No internet connection</string>
<string name="error_timeout">Too long wait for connection</string>
<string name="error_login_failed">Login is failed. Try again or restart the app</string>
<string name="error_service_unavaible">The log is not available. Try again later</string>
<string name="error_unknown">An unexpected error occurred</string>
</resources>