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.RequestBody
|
||||||
import okhttp3.TlsVersion
|
import okhttp3.TlsVersion
|
||||||
import okio.Buffer
|
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.Notification
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
import pl.szczodrzynski.edziennik.data.db.entity.Team
|
||||||
import pl.szczodrzynski.edziennik.network.TLSSocketFactory
|
import pl.szczodrzynski.edziennik.network.TLSSocketFactory
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
import java.io.InterruptedIOException
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import java.net.UnknownHostException
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
@ -67,6 +74,7 @@ import java.util.zip.CRC32
|
|||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.SSLException
|
||||||
import javax.net.ssl.TrustManagerFactory
|
import javax.net.ssl.TrustManagerFactory
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
import kotlin.Pair
|
import kotlin.Pair
|
||||||
@ -1034,5 +1042,29 @@ fun CharSequence.containsAll(list: List<CharSequence>, ignoreCase: Boolean = fal
|
|||||||
return true
|
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) }
|
= 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 CODE_PROFILE_ARCHIVED = 30*/
|
||||||
|
|
||||||
const val ERROR_APP_CRASH = 1
|
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_MESSAGE_NOT_SENT = 10
|
||||||
|
|
||||||
const val ERROR_REQUEST_FAILURE = 50
|
const val ERROR_REQUEST_FAILURE = 50
|
||||||
|
@ -9,10 +9,20 @@ import com.google.gson.JsonObject
|
|||||||
import im.wangchao.mhttp.Request
|
import im.wangchao.mhttp.Request
|
||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import pl.szczodrzynski.edziennik.R
|
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.data.api.szkolny.request.ErrorReportRequest
|
||||||
import pl.szczodrzynski.edziennik.stackTraceString
|
import pl.szczodrzynski.edziennik.stackTraceString
|
||||||
|
import pl.szczodrzynski.edziennik.toErrorCode
|
||||||
|
|
||||||
class ApiError(val tag: String, var errorCode: Int) {
|
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()
|
val id = System.currentTimeMillis()
|
||||||
var profileId: Int? = null
|
var profileId: Int? = null
|
||||||
var throwable: Throwable? = null
|
var throwable: Throwable? = null
|
||||||
@ -58,6 +68,8 @@ class ApiError(val tag: String, var errorCode: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getStringReason(context: Context): String {
|
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 {
|
return context.resources.getIdentifier("error_${errorCode}_reason", "string", context.packageName).let {
|
||||||
if (it != 0)
|
if (it != 0)
|
||||||
context.getString(it)
|
context.getString(it)
|
||||||
|
@ -6,21 +6,13 @@ import androidx.core.util.size
|
|||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import im.wangchao.mhttp.Response
|
import im.wangchao.mhttp.Response
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
|
||||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback
|
import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback
|
||||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.*
|
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.Utils.d
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
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) {
|
abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) {
|
||||||
companion object {
|
companion object {
|
||||||
@ -347,27 +339,12 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun error(apiError: ApiError) {
|
fun error(apiError: ApiError) {
|
||||||
apiError.errorCode = when (apiError.throwable) {
|
apiError.errorCode = apiError.throwable?.toErrorCode() ?:
|
||||||
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 ->
|
|
||||||
if (apiError.errorCode == ERROR_REQUEST_FAILURE)
|
if (apiError.errorCode == ERROR_REQUEST_FAILURE)
|
||||||
when (apiError.response?.code()) {
|
apiError.response?.toErrorCode() ?: apiError.errorCode
|
||||||
400 -> ERROR_REQUEST_HTTP_400
|
else
|
||||||
401 -> ERROR_REQUEST_HTTP_401
|
apiError.errorCode
|
||||||
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
|
|
||||||
}
|
|
||||||
callback.onError(apiError)
|
callback.onError(apiError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,17 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.api.szkolny
|
package pl.szczodrzynski.edziennik.data.api.szkolny
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.google.gson.GsonBuilder
|
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 okhttp3.OkHttpClient
|
||||||
import pl.szczodrzynski.edziennik.App
|
import pl.szczodrzynski.edziennik.App
|
||||||
import pl.szczodrzynski.edziennik.BuildConfig
|
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.DateAdapter
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
|
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.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.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
import pl.szczodrzynski.edziennik.md5
|
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.Date
|
||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
import retrofit2.Response
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import retrofit2.create
|
import retrofit2.create
|
||||||
import java.util.concurrent.TimeUnit.SECONDS
|
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 api: SzkolnyService
|
||||||
|
private val retrofit: Retrofit
|
||||||
|
|
||||||
|
private val job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val okHttpClient: OkHttpClient = app.http.newBuilder()
|
val okHttpClient: OkHttpClient = app.http.newBuilder()
|
||||||
.followRedirects(true)
|
.followRedirects(true)
|
||||||
.callTimeout(30, SECONDS)
|
.callTimeout(10, SECONDS)
|
||||||
.addInterceptor(SignatureInterceptor(app))
|
.addInterceptor(SignatureInterceptor(app))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@ -47,7 +66,7 @@ class SzkolnyApi(val app: App) {
|
|||||||
.registerTypeAdapter(Time::class.java, TimeAdapter())
|
.registerTypeAdapter(Time::class.java, TimeAdapter())
|
||||||
.create())
|
.create())
|
||||||
|
|
||||||
val retrofit: Retrofit = Retrofit.Builder()
|
retrofit = Retrofit.Builder()
|
||||||
.baseUrl("https://api.szkolny.eu/")
|
.baseUrl("https://api.szkolny.eu/")
|
||||||
.addConverterFactory(gsonConverterFactory)
|
.addConverterFactory(gsonConverterFactory)
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
@ -56,6 +75,68 @@ class SzkolnyApi(val app: App) {
|
|||||||
api = retrofit.create()
|
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 {
|
private fun getDevice() = run {
|
||||||
val config = app.config
|
val config = app.config
|
||||||
val device = Device(
|
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> {
|
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> {
|
||||||
val teams = app.db.teamDao().allNow
|
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) }
|
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
|
||||||
)).execute().body()
|
)).execute()
|
||||||
|
parseResponse(response)
|
||||||
|
|
||||||
val events = mutableListOf<EventFull>()
|
val events = mutableListOf<EventFull>()
|
||||||
|
|
||||||
response?.data?.events?.forEach { event ->
|
response.body()?.data?.events?.forEach { event ->
|
||||||
if (event.id in blacklistedIds)
|
if (event.id in blacklistedIds)
|
||||||
return@forEach
|
return@forEach
|
||||||
teams.filter { it.code == event.teamCode }.onEach { team ->
|
teams.filter { it.code == event.teamCode }.onEach { team ->
|
||||||
@ -129,100 +212,117 @@ class SzkolnyApi(val app: App) {
|
|||||||
return events
|
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)
|
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
|
||||||
|
|
||||||
return api.shareEvent(EventShareRequest(
|
val response = api.shareEvent(EventShareRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
sharedByName = event.sharedByName,
|
sharedByName = event.sharedByName,
|
||||||
shareTeamCode = team.code,
|
shareTeamCode = team.code,
|
||||||
event = event
|
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)
|
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
|
||||||
|
|
||||||
return api.shareEvent(EventShareRequest(
|
val response = api.shareEvent(EventShareRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
sharedByName = event.sharedByName,
|
sharedByName = event.sharedByName,
|
||||||
unshareTeamCode = team.code,
|
unshareTeamCode = team.code,
|
||||||
eventId = event.id
|
eventId = event.id
|
||||||
)).execute().body()
|
)).execute()
|
||||||
|
parseResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*fun eventEditRequest(requesterName: String, event: Event): ApiResponse<Nothing>? {
|
/*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(
|
val response = api.webPush(WebPushRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
action = "pairBrowser",
|
action = "pairBrowser",
|
||||||
browserId = browserId,
|
browserId = browserId,
|
||||||
pairToken = pairToken
|
pairToken = pairToken
|
||||||
)).execute().body()
|
)).execute()
|
||||||
|
parseResponse(response)
|
||||||
|
|
||||||
response?.errors?.let {
|
return response.body()?.data?.browsers ?: emptyList()
|
||||||
onError?.invoke(it)
|
|
||||||
return emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response?.data?.browsers ?: emptyList()
|
@Throws(Exception::class)
|
||||||
}
|
fun listBrowsers(): List<WebPushResponse.Browser> {
|
||||||
|
|
||||||
fun listBrowsers(onError: ((List<ApiResponse.Error>) -> Unit)? = null): List<WebPushResponse.Browser> {
|
|
||||||
val response = api.webPush(WebPushRequest(
|
val response = api.webPush(WebPushRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
action = "listBrowsers"
|
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> {
|
fun unpairBrowser(browserId: String): List<WebPushResponse.Browser> {
|
||||||
val response = api.webPush(WebPushRequest(
|
val response = api.webPush(WebPushRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
action = "unpairBrowser",
|
action = "unpairBrowser",
|
||||||
browserId = browserId
|
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>? {
|
@Throws(Exception::class)
|
||||||
return api.errorReport(ErrorReportRequest(
|
fun errorReport(errors: List<ErrorReportRequest.Error>) {
|
||||||
|
val response = api.errorReport(ErrorReportRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
appVersion = BuildConfig.VERSION_NAME,
|
appVersion = BuildConfig.VERSION_NAME,
|
||||||
errors = errors
|
errors = errors
|
||||||
)).execute().body()
|
)).execute()
|
||||||
|
parseResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterAppUser(userCode: String): ApiResponse<Nothing>? {
|
@Throws(Exception::class)
|
||||||
return api.appUser(AppUserRequest(
|
fun unregisterAppUser(userCode: String) {
|
||||||
|
val response = api.appUser(AppUserRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
userCode = userCode
|
userCode = userCode
|
||||||
)).execute().body()
|
)).execute()
|
||||||
|
parseResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUpdate(channel: String): ApiResponse<List<Update>>? {
|
@Throws(Exception::class)
|
||||||
return api.updates(channel).execute().body()
|
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? {
|
@Throws(Exception::class)
|
||||||
return api.feedbackMessage(FeedbackMessageRequest(
|
fun sendFeedbackMessage(senderName: String?, targetDeviceId: String?, text: String): FeedbackMessage {
|
||||||
|
val response = api.feedbackMessage(FeedbackMessageRequest(
|
||||||
deviceId = app.deviceId,
|
deviceId = app.deviceId,
|
||||||
device = getDevice(),
|
device = getDevice(),
|
||||||
senderName = senderName,
|
senderName = senderName,
|
||||||
targetDeviceId = targetDeviceId,
|
targetDeviceId = targetDeviceId,
|
||||||
text = text
|
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>>
|
fun serverSync(@Body request: ServerSyncRequest): Call<ApiResponse<ServerSyncResponse>>
|
||||||
|
|
||||||
@POST("share")
|
@POST("share")
|
||||||
fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Nothing>>
|
fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Unit>>
|
||||||
|
|
||||||
@POST("webPush")
|
@POST("webPush")
|
||||||
fun webPush(@Body request: WebPushRequest): Call<ApiResponse<WebPushResponse>>
|
fun webPush(@Body request: WebPushRequest): Call<ApiResponse<WebPushResponse>>
|
||||||
|
|
||||||
@POST("errorReport")
|
@POST("errorReport")
|
||||||
fun errorReport(@Body request: ErrorReportRequest): Call<ApiResponse<Nothing>>
|
fun errorReport(@Body request: ErrorReportRequest): Call<ApiResponse<Unit>>
|
||||||
|
|
||||||
@POST("appUser")
|
@POST("appUser")
|
||||||
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Nothing>>
|
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Unit>>
|
||||||
|
|
||||||
@GET("updates/app")
|
@GET("updates/app")
|
||||||
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>
|
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 {
|
try {
|
||||||
val update = overrideUpdate
|
val update = overrideUpdate
|
||||||
?: run {
|
?: run {
|
||||||
val response = withContext(Dispatchers.Default) { SzkolnyApi(app).getUpdate("beta") }
|
val updates = withContext(Dispatchers.Default) {
|
||||||
if (response?.success != true) {
|
SzkolnyApi(app).runCatching({
|
||||||
|
getUpdate("beta")
|
||||||
|
}, {
|
||||||
Toast.makeText(app, app.getString(R.string.notification_cant_check_update), Toast.LENGTH_SHORT).show()
|
Toast.makeText(app, app.getString(R.string.notification_cant_check_update), Toast.LENGTH_SHORT).show()
|
||||||
return@run null
|
})
|
||||||
}
|
} ?: return@run null
|
||||||
val updates = response.data
|
|
||||||
if (updates?.isNotEmpty() != true) {
|
if (updates.isEmpty()) {
|
||||||
app.config.update = null
|
app.config.update = null
|
||||||
Toast.makeText(app, app.getString(R.string.notification_no_update), Toast.LENGTH_SHORT).show()
|
Toast.makeText(app, app.getString(R.string.notification_no_update), Toast.LENGTH_SHORT).show()
|
||||||
return@run null
|
return@run null
|
||||||
|
@ -150,23 +150,21 @@ class EventDetailsDialog(
|
|||||||
private fun removeEvent() {
|
private fun removeEvent() {
|
||||||
launch {
|
launch {
|
||||||
if (eventShared && eventOwn) {
|
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.runCatching(activity) {
|
||||||
api.unshareEvent(event)
|
unshareEvent(event)
|
||||||
}
|
} ?: return@launch
|
||||||
|
|
||||||
response?.errors?.ifNotEmpty {
|
|
||||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
finishRemoving()
|
finishRemoving()
|
||||||
} else if (eventShared && !eventOwn) {
|
} 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
|
// TODO
|
||||||
} else {
|
} 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()
|
finishRemoving()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -621,14 +621,9 @@ class EventManualDialog(
|
|||||||
sharedByName = profile?.studentNameLong
|
sharedByName = profile?.studentNameLong
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = withContext(Dispatchers.Default) {
|
api.runCatching(activity) {
|
||||||
api.unshareEvent(eventObject)
|
unshareEvent(eventObject)
|
||||||
}
|
} ?: return@launch
|
||||||
|
|
||||||
response?.errors?.ifNotEmpty {
|
|
||||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
eventObject.sharedByName = null
|
eventObject.sharedByName = null
|
||||||
finishAdding(eventObject, metadataObject)
|
finishAdding(eventObject, metadataObject)
|
||||||
@ -643,14 +638,9 @@ class EventManualDialog(
|
|||||||
|
|
||||||
metadataObject.addedDate = System.currentTimeMillis()
|
metadataObject.addedDate = System.currentTimeMillis()
|
||||||
|
|
||||||
val response = withContext(Dispatchers.Default) {
|
api.runCatching(activity) {
|
||||||
api.shareEvent(eventObject.withMetadata(metadataObject))
|
shareEvent(eventObject.withMetadata(metadataObject))
|
||||||
}
|
} ?: return@launch
|
||||||
|
|
||||||
response?.errors?.ifNotEmpty {
|
|
||||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
eventObject.sharedBy = "self"
|
eventObject.sharedBy = "self"
|
||||||
finishAdding(eventObject, metadataObject)
|
finishAdding(eventObject, metadataObject)
|
||||||
@ -664,22 +654,20 @@ class EventManualDialog(
|
|||||||
private fun removeEvent() {
|
private fun removeEvent() {
|
||||||
launch {
|
launch {
|
||||||
if (editingShared && editingOwn) {
|
if (editingShared && editingOwn) {
|
||||||
|
// unshare + remove own event
|
||||||
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.event_manual_unshare_remove, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
val response = withContext(Dispatchers.Default) {
|
api.runCatching(activity) {
|
||||||
api.unshareEvent(editingEvent!!)
|
unshareEvent(editingEvent!!)
|
||||||
}
|
} ?: return@launch
|
||||||
|
|
||||||
response?.errors?.ifNotEmpty {
|
|
||||||
Toast.makeText(activity, "Error: "+it[0].reason, Toast.LENGTH_SHORT).show()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
finishRemoving()
|
finishRemoving()
|
||||||
} else if (editingShared && !editingOwn) {
|
} else if (editingShared && !editingOwn) {
|
||||||
|
// remove + blacklist somebody's event
|
||||||
Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show()
|
||||||
// TODO
|
// TODO
|
||||||
} else {
|
} else {
|
||||||
|
// remove event
|
||||||
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show()
|
||||||
finishRemoving()
|
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.SzkolnyApi
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
|
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||||
import pl.szczodrzynski.edziennik.ifNotEmpty
|
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes.appTheme
|
import pl.szczodrzynski.edziennik.utils.Themes.appTheme
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -86,22 +85,17 @@ class CrashActivity : AppCompatActivity(), CoroutineScope {
|
|||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
launch {
|
launch {
|
||||||
val response = withContext(Dispatchers.Default) {
|
api.runCatching({
|
||||||
api.errorReport(listOf(getReportableError(intent)))
|
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()
|
Toast.makeText(app, getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show()
|
||||||
reportButton.isEnabled = false
|
reportButton.isEnabled = false
|
||||||
reportButton.setTextColor(resources.getColor(android.R.color.darker_gray))
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
package pl.szczodrzynski.edziennik.ui.modules.error
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import pl.szczodrzynski.edziennik.*
|
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.models.ApiError
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
|
||||||
import pl.szczodrzynski.navlib.getColorFromAttr
|
import pl.szczodrzynski.navlib.getColorFromAttr
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -31,47 +30,12 @@ class ErrorSnackbar(val activity: AppCompatActivity) : CoroutineScope {
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
private val api by lazy { SzkolnyApi(activity.applicationContext as App) }
|
|
||||||
|
|
||||||
fun setCoordinator(coordinatorLayout: CoordinatorLayout, showAbove: View? = null) {
|
fun setCoordinator(coordinatorLayout: CoordinatorLayout, showAbove: View? = null) {
|
||||||
this.coordinator = coordinatorLayout
|
this.coordinator = coordinatorLayout
|
||||||
snackbar = Snackbar.make(coordinator, R.string.snackbar_error_text, Snackbar.LENGTH_INDEFINITE)
|
snackbar = Snackbar.make(coordinator, R.string.snackbar_error_text, Snackbar.LENGTH_INDEFINITE)
|
||||||
snackbar?.setAction(R.string.more) {
|
snackbar?.setAction(R.string.more) {
|
||||||
if (errors.isNotEmpty()) {
|
ErrorDetailsDialog(activity, errors)
|
||||||
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()
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val bgColor = ColorUtils.compositeColors(
|
val bgColor = ColorUtils.compositeColors(
|
||||||
getColorFromAttr(activity, R.attr.colorOnSurface) and 0xcfffffff.toInt(),
|
getColorFromAttr(activity, R.attr.colorOnSurface) and 0xcfffffff.toInt(),
|
||||||
|
@ -239,22 +239,15 @@ class FeedbackFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
val message = withContext(Dispatchers.Default) {
|
val message = api.runCatching(activity.errorSnackbar) {
|
||||||
try {
|
val message = api.sendFeedbackMessage(
|
||||||
api.sendFeedbackMessage(
|
|
||||||
senderName = App.profile.accountName ?: App.profile.studentNameLong,
|
senderName = App.profile.accountName ?: App.profile.studentNameLong,
|
||||||
targetDeviceId = if (isDev) currentDeviceId else null,
|
targetDeviceId = if (isDev) currentDeviceId else null,
|
||||||
text = text
|
text = text
|
||||||
)?.also {
|
)
|
||||||
app.db.feedbackMessageDao().add(it)
|
app.db.feedbackMessageDao().add(message)
|
||||||
}
|
message
|
||||||
} catch (ignore: Exception) { null }
|
} ?: return@launch
|
||||||
}
|
|
||||||
|
|
||||||
if (message == null) {
|
|
||||||
Toast.makeText(app, "Nie udało się wysłać wiadomości.", Toast.LENGTH_SHORT).show()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
b.chatLayout.visibility = View.VISIBLE
|
b.chatLayout.visibility = View.VISIBLE
|
||||||
b.inputLayout.visibility = View.GONE
|
b.inputLayout.visibility = View.GONE
|
||||||
|
@ -915,7 +915,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
|
|||||||
.negativeText(R.string.abort)
|
.negativeText(R.string.abort)
|
||||||
.show();
|
.show();
|
||||||
AsyncTask.execute(() -> {
|
AsyncTask.execute(() -> {
|
||||||
new SzkolnyApi(app).unregisterAppUser(app.getProfile().getUserCode());
|
new SzkolnyApi(app).runCatching(szkolnyApi -> null, szkolnyApi -> null);
|
||||||
activity.runOnUiThread(() -> {
|
activity.runOnUiThread(() -> {
|
||||||
progressDialog.dismiss();
|
progressDialog.dismiss();
|
||||||
Toast.makeText(activity, getString(R.string.settings_register_allow_registration_dialog_disabling_finished), Toast.LENGTH_SHORT).show();
|
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.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
|
||||||
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
|
||||||
@ -93,9 +96,9 @@ class WebPushFragment : Fragment(), CoroutineScope {
|
|||||||
)
|
)
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
val browsers = withContext(Dispatchers.Default) {
|
val browsers = api.runCatching(activity.errorSnackbar) {
|
||||||
api.listBrowsers()
|
listBrowsers()
|
||||||
}
|
} ?: return@launch
|
||||||
updateBrowserList(browsers)
|
updateBrowserList(browsers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,21 +131,22 @@ class WebPushFragment : Fragment(), CoroutineScope {
|
|||||||
b.tokenEditText.isEnabled = false
|
b.tokenEditText.isEnabled = false
|
||||||
b.tokenEditText.clearFocus()
|
b.tokenEditText.clearFocus()
|
||||||
launch {
|
launch {
|
||||||
val browsers = withContext(Dispatchers.Default) {
|
val browsers = api.runCatching(activity.errorSnackbar) {
|
||||||
api.pairBrowser(browserId, pairToken)
|
pairBrowser(browserId, pairToken)
|
||||||
}
|
}
|
||||||
b.scanQrCode.isEnabled = true
|
b.scanQrCode.isEnabled = true
|
||||||
b.tokenAccept.isEnabled = true
|
b.tokenAccept.isEnabled = true
|
||||||
b.tokenEditText.isEnabled = true
|
b.tokenEditText.isEnabled = true
|
||||||
|
if (browsers != null)
|
||||||
updateBrowserList(browsers)
|
updateBrowserList(browsers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unpairBrowser(browserId: String) {
|
private fun unpairBrowser(browserId: String) {
|
||||||
launch {
|
launch {
|
||||||
val browsers = withContext(Dispatchers.Default) {
|
val browsers = api.runCatching(activity.errorSnackbar) {
|
||||||
api.unpairBrowser(browserId)
|
unpairBrowser(browserId)
|
||||||
}
|
} ?: return@launch
|
||||||
updateBrowserList(browsers)
|
updateBrowserList(browsers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user