forked from github/szkolny
[API] Implement error handling and exception catching in Szkolny API.
This commit is contained in:
parent
fc21d757c3
commit
d0992eaf54
@ -49,15 +49,22 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.TlsVersion
|
||||
import okio.Buffer
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||
import pl.szczodrzynski.edziennik.network.TLSSocketFactory
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import java.io.InterruptedIOException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.math.BigInteger
|
||||
import java.net.ConnectException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import java.nio.charset.Charset
|
||||
import java.security.KeyStore
|
||||
import java.security.MessageDigest
|
||||
@ -67,6 +74,7 @@ import java.util.zip.CRC32
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLException
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import kotlin.Pair
|
||||
@ -1034,5 +1042,29 @@ fun CharSequence.containsAll(list: List<CharSequence>, ignoreCase: Boolean = fal
|
||||
return true
|
||||
}
|
||||
|
||||
fun RadioButton.setOnSelectedListener(listener: (buttonView: CompoundButton) -> Unit)
|
||||
inline fun RadioButton.setOnSelectedListener(crossinline listener: (buttonView: CompoundButton) -> Unit)
|
||||
= setOnCheckedChangeListener { buttonView, isChecked -> if (isChecked) listener(buttonView) }
|
||||
|
||||
fun Response.toErrorCode() = when (this.code()) {
|
||||
400 -> ERROR_REQUEST_HTTP_400
|
||||
401 -> ERROR_REQUEST_HTTP_401
|
||||
403 -> ERROR_REQUEST_HTTP_403
|
||||
404 -> ERROR_REQUEST_HTTP_404
|
||||
405 -> ERROR_REQUEST_HTTP_405
|
||||
410 -> ERROR_REQUEST_HTTP_410
|
||||
424 -> ERROR_REQUEST_HTTP_424
|
||||
500 -> ERROR_REQUEST_HTTP_500
|
||||
503 -> ERROR_REQUEST_HTTP_503
|
||||
else -> null
|
||||
}
|
||||
fun Throwable.toErrorCode() = when (this) {
|
||||
is UnknownHostException -> ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND
|
||||
is SSLException -> ERROR_REQUEST_FAILURE_SSL_ERROR
|
||||
is SocketTimeoutException -> ERROR_REQUEST_FAILURE_TIMEOUT
|
||||
is InterruptedIOException, is ConnectException -> ERROR_REQUEST_FAILURE_NO_INTERNET
|
||||
is SzkolnyApiException -> this.error?.toErrorCode()
|
||||
else -> null
|
||||
}
|
||||
private fun ApiResponse.Error.toErrorCode() = when (this.code) {
|
||||
else -> ERROR_API_EXCEPTION
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ const val CODE_LIBRUS_DISCONNECTED = 31
|
||||
const val CODE_PROFILE_ARCHIVED = 30*/
|
||||
|
||||
const val ERROR_APP_CRASH = 1
|
||||
const val ERROR_EXCEPTION = 2
|
||||
const val ERROR_API_EXCEPTION = 3
|
||||
const val ERROR_MESSAGE_NOT_SENT = 10
|
||||
|
||||
const val ERROR_REQUEST_FAILURE = 50
|
||||
|
@ -9,10 +9,20 @@ import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_API_EXCEPTION
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_EXCEPTION
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
|
||||
import pl.szczodrzynski.edziennik.stackTraceString
|
||||
import pl.szczodrzynski.edziennik.toErrorCode
|
||||
|
||||
class ApiError(val tag: String, var errorCode: Int) {
|
||||
companion object {
|
||||
fun fromThrowable(tag: String, throwable: Throwable) =
|
||||
ApiError(tag, throwable.toErrorCode() ?: ERROR_EXCEPTION)
|
||||
.withThrowable(throwable)
|
||||
}
|
||||
|
||||
val id = System.currentTimeMillis()
|
||||
var profileId: Int? = null
|
||||
var throwable: Throwable? = null
|
||||
@ -58,6 +68,8 @@ class ApiError(val tag: String, var errorCode: Int) {
|
||||
}
|
||||
|
||||
fun getStringReason(context: Context): String {
|
||||
if (errorCode == ERROR_API_EXCEPTION && throwable is SzkolnyApiException)
|
||||
return throwable?.message.toString()
|
||||
return context.resources.getIdentifier("error_${errorCode}_reason", "string", context.packageName).let {
|
||||
if (it != 0)
|
||||
context.getString(it)
|
||||
|
@ -6,21 +6,13 @@ import androidx.core.util.size
|
||||
import androidx.room.OnConflictStrategy
|
||||
import com.google.gson.JsonObject
|
||||
import im.wangchao.mhttp.Response
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback
|
||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.*
|
||||
import pl.szczodrzynski.edziennik.singleOrNull
|
||||
import pl.szczodrzynski.edziennik.toSparseArray
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.values
|
||||
import java.io.InterruptedIOException
|
||||
import java.net.ConnectException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import javax.net.ssl.SSLException
|
||||
|
||||
abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) {
|
||||
companion object {
|
||||
@ -347,27 +339,12 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
|
||||
}
|
||||
|
||||
fun error(apiError: ApiError) {
|
||||
apiError.errorCode = when (apiError.throwable) {
|
||||
is UnknownHostException -> ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND
|
||||
is SSLException -> ERROR_REQUEST_FAILURE_SSL_ERROR
|
||||
is SocketTimeoutException -> ERROR_REQUEST_FAILURE_TIMEOUT
|
||||
is InterruptedIOException, is ConnectException -> ERROR_REQUEST_FAILURE_NO_INTERNET
|
||||
else ->
|
||||
apiError.errorCode = apiError.throwable?.toErrorCode() ?:
|
||||
if (apiError.errorCode == ERROR_REQUEST_FAILURE)
|
||||
when (apiError.response?.code()) {
|
||||
400 -> ERROR_REQUEST_HTTP_400
|
||||
401 -> ERROR_REQUEST_HTTP_401
|
||||
403 -> ERROR_REQUEST_HTTP_403
|
||||
404 -> ERROR_REQUEST_HTTP_404
|
||||
405 -> ERROR_REQUEST_HTTP_405
|
||||
410 -> ERROR_REQUEST_HTTP_410
|
||||
424 -> ERROR_REQUEST_HTTP_424
|
||||
500 -> ERROR_REQUEST_HTTP_500
|
||||
503 -> ERROR_REQUEST_HTTP_503
|
||||
else -> apiError.errorCode
|
||||
}
|
||||
else apiError.errorCode
|
||||
}
|
||||
apiError.response?.toErrorCode() ?: apiError.errorCode
|
||||
else
|
||||
apiError.errorCode
|
||||
|
||||
callback.onError(apiError)
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,17 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.szkolny
|
||||
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.gson.GsonBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
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
|
||||
@ -22,21 +29,33 @@ import pl.szczodrzynski.edziennik.data.db.entity.Notification
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||
import pl.szczodrzynski.edziennik.md5
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDetailsDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.create
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class SzkolnyApi(val app: App) {
|
||||
class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
companion object {
|
||||
const val TAG = "SzkolnyApi"
|
||||
}
|
||||
|
||||
private val api: SzkolnyService
|
||||
private val retrofit: Retrofit
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
init {
|
||||
val okHttpClient: OkHttpClient = app.http.newBuilder()
|
||||
.followRedirects(true)
|
||||
.callTimeout(30, SECONDS)
|
||||
.callTimeout(10, SECONDS)
|
||||
.addInterceptor(SignatureInterceptor(app))
|
||||
.build()
|
||||
|
||||
@ -47,7 +66,7 @@ class SzkolnyApi(val app: App) {
|
||||
.registerTypeAdapter(Time::class.java, TimeAdapter())
|
||||
.create())
|
||||
|
||||
val retrofit: Retrofit = Retrofit.Builder()
|
||||
retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://api.szkolny.eu/")
|
||||
.addConverterFactory(gsonConverterFactory)
|
||||
.client(okHttpClient)
|
||||
@ -56,6 +75,68 @@ class SzkolnyApi(val app: App) {
|
||||
api = retrofit.create()
|
||||
}
|
||||
|
||||
suspend inline fun <T> runCatching(errorSnackbar: ErrorSnackbar, crossinline block: SzkolnyApi.() -> T?): T? {
|
||||
return try {
|
||||
withContext(Dispatchers.Default) { block() }
|
||||
}
|
||||
catch (e: Exception) {
|
||||
errorSnackbar.addError(ApiError.fromThrowable(TAG, e)).show()
|
||||
null
|
||||
}
|
||||
}
|
||||
suspend inline fun <T> runCatching(activity: AppCompatActivity, crossinline block: SzkolnyApi.() -> T?): T? {
|
||||
return try {
|
||||
withContext(Dispatchers.Default) { block() }
|
||||
}
|
||||
catch (e: Exception) {
|
||||
ErrorDetailsDialog(
|
||||
activity,
|
||||
listOf(ApiError.fromThrowable(TAG, e)),
|
||||
R.string.error_occured
|
||||
)
|
||||
null
|
||||
}
|
||||
}
|
||||
inline fun <T> runCatching(block: SzkolnyApi.() -> T, onError: (e: Throwable) -> Unit): T? {
|
||||
return try {
|
||||
block()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
onError(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a server request returned a successful response.
|
||||
*
|
||||
* If not, throw a [SzkolnyApiException] containing an [ApiResponse.Error],
|
||||
* or null if it's a HTTP call error.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
private inline fun <reified T> parseResponse(response: Response<ApiResponse<T>>): T {
|
||||
if (response.isSuccessful && response.body()?.success == true) {
|
||||
if (Unit is T) {
|
||||
return Unit
|
||||
}
|
||||
if (response.body()?.data != null) {
|
||||
return response.body()?.data!!
|
||||
}
|
||||
}
|
||||
|
||||
val body = response.body() ?: response.errorBody()?.let {
|
||||
try {
|
||||
retrofit.responseBodyConverter<ApiResponse<T>>(ApiResponse::class.java, arrayOf()).convert(it)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
throw SzkolnyApiException(body?.errors?.firstOrNull())
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun getDevice() = run {
|
||||
val config = app.config
|
||||
val device = Device(
|
||||
@ -78,6 +159,7 @@ class SzkolnyApi(val app: App) {
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> {
|
||||
val teams = app.db.teamDao().allNow
|
||||
|
||||
@ -104,11 +186,12 @@ class SzkolnyApi(val app: App) {
|
||||
}
|
||||
},
|
||||
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
|
||||
val events = mutableListOf<EventFull>()
|
||||
|
||||
response?.data?.events?.forEach { event ->
|
||||
response.body()?.data?.events?.forEach { event ->
|
||||
if (event.id in blacklistedIds)
|
||||
return@forEach
|
||||
teams.filter { it.code == event.teamCode }.onEach { team ->
|
||||
@ -129,100 +212,117 @@ class SzkolnyApi(val app: App) {
|
||||
return events
|
||||
}
|
||||
|
||||
fun shareEvent(event: EventFull): ApiResponse<Nothing>? {
|
||||
@Throws(Exception::class)
|
||||
fun shareEvent(event: EventFull) {
|
||||
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
|
||||
|
||||
return api.shareEvent(EventShareRequest(
|
||||
val response = api.shareEvent(EventShareRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
sharedByName = event.sharedByName,
|
||||
shareTeamCode = team.code,
|
||||
event = event
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
}
|
||||
|
||||
fun unshareEvent(event: Event): ApiResponse<Nothing>? {
|
||||
@Throws(Exception::class)
|
||||
fun unshareEvent(event: Event) {
|
||||
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
|
||||
|
||||
return api.shareEvent(EventShareRequest(
|
||||
val response = api.shareEvent(EventShareRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
sharedByName = event.sharedByName,
|
||||
unshareTeamCode = team.code,
|
||||
eventId = event.id
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
}
|
||||
|
||||
/*fun eventEditRequest(requesterName: String, event: Event): ApiResponse<Nothing>? {
|
||||
|
||||
}*/
|
||||
|
||||
fun pairBrowser(browserId: String?, pairToken: String?, onError: ((List<ApiResponse.Error>) -> Unit)? = null): List<WebPushResponse.Browser> {
|
||||
@Throws(Exception::class)
|
||||
fun pairBrowser(browserId: String?, pairToken: String?): List<WebPushResponse.Browser> {
|
||||
val response = api.webPush(WebPushRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
action = "pairBrowser",
|
||||
browserId = browserId,
|
||||
pairToken = pairToken
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
|
||||
response?.errors?.let {
|
||||
onError?.invoke(it)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return response?.data?.browsers ?: emptyList()
|
||||
return response.body()?.data?.browsers ?: emptyList()
|
||||
}
|
||||
|
||||
fun listBrowsers(onError: ((List<ApiResponse.Error>) -> Unit)? = null): List<WebPushResponse.Browser> {
|
||||
@Throws(Exception::class)
|
||||
fun listBrowsers(): List<WebPushResponse.Browser> {
|
||||
val response = api.webPush(WebPushRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
action = "listBrowsers"
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
|
||||
return response?.data?.browsers ?: emptyList()
|
||||
return response.body()?.data?.browsers ?: emptyList()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun unpairBrowser(browserId: String): List<WebPushResponse.Browser> {
|
||||
val response = api.webPush(WebPushRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
action = "unpairBrowser",
|
||||
browserId = browserId
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
|
||||
return response?.data?.browsers ?: emptyList()
|
||||
return response.body()?.data?.browsers ?: emptyList()
|
||||
}
|
||||
|
||||
fun errorReport(errors: List<ErrorReportRequest.Error>): ApiResponse<Nothing>? {
|
||||
return api.errorReport(ErrorReportRequest(
|
||||
@Throws(Exception::class)
|
||||
fun errorReport(errors: List<ErrorReportRequest.Error>) {
|
||||
val response = api.errorReport(ErrorReportRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
appVersion = BuildConfig.VERSION_NAME,
|
||||
errors = errors
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
}
|
||||
|
||||
fun unregisterAppUser(userCode: String): ApiResponse<Nothing>? {
|
||||
return api.appUser(AppUserRequest(
|
||||
@Throws(Exception::class)
|
||||
fun unregisterAppUser(userCode: String) {
|
||||
val response = api.appUser(AppUserRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
userCode = userCode
|
||||
)).execute().body()
|
||||
)).execute()
|
||||
parseResponse(response)
|
||||
}
|
||||
|
||||
fun getUpdate(channel: String): ApiResponse<List<Update>>? {
|
||||
return api.updates(channel).execute().body()
|
||||
@Throws(Exception::class)
|
||||
fun getUpdate(channel: String): List<Update> {
|
||||
val response = api.updates(channel).execute()
|
||||
parseResponse(response)
|
||||
|
||||
return response.body()?.data ?: emptyList()
|
||||
}
|
||||
|
||||
fun sendFeedbackMessage(senderName: String?, targetDeviceId: String?, text: String): FeedbackMessage? {
|
||||
return api.feedbackMessage(FeedbackMessageRequest(
|
||||
@Throws(Exception::class)
|
||||
fun sendFeedbackMessage(senderName: String?, targetDeviceId: String?, text: String): FeedbackMessage {
|
||||
val response = api.feedbackMessage(FeedbackMessageRequest(
|
||||
deviceId = app.deviceId,
|
||||
device = getDevice(),
|
||||
senderName = senderName,
|
||||
targetDeviceId = targetDeviceId,
|
||||
text = text
|
||||
)).execute().body()?.data?.message
|
||||
)).execute()
|
||||
val data = parseResponse(response)
|
||||
|
||||
return data.message
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-16.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.szkolny
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
|
||||
|
||||
class SzkolnyApiException(val error: ApiResponse.Error?) : Exception(if (error == null) "Error body does not contain a valid Error." else "${error.code}: ${error.reason}")
|
@ -18,16 +18,16 @@ interface SzkolnyService {
|
||||
fun serverSync(@Body request: ServerSyncRequest): Call<ApiResponse<ServerSyncResponse>>
|
||||
|
||||
@POST("share")
|
||||
fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Nothing>>
|
||||
fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Unit>>
|
||||
|
||||
@POST("webPush")
|
||||
fun webPush(@Body request: WebPushRequest): Call<ApiResponse<WebPushResponse>>
|
||||
|
||||
@POST("errorReport")
|
||||
fun errorReport(@Body request: ErrorReportRequest): Call<ApiResponse<Nothing>>
|
||||
fun errorReport(@Body request: ErrorReportRequest): Call<ApiResponse<Unit>>
|
||||
|
||||
@POST("appUser")
|
||||
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Nothing>>
|
||||
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Unit>>
|
||||
|
||||
@GET("updates/app")
|
||||
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>
|
||||
|
@ -76,13 +76,15 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker(
|
||||
try {
|
||||
val update = overrideUpdate
|
||||
?: run {
|
||||
val response = withContext(Dispatchers.Default) { SzkolnyApi(app).getUpdate("beta") }
|
||||
if (response?.success != true) {
|
||||
Toast.makeText(app, app.getString(R.string.notification_cant_check_update), Toast.LENGTH_SHORT).show()
|
||||
return@run null
|
||||
}
|
||||
val updates = response.data
|
||||
if (updates?.isNotEmpty() != true) {
|
||||
val updates = withContext(Dispatchers.Default) {
|
||||
SzkolnyApi(app).runCatching({
|
||||
getUpdate("beta")
|
||||
}, {
|
||||
Toast.makeText(app, app.getString(R.string.notification_cant_check_update), Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
} ?: return@run null
|
||||
|
||||
if (updates.isEmpty()) {
|
||||
app.config.update = null
|
||||
Toast.makeText(app, app.getString(R.string.notification_no_update), Toast.LENGTH_SHORT).show()
|
||||
return@run null
|
||||
|
@ -150,23 +150,21 @@ class EventDetailsDialog(
|
||||
private fun removeEvent() {
|
||||
launch {
|
||||
if (eventShared && eventOwn) {
|
||||
Toast.makeText(activity, "Unshare + remove own event", Toast.LENGTH_SHORT).show()
|
||||
// unshare + remove own event
|
||||
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show()
|
||||
|
||||
val response = withContext(Dispatchers.Default) {
|
||||
api.unshareEvent(event)
|
||||
}
|
||||
|
||||
response?.errors?.ifNotEmpty {
|
||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
||||
return@launch
|
||||
}
|
||||
api.runCatching(activity) {
|
||||
unshareEvent(event)
|
||||
} ?: return@launch
|
||||
|
||||
finishRemoving()
|
||||
} else if (eventShared && !eventOwn) {
|
||||
Toast.makeText(activity, "Remove + blacklist somebody's event", Toast.LENGTH_SHORT).show()
|
||||
// remove + blacklist somebody's event
|
||||
Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show()
|
||||
// TODO
|
||||
} else {
|
||||
Toast.makeText(activity, "Remove event", Toast.LENGTH_SHORT).show()
|
||||
// remove event
|
||||
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
||||
finishRemoving()
|
||||
}
|
||||
}
|
||||
|
@ -621,14 +621,9 @@ class EventManualDialog(
|
||||
sharedByName = profile?.studentNameLong
|
||||
}
|
||||
|
||||
val response = withContext(Dispatchers.Default) {
|
||||
api.unshareEvent(eventObject)
|
||||
}
|
||||
|
||||
response?.errors?.ifNotEmpty {
|
||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
||||
return@launch
|
||||
}
|
||||
api.runCatching(activity) {
|
||||
unshareEvent(eventObject)
|
||||
} ?: return@launch
|
||||
|
||||
eventObject.sharedByName = null
|
||||
finishAdding(eventObject, metadataObject)
|
||||
@ -643,14 +638,9 @@ class EventManualDialog(
|
||||
|
||||
metadataObject.addedDate = System.currentTimeMillis()
|
||||
|
||||
val response = withContext(Dispatchers.Default) {
|
||||
api.shareEvent(eventObject.withMetadata(metadataObject))
|
||||
}
|
||||
|
||||
response?.errors?.ifNotEmpty {
|
||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
||||
return@launch
|
||||
}
|
||||
api.runCatching(activity) {
|
||||
shareEvent(eventObject.withMetadata(metadataObject))
|
||||
} ?: return@launch
|
||||
|
||||
eventObject.sharedBy = "self"
|
||||
finishAdding(eventObject, metadataObject)
|
||||
@ -664,22 +654,20 @@ class EventManualDialog(
|
||||
private fun removeEvent() {
|
||||
launch {
|
||||
if (editingShared && editingOwn) {
|
||||
// unshare + remove own event
|
||||
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show()
|
||||
|
||||
val response = withContext(Dispatchers.Default) {
|
||||
api.unshareEvent(editingEvent!!)
|
||||
}
|
||||
|
||||
response?.errors?.ifNotEmpty {
|
||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
||||
return@launch
|
||||
}
|
||||
api.runCatching(activity) {
|
||||
unshareEvent(editingEvent!!)
|
||||
} ?: return@launch
|
||||
|
||||
finishRemoving()
|
||||
} else if (editingShared && !editingOwn) {
|
||||
// remove + blacklist somebody's event
|
||||
Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show()
|
||||
// TODO
|
||||
} else {
|
||||
// remove event
|
||||
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
||||
finishRemoving()
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import pl.szczodrzynski.edziennik.data.api.ERROR_APP_CRASH
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.ifNotEmpty
|
||||
import pl.szczodrzynski.edziennik.utils.Themes.appTheme
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -86,22 +85,17 @@ class CrashActivity : AppCompatActivity(), CoroutineScope {
|
||||
.show()
|
||||
} else {
|
||||
launch {
|
||||
val response = withContext(Dispatchers.Default) {
|
||||
api.errorReport(listOf(getReportableError(intent)))
|
||||
}
|
||||
api.runCatching({
|
||||
withContext(Dispatchers.Default) {
|
||||
errorReport(listOf(getReportableError(intent)))
|
||||
}
|
||||
}, {
|
||||
Toast.makeText(app, getString(R.string.crash_report_cannot_send) + it, Toast.LENGTH_LONG).show()
|
||||
}) ?: return@launch
|
||||
|
||||
response?.errors?.ifNotEmpty {
|
||||
Toast.makeText(app, getString(R.string.crash_report_cannot_send) + ": " + it[0].reason, Toast.LENGTH_LONG).show()
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
Toast.makeText(app, getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show()
|
||||
reportButton.isEnabled = false
|
||||
reportButton.setTextColor(resources.getColor(android.R.color.darker_gray))
|
||||
} else {
|
||||
Toast.makeText(app, getString(R.string.crash_report_cannot_send) + " JsonObject equals null", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
Toast.makeText(app, getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show()
|
||||
reportButton.isEnabled = false
|
||||
reportButton.setTextColor(resources.getColor(android.R.color.darker_gray))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-2-16.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.ui.modules.error
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class ErrorDetailsDialog(
|
||||
val activity: AppCompatActivity,
|
||||
val errors: List<ApiError>,
|
||||
val titleRes: Int = R.string.dialog_error_details_title,
|
||||
val onShowListener: ((tag: String) -> Unit)? = null,
|
||||
val onDismissListener: ((tag: String) -> Unit)? = null
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "ApiErrorDialog"
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
private val job = Job()
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = job + Dispatchers.Main
|
||||
|
||||
private val api by lazy { SzkolnyApi(activity.applicationContext as App) }
|
||||
|
||||
init { run {
|
||||
if (activity.isFinishing)
|
||||
return@run
|
||||
onShowListener?.invoke(TAG)
|
||||
app = activity.applicationContext as App
|
||||
|
||||
if (errors.isNotEmpty()) {
|
||||
val message = errors.map {
|
||||
listOf(
|
||||
it.getStringReason(activity).asBoldSpannable().asColoredSpannable(R.attr.colorOnBackground.resolveAttr(activity)),
|
||||
activity.getString(R.string.error_unknown_format, it.errorCode, it.tag),
|
||||
if (App.devMode)
|
||||
it.throwable?.stackTraceString ?: it.throwable?.localizedMessage
|
||||
else
|
||||
it.throwable?.localizedMessage
|
||||
).concat("\n")
|
||||
}.concat("\n\n")
|
||||
|
||||
dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(titleRes)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNeutralButton(R.string.report) { _, _ ->
|
||||
launch {
|
||||
api.runCatching({
|
||||
withContext(Dispatchers.Default) {
|
||||
errorReport(errors.map { it.toReportableError(activity) })
|
||||
}
|
||||
}, {
|
||||
Toast.makeText(activity, activity.getString(R.string.crash_report_cannot_send) + it, Toast.LENGTH_LONG).show()
|
||||
}) ?: return@launch
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
.setCancelable(false)
|
||||
.setOnDismissListener {
|
||||
onDismissListener?.invoke(TAG)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}}
|
||||
}
|
@ -5,16 +5,15 @@
|
||||
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 kotlinx.coroutines.*
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -31,47 +30,12 @@ class ErrorSnackbar(val activity: AppCompatActivity) : CoroutineScope {
|
||||
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 {
|
||||
listOf(
|
||||
it.getStringReason(activity).asBoldSpannable().asColoredSpannable(R.attr.colorOnBackground.resolveAttr(activity)),
|
||||
activity.getString(R.string.error_unknown_format, it.errorCode, it.tag),
|
||||
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)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.ok) { dialog, _ ->
|
||||
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()
|
||||
}
|
||||
ErrorDetailsDialog(activity, errors)
|
||||
errors.clear()
|
||||
}
|
||||
val bgColor = ColorUtils.compositeColors(
|
||||
getColorFromAttr(activity, R.attr.colorOnSurface) and 0xcfffffff.toInt(),
|
||||
|
@ -239,22 +239,15 @@ class FeedbackFragment : Fragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
launch {
|
||||
val message = withContext(Dispatchers.Default) {
|
||||
try {
|
||||
api.sendFeedbackMessage(
|
||||
senderName = App.profile.accountName ?: App.profile.studentNameLong,
|
||||
targetDeviceId = if (isDev) currentDeviceId else null,
|
||||
text = text
|
||||
)?.also {
|
||||
app.db.feedbackMessageDao().add(it)
|
||||
}
|
||||
} catch (ignore: Exception) { null }
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
Toast.makeText(app, "Nie udało się wysłać wiadomości.", Toast.LENGTH_SHORT).show()
|
||||
return@launch
|
||||
}
|
||||
val message = api.runCatching(activity.errorSnackbar) {
|
||||
val message = api.sendFeedbackMessage(
|
||||
senderName = App.profile.accountName ?: App.profile.studentNameLong,
|
||||
targetDeviceId = if (isDev) currentDeviceId else null,
|
||||
text = text
|
||||
)
|
||||
app.db.feedbackMessageDao().add(message)
|
||||
message
|
||||
} ?: return@launch
|
||||
|
||||
b.chatLayout.visibility = View.VISIBLE
|
||||
b.inputLayout.visibility = View.GONE
|
||||
|
@ -915,7 +915,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
|
||||
.negativeText(R.string.abort)
|
||||
.show();
|
||||
AsyncTask.execute(() -> {
|
||||
new SzkolnyApi(app).unregisterAppUser(app.getProfile().getUserCode());
|
||||
new SzkolnyApi(app).runCatching(szkolnyApi -> null, szkolnyApi -> null);
|
||||
activity.runOnUiThread(() -> {
|
||||
progressDialog.dismiss();
|
||||
Toast.makeText(activity, getString(R.string.settings_register_allow_registration_dialog_disabling_finished), Toast.LENGTH_SHORT).show();
|
||||
|
@ -15,7 +15,10 @@ import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||
@ -93,9 +96,9 @@ class WebPushFragment : Fragment(), CoroutineScope {
|
||||
)
|
||||
|
||||
launch {
|
||||
val browsers = withContext(Dispatchers.Default) {
|
||||
api.listBrowsers()
|
||||
}
|
||||
val browsers = api.runCatching(activity.errorSnackbar) {
|
||||
listBrowsers()
|
||||
} ?: return@launch
|
||||
updateBrowserList(browsers)
|
||||
}
|
||||
}
|
||||
@ -128,21 +131,22 @@ class WebPushFragment : Fragment(), CoroutineScope {
|
||||
b.tokenEditText.isEnabled = false
|
||||
b.tokenEditText.clearFocus()
|
||||
launch {
|
||||
val browsers = withContext(Dispatchers.Default) {
|
||||
api.pairBrowser(browserId, pairToken)
|
||||
val browsers = api.runCatching(activity.errorSnackbar) {
|
||||
pairBrowser(browserId, pairToken)
|
||||
}
|
||||
b.scanQrCode.isEnabled = true
|
||||
b.tokenAccept.isEnabled = true
|
||||
b.tokenEditText.isEnabled = true
|
||||
updateBrowserList(browsers)
|
||||
if (browsers != null)
|
||||
updateBrowserList(browsers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unpairBrowser(browserId: String) {
|
||||
launch {
|
||||
val browsers = withContext(Dispatchers.Default) {
|
||||
api.unpairBrowser(browserId)
|
||||
}
|
||||
val browsers = api.runCatching(activity.errorSnackbar) {
|
||||
unpairBrowser(browserId)
|
||||
} ?: return@launch
|
||||
updateBrowserList(browsers)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user