mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2024-11-24 10:54:36 -06:00
[API/Login] Make user action handling more universal.
This commit is contained in:
parent
7ded400a30
commit
93ccdbdeb7
@ -34,6 +34,7 @@ import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import pl.droidsonroids.gif.GifDrawable
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUIRES_USER_ACTION
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.*
|
||||
@ -69,6 +70,7 @@ import pl.szczodrzynski.edziennik.ui.grades.editor.GradesEditorFragment
|
||||
import pl.szczodrzynski.edziennik.ui.home.HomeFragment
|
||||
import pl.szczodrzynski.edziennik.ui.homework.HomeworkFragment
|
||||
import pl.szczodrzynski.edziennik.ui.login.LoginActivity
|
||||
import pl.szczodrzynski.edziennik.ui.login.LoginProgressFragment
|
||||
import pl.szczodrzynski.edziennik.ui.messages.compose.MessagesComposeFragment
|
||||
import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment
|
||||
import pl.szczodrzynski.edziennik.ui.messages.single.MessageFragment
|
||||
@ -83,6 +85,7 @@ import pl.szczodrzynski.edziennik.utils.*
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.dpToPx
|
||||
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
|
||||
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.NavTarget
|
||||
import pl.szczodrzynski.navlib.*
|
||||
@ -853,7 +856,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onUserActionRequiredEvent(event: UserActionRequiredEvent) {
|
||||
app.userActionManager.execute(this, event.profileId, event.type, event.params)
|
||||
app.userActionManager.execute(this, event, UserActionManager.UserActionCallback())
|
||||
}
|
||||
|
||||
private fun fragmentToSyncName(currentFragment: Int): Int {
|
||||
@ -911,12 +914,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
false
|
||||
}
|
||||
"userActionRequired" -> {
|
||||
app.userActionManager.execute(
|
||||
this,
|
||||
extras.getInt("profileId"),
|
||||
extras.getInt("type"),
|
||||
extras.getBundle("params"),
|
||||
val event = UserActionRequiredEvent(
|
||||
profileId = extras.getInt("profileId"),
|
||||
type = extras.getEnum<UserActionRequiredEvent.Type>("type") ?: return,
|
||||
params = extras.getBundle("params") ?: return,
|
||||
errorText = 0,
|
||||
)
|
||||
app.userActionManager.execute(this, event, UserActionManager.UserActionCallback())
|
||||
true
|
||||
}
|
||||
"createManualEvent" -> {
|
||||
|
@ -84,19 +84,21 @@ class ApiService : Service() {
|
||||
runTask()
|
||||
}
|
||||
|
||||
override fun onRequiresUserAction(event: UserActionRequiredEvent) {
|
||||
app.userActionManager.sendToUser(event)
|
||||
taskRunning?.cancel()
|
||||
clearTask()
|
||||
runTask()
|
||||
}
|
||||
|
||||
override fun onError(apiError: ApiError) {
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
d(TAG, "Task $taskRunningId threw an error - $apiError")
|
||||
apiError.profileId = taskProfileId
|
||||
|
||||
if (app.userActionManager.requiresUserAction(apiError)) {
|
||||
app.userActionManager.sendToUser(apiError)
|
||||
}
|
||||
else {
|
||||
EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError))
|
||||
errorList.add(apiError)
|
||||
apiError.throwable?.printStackTrace()
|
||||
}
|
||||
EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError))
|
||||
errorList.add(apiError)
|
||||
apiError.throwable?.printStackTrace()
|
||||
|
||||
if (apiError.isCritical) {
|
||||
taskRunning?.cancel()
|
||||
|
@ -59,6 +59,9 @@ const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="
|
||||
const val LIBRUS_SYNERGIA_HOMEWORK_ATTACHMENT_URL = "https://synergia.librus.pl/homework/downloadFile"
|
||||
const val LIBRUS_SYNERGIA_MESSAGES_ATTACHMENT_URL = "https://synergia.librus.pl/wiadomosci/pobierz_zalacznik"
|
||||
|
||||
const val LIBRUS_PORTAL_RECAPTCHA_KEY = "6Lf48moUAAAAAB9ClhdvHr46gRWR"
|
||||
const val LIBRUS_PORTAL_RECAPTCHA_REFERER = "https://portal.librus.pl/rodzina/login"
|
||||
|
||||
|
||||
val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
|
||||
|
||||
|
@ -58,11 +58,7 @@ const val ERROR_INVALID_LOGIN_MODE = 110
|
||||
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
|
||||
const val ERROR_NOT_IMPLEMENTED = 112
|
||||
const val ERROR_FILE_DOWNLOAD = 113
|
||||
|
||||
const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115
|
||||
|
||||
const val ERROR_CAPTCHA_NEEDED = 3000
|
||||
const val ERROR_CAPTCHA_LIBRUS_PORTAL = 3001
|
||||
const val ERROR_REQUIRES_USER_ACTION = 114
|
||||
|
||||
const val ERROR_API_PDO_ERROR = 5000
|
||||
const val ERROR_API_INVALID_CLIENT = 5001
|
||||
@ -204,7 +200,6 @@ const val ERROR_PODLASIE_API_NO_TOKEN = 630
|
||||
const val ERROR_PODLASIE_API_OTHER = 631
|
||||
const val ERROR_PODLASIE_API_DATA_MISSING = 632
|
||||
|
||||
const val ERROR_USOS_OAUTH_LOGIN_REQUEST = 701
|
||||
const val ERROR_USOS_OAUTH_GOT_DIFFERENT_TOKEN = 702
|
||||
const val ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE = 703
|
||||
const val ERROR_USOS_NO_STUDENT_PROGRAMMES = 704
|
||||
|
@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.Librus
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin.LibrusFirstLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
@ -162,6 +163,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
return object : EdziennikCallback {
|
||||
override fun onCompleted() { callback.onCompleted() }
|
||||
override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) }
|
||||
override fun onProgress(step: Float) { callback.onProgress(step) }
|
||||
override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) }
|
||||
override fun onError(apiError: ApiError) {
|
||||
|
@ -10,6 +10,7 @@ import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.ext.*
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
@ -148,12 +149,23 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
val error = if (response.code() == 200) null else
|
||||
json.getJsonArray("errors")?.getString(0)
|
||||
?: json.getJsonObject("errors")?.entrySet()?.firstOrNull()?.value?.asString
|
||||
|
||||
if (error?.contains("robotem") == true || json.getBoolean("captchaRequired") == true) {
|
||||
data.requireUserAction(
|
||||
type = UserActionRequiredEvent.Type.RECAPTCHA,
|
||||
params = Bundle(
|
||||
"siteKey" to LIBRUS_PORTAL_RECAPTCHA_KEY,
|
||||
"referer" to LIBRUS_PORTAL_RECAPTCHA_REFERER,
|
||||
),
|
||||
errorText = R.string.notification_user_action_required_captcha_librus,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
error?.let { code ->
|
||||
when {
|
||||
code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED
|
||||
code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||
// this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set
|
||||
code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL
|
||||
code.contains("Podany adres e-mail jest nieprawidłowy.") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN
|
||||
else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR
|
||||
}.let { errorCode ->
|
||||
@ -163,12 +175,6 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (json.getBoolean("captchaRequired") == true) {
|
||||
data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json))
|
||||
return
|
||||
}
|
||||
authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL))
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.Mobidzien
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.firstlogin.MobidziennikFirstLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
@ -142,6 +143,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
return object : EdziennikCallback {
|
||||
override fun onCompleted() { callback.onCompleted() }
|
||||
override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) }
|
||||
override fun onProgress(step: Float) { callback.onProgress(step) }
|
||||
override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) }
|
||||
override fun onError(apiError: ApiError) {
|
||||
|
@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.data.PodlasieData
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.firstlogin.PodlasieFirstLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
@ -142,6 +143,10 @@ class Podlasie(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
override fun onRequiresUserAction(event: UserActionRequiredEvent) {
|
||||
callback.onRequiresUserAction(event)
|
||||
}
|
||||
|
||||
override fun onProgress(step: Float) {
|
||||
callback.onProgress(step)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.CODE_INTERNAL_LIBRUS_ACCOUNT_410
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.data.TemplateData
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.firstlogin.TemplateFirstLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
@ -108,6 +109,10 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
override fun onRequiresUserAction(event: UserActionRequiredEvent) {
|
||||
callback.onRequiresUserAction(event)
|
||||
}
|
||||
|
||||
override fun onProgress(step: Float) {
|
||||
callback.onProgress(step)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.firstlogin.UsosFirstLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.login.UsosLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
@ -88,6 +89,10 @@ class Usos(
|
||||
callback.onCompleted()
|
||||
}
|
||||
|
||||
override fun onRequiresUserAction(event: UserActionRequiredEvent) {
|
||||
callback.onRequiresUserAction(event)
|
||||
}
|
||||
|
||||
override fun onProgress(step: Float) {
|
||||
callback.onProgress(step)
|
||||
}
|
||||
|
@ -4,9 +4,11 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.usos.login
|
||||
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.DataUsos
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.usos.data.UsosApi
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.ext.Bundle
|
||||
import pl.szczodrzynski.edziennik.ext.fromQueryString
|
||||
@ -53,13 +55,16 @@ class UsosLoginApi(val data: DataUsos, val onSuccess: () -> Unit) {
|
||||
"interactivity" to "confirm_user",
|
||||
"oauth_token" to (data.oauthTokenKey ?: ""),
|
||||
)
|
||||
val params = Bundle(
|
||||
"authorizeUrl" to "$authUrl?${authParams.toQueryString()}",
|
||||
"redirectUrl" to USOS_API_OAUTH_REDIRECT_URL,
|
||||
"responseStoreKey" to "oauthLoginResponse",
|
||||
"extras" to data.loginStore.data.toBundle(),
|
||||
data.requireUserAction(
|
||||
type = UserActionRequiredEvent.Type.OAUTH,
|
||||
params = Bundle(
|
||||
"authorizeUrl" to "$authUrl?${authParams.toQueryString()}",
|
||||
"redirectUrl" to USOS_API_OAUTH_REDIRECT_URL,
|
||||
"responseStoreKey" to "oauthLoginResponse",
|
||||
"extras" to data.loginStore.data.toBundle(),
|
||||
),
|
||||
errorText = R.string.notification_user_action_required_oauth_usos,
|
||||
)
|
||||
data.error(ApiError(TAG, ERROR_USOS_OAUTH_LOGIN_REQUEST).withParams(params))
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,6 +98,7 @@ class UsosLoginApi(val data: DataUsos, val onSuccess: () -> Unit) {
|
||||
data.oauthTokenKey = accessData["oauth_token"]
|
||||
data.oauthTokenSecret = accessData["oauth_token_secret"]
|
||||
data.oauthTokenIsUser = data.oauthTokenKey != null && data.oauthTokenSecret != null
|
||||
data.loginStore.removeLoginData("oauthLoginResponse")
|
||||
|
||||
if (!data.oauthTokenIsUser)
|
||||
data.error(ApiError(TAG, ERROR_USOS_OAUTH_INCOMPLETE_RESPONSE)
|
||||
|
@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
@ -179,6 +180,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
|
||||
return object : EdziennikCallback {
|
||||
override fun onCompleted() { callback.onCompleted() }
|
||||
override fun onRequiresUserAction(event: UserActionRequiredEvent) { callback.onRequiresUserAction(event) }
|
||||
override fun onProgress(step: Float) { callback.onProgress(step) }
|
||||
override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) }
|
||||
override fun onError(apiError: ApiError) {
|
||||
|
@ -6,12 +6,14 @@ package pl.szczodrzynski.edziennik.data.api.events
|
||||
|
||||
import android.os.Bundle
|
||||
|
||||
data class UserActionRequiredEvent(val profileId: Int, val type: Int, val params: Bundle?) {
|
||||
companion object {
|
||||
const val LOGIN_DATA_MOBIDZIENNIK = 101
|
||||
const val LOGIN_DATA_LIBRUS = 102
|
||||
const val LOGIN_DATA_VULCAN = 104
|
||||
const val CAPTCHA_LIBRUS = 202
|
||||
const val OAUTH_USOS = 701
|
||||
data class UserActionRequiredEvent(
|
||||
val profileId: Int?,
|
||||
val type: Type,
|
||||
val params: Bundle,
|
||||
val errorText: Int,
|
||||
) {
|
||||
enum class Type {
|
||||
RECAPTCHA,
|
||||
OAUTH,
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.interfaces
|
||||
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.Feature
|
||||
import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
|
||||
|
||||
@ -14,4 +15,5 @@ import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
|
||||
*/
|
||||
interface EdziennikCallback : EndpointCallback {
|
||||
fun onCompleted()
|
||||
fun onRequiresUserAction(event: UserActionRequiredEvent)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package pl.szczodrzynski.edziennik.data.api.models
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.LongSparseArray
|
||||
import android.util.SparseArray
|
||||
import androidx.core.util.set
|
||||
@ -12,7 +13,8 @@ import pl.szczodrzynski.edziennik.BuildConfig
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
|
||||
import pl.szczodrzynski.edziennik.data.api.Regexes.MESSAGE_META
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
|
||||
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.*
|
||||
import pl.szczodrzynski.edziennik.ext.*
|
||||
@ -37,7 +39,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
|
||||
/**
|
||||
* A callback passed to all [Feature]s and [LoginMethod]s
|
||||
*/
|
||||
lateinit var callback: EndpointCallback
|
||||
lateinit var callback: EdziennikCallback
|
||||
|
||||
/**
|
||||
* A list of [LoginMethod]s *already fulfilled* during this sync.
|
||||
@ -374,6 +376,15 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
|
||||
callback.onError(apiError)
|
||||
}
|
||||
|
||||
fun requireUserAction(type: UserActionRequiredEvent.Type, params: Bundle, errorText: Int) {
|
||||
callback.onRequiresUserAction(UserActionRequiredEvent(
|
||||
profileId = profile?.id,
|
||||
type = type,
|
||||
params = params,
|
||||
errorText = errorText,
|
||||
))
|
||||
}
|
||||
|
||||
fun progress(step: Float) {
|
||||
callback.onProgress(step)
|
||||
}
|
||||
|
@ -22,6 +22,15 @@ fun Bundle?.getFloat(key: String, defaultValue: Float): Float {
|
||||
fun Bundle?.getString(key: String, defaultValue: String): String {
|
||||
return this?.getString(key, defaultValue) ?: defaultValue
|
||||
}
|
||||
inline fun <reified E : Enum<E>> Bundle?.getEnum(key: String): E? {
|
||||
return this?.getString(key)?.let {
|
||||
try {
|
||||
enumValueOf<E>(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Bundle?.getIntOrNull(key: String): Int? {
|
||||
return this?.get(key) as? Int
|
||||
@ -48,6 +57,7 @@ fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
|
||||
is Bundle -> putBundle(property.first, property.second as Bundle)
|
||||
is Parcelable -> putParcelable(property.first, property.second as Parcelable)
|
||||
is Array<*> -> putParcelableArray(property.first, property.second as Array<out Parcelable>)
|
||||
is Enum<*> -> putString(property.first, (property.second as Enum<*>).name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,16 @@ import pl.szczodrzynski.edziennik.databinding.RecaptchaViewBinding
|
||||
import pl.szczodrzynski.edziennik.ext.onClick
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
|
||||
|
||||
class LibrusCaptchaDialog(
|
||||
class RecaptchaPromptDialog(
|
||||
activity: AppCompatActivity,
|
||||
private val siteKey: String,
|
||||
private val referer: String,
|
||||
private val onSuccess: (recaptchaCode: String) -> Unit,
|
||||
private val onFailure: (() -> Unit)?,
|
||||
private val onCancel: (() -> Unit)?,
|
||||
onShowListener: ((tag: String) -> Unit)? = null,
|
||||
onDismissListener: ((tag: String) -> Unit)? = null,
|
||||
) : BindingDialog<RecaptchaViewBinding>(activity, onShowListener, onDismissListener) {
|
||||
override val TAG = "LibrusCaptchaDialog"
|
||||
override val TAG = "RecaptchaPromptDialog"
|
||||
|
||||
override fun getTitleRes(): Int? = null
|
||||
override fun inflate(layoutInflater: LayoutInflater) =
|
||||
@ -46,8 +48,8 @@ class LibrusCaptchaDialog(
|
||||
b.progress.visibility = View.VISIBLE
|
||||
RecaptchaDialog(
|
||||
activity,
|
||||
siteKey = "6Lf48moUAAAAAB9ClhdvHr46gRWR-CN31CXQPG2U",
|
||||
referer = "https://portal.librus.pl/rodzina/login",
|
||||
siteKey = siteKey,
|
||||
referer = referer,
|
||||
onSuccess = { recaptchaCode ->
|
||||
b.checkbox.background = checkboxBackground
|
||||
b.checkbox.foreground = checkboxForeground
|
||||
@ -67,6 +69,6 @@ class LibrusCaptchaDialog(
|
||||
|
||||
override fun onDismiss() {
|
||||
if (!success)
|
||||
onFailure?.invoke()
|
||||
onCancel?.invoke()
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.entity.Profile
|
||||
import pl.szczodrzynski.edziennik.databinding.CardHomeDebugBinding
|
||||
import pl.szczodrzynski.edziennik.ext.dp
|
||||
import pl.szczodrzynski.edziennik.ext.onClick
|
||||
import pl.szczodrzynski.edziennik.ui.captcha.LibrusCaptchaDialog
|
||||
import pl.szczodrzynski.edziennik.ui.captcha.RecaptchaPromptDialog
|
||||
import pl.szczodrzynski.edziennik.ui.home.HomeCard
|
||||
import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter
|
||||
import pl.szczodrzynski.edziennik.ui.home.HomeFragment
|
||||
@ -85,11 +85,6 @@ class HomeDebugCard(
|
||||
app.startActivity(Chucker.getLaunchIntent(activity, 1));
|
||||
}
|
||||
|
||||
b.librusCaptchaButton.onClick {
|
||||
//app.startActivity(Intent(activity, LoginLibrusCaptchaActivity::class.java))
|
||||
LibrusCaptchaDialog(activity, onSuccess = {}, onFailure = {}).show()
|
||||
}
|
||||
|
||||
b.getLogs.onClick {
|
||||
val logs = HyperLog.getDeviceLogsInFile(activity, true)
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
|
@ -65,7 +65,6 @@ object LoginInfo {
|
||||
errorCodes = mapOf(
|
||||
ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED to R.string.login_error_account_not_activated,
|
||||
ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
|
||||
ERROR_CAPTCHA_LIBRUS_PORTAL to R.string.error_3001_reason
|
||||
)
|
||||
),
|
||||
/*Mode(
|
||||
|
@ -19,7 +19,7 @@ import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_NEEDED
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUIRES_USER_ACTION
|
||||
import pl.szczodrzynski.edziennik.data.api.LOGIN_NO_ARGUMENTS
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent
|
||||
@ -29,6 +29,7 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
||||
import pl.szczodrzynski.edziennik.databinding.LoginProgressFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.ext.joinNotNullStrings
|
||||
import pl.szczodrzynski.edziennik.utils.managers.UserActionManager
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.math.max
|
||||
|
||||
@ -137,13 +138,21 @@ class LoginProgressFragment : Fragment(), CoroutineScope {
|
||||
return
|
||||
}
|
||||
|
||||
app.userActionManager.execute(activity, event.profileId, event.type, event.params, onSuccess = { params ->
|
||||
args.putAll(params)
|
||||
doFirstLogin(args)
|
||||
}, onFailure = {
|
||||
activity.error(ApiError(TAG, ERROR_CAPTCHA_NEEDED))
|
||||
nav.navigateUp()
|
||||
})
|
||||
val callback = UserActionManager.UserActionCallback(
|
||||
onSuccess = { data ->
|
||||
args.putAll(data)
|
||||
doFirstLogin(args)
|
||||
},
|
||||
onFailure = {
|
||||
activity.error(ApiError(TAG, ERROR_REQUIRES_USER_ACTION))
|
||||
nav.navigateUp()
|
||||
},
|
||||
onCancel = {
|
||||
nav.navigateUp()
|
||||
},
|
||||
)
|
||||
|
||||
app.userActionManager.execute(activity, event, callback)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
@ -16,13 +16,10 @@ import org.greenrobot.eventbus.ThreadMode
|
||||
import pl.szczodrzynski.edziennik.App
|
||||
import pl.szczodrzynski.edziennik.MainActivity
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_CAPTCHA_LIBRUS_PORTAL
|
||||
import pl.szczodrzynski.edziennik.data.api.ERROR_USOS_OAUTH_LOGIN_REQUEST
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||
import pl.szczodrzynski.edziennik.data.api.events.UserActionRequiredEvent
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.ext.*
|
||||
import pl.szczodrzynski.edziennik.ui.captcha.LibrusCaptchaDialog
|
||||
import pl.szczodrzynski.edziennik.ui.captcha.RecaptchaPromptDialog
|
||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginActivity
|
||||
import pl.szczodrzynski.edziennik.ui.login.oauth.OAuthLoginResult
|
||||
import pl.szczodrzynski.edziennik.utils.Utils.d
|
||||
@ -32,43 +29,31 @@ class UserActionManager(val app: App) {
|
||||
private const val TAG = "UserActionManager"
|
||||
}
|
||||
|
||||
fun requiresUserAction(apiError: ApiError) = when (apiError.errorCode) {
|
||||
ERROR_CAPTCHA_LIBRUS_PORTAL -> true
|
||||
ERROR_USOS_OAUTH_LOGIN_REQUEST -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun sendToUser(apiError: ApiError) {
|
||||
val type = when (apiError.errorCode) {
|
||||
ERROR_CAPTCHA_LIBRUS_PORTAL -> UserActionRequiredEvent.CAPTCHA_LIBRUS
|
||||
ERROR_USOS_OAUTH_LOGIN_REQUEST -> UserActionRequiredEvent.OAUTH_USOS
|
||||
else -> 0
|
||||
}
|
||||
|
||||
fun sendToUser(event: UserActionRequiredEvent) {
|
||||
if (EventBus.getDefault().hasSubscriberForEvent(UserActionRequiredEvent::class.java)) {
|
||||
EventBus.getDefault().post(UserActionRequiredEvent(apiError.profileId ?: -1, type, apiError.params))
|
||||
EventBus.getDefault().post(event)
|
||||
return
|
||||
}
|
||||
|
||||
val manager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val text = app.getString(when (type) {
|
||||
UserActionRequiredEvent.CAPTCHA_LIBRUS -> R.string.notification_user_action_required_captcha_librus
|
||||
UserActionRequiredEvent.OAUTH_USOS -> R.string.notification_user_action_required_oauth_usos
|
||||
else -> R.string.notification_user_action_required_text
|
||||
}, apiError.profileId)
|
||||
|
||||
val text = app.getString(event.errorText, event.profileId)
|
||||
val intent = Intent(
|
||||
app,
|
||||
MainActivity::class.java,
|
||||
"action" to "userActionRequired",
|
||||
"profileId" to (apiError.profileId ?: -1),
|
||||
"type" to type,
|
||||
"params" to apiError.params,
|
||||
app,
|
||||
MainActivity::class.java,
|
||||
"action" to "userActionRequired",
|
||||
"profileId" to event.profileId,
|
||||
"type" to event.type,
|
||||
"params" to event.params,
|
||||
)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
app,
|
||||
System.currentTimeMillis().toInt(),
|
||||
intent,
|
||||
PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag(),
|
||||
)
|
||||
val pendingIntent = PendingIntent.getActivity(app, System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_ONE_SHOT or pendingIntentFlag())
|
||||
|
||||
val notification = NotificationCompat.Builder(app, app.notificationChannelsManager.userAttention.key)
|
||||
val notification =
|
||||
NotificationCompat.Builder(app, app.notificationChannelsManager.userAttention.key)
|
||||
.setContentTitle(app.getString(R.string.notification_user_action_required_title))
|
||||
.setContentText(text)
|
||||
.setSmallIcon(R.drawable.ic_error_outline)
|
||||
@ -84,70 +69,56 @@ class UserActionManager(val app: App) {
|
||||
manager.notify(System.currentTimeMillis().toInt(), notification)
|
||||
}
|
||||
|
||||
class UserActionCallback(
|
||||
val onSuccess: ((data: Bundle) -> Unit)? = null,
|
||||
val onFailure: (() -> Unit)? = null,
|
||||
val onCancel: (() -> Unit)? = null,
|
||||
)
|
||||
|
||||
fun execute(
|
||||
activity: AppCompatActivity,
|
||||
profileId: Int?,
|
||||
type: Int,
|
||||
params: Bundle? = null,
|
||||
onSuccess: ((params: Bundle) -> Unit)? = null,
|
||||
onFailure: (() -> Unit)? = null
|
||||
activity: AppCompatActivity,
|
||||
event: UserActionRequiredEvent,
|
||||
callback: UserActionCallback,
|
||||
) {
|
||||
d(TAG, "Running user action ($type) with params: ${params?.toJsonObject()}")
|
||||
val isSuccessful = when (type) {
|
||||
UserActionRequiredEvent.CAPTCHA_LIBRUS -> executeLibrus(activity, profileId, params, onSuccess, onFailure)
|
||||
UserActionRequiredEvent.OAUTH_USOS -> executeOauth(activity, profileId, params, onSuccess, onFailure)
|
||||
else -> false
|
||||
}
|
||||
if (!isSuccessful) {
|
||||
onFailure?.invoke()
|
||||
d(TAG, "Running user action (${event.type}) with params: ${event.params}")
|
||||
val isSuccessful = when (event.type) {
|
||||
UserActionRequiredEvent.Type.RECAPTCHA -> executeRecaptcha(activity, event, callback)
|
||||
UserActionRequiredEvent.Type.OAUTH -> executeOauth(activity, event, callback)
|
||||
}
|
||||
if (!isSuccessful)
|
||||
callback.onFailure?.invoke()
|
||||
}
|
||||
|
||||
private fun executeLibrus(
|
||||
private fun executeRecaptcha(
|
||||
activity: AppCompatActivity,
|
||||
profileId: Int?,
|
||||
params: Bundle?,
|
||||
onSuccess: ((params: Bundle) -> Unit)?,
|
||||
onFailure: (() -> Unit)?,
|
||||
event: UserActionRequiredEvent,
|
||||
callback: UserActionCallback,
|
||||
): Boolean {
|
||||
if (profileId == null)
|
||||
return false
|
||||
val extras = params?.getBundle("extras")
|
||||
// show captcha dialog
|
||||
// use passed onSuccess listener, else sync profile
|
||||
LibrusCaptchaDialog(
|
||||
val siteKey = event.params.getString("siteKey") ?: return false
|
||||
val referer = event.params.getString("referer") ?: return false
|
||||
RecaptchaPromptDialog(
|
||||
activity = activity,
|
||||
siteKey = siteKey,
|
||||
referer = referer,
|
||||
onSuccess = { code ->
|
||||
val args = Bundle(
|
||||
finishAction(activity, event, callback, Bundle(
|
||||
"recaptchaCode" to code,
|
||||
"recaptchaTime" to System.currentTimeMillis(),
|
||||
)
|
||||
if (extras != null)
|
||||
args.putAll(extras)
|
||||
|
||||
if (onSuccess != null)
|
||||
onSuccess(args)
|
||||
else
|
||||
EdziennikTask.syncProfile(profileId, arguments = args.toJsonObject()).enqueue(activity)
|
||||
))
|
||||
},
|
||||
onFailure = onFailure,
|
||||
onCancel = callback.onCancel,
|
||||
).show()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun executeOauth(
|
||||
activity: AppCompatActivity,
|
||||
profileId: Int?,
|
||||
params: Bundle?,
|
||||
onSuccess: ((params: Bundle) -> Unit)?,
|
||||
onFailure: (() -> Unit)?,
|
||||
event: UserActionRequiredEvent,
|
||||
callback: UserActionCallback,
|
||||
): Boolean {
|
||||
if (profileId == null || params == null)
|
||||
return false
|
||||
val extras = params.getBundle("extras")
|
||||
val storeKey = params.getString("responseStoreKey") ?: return false
|
||||
params.getString("authorizeUrl") ?: return false
|
||||
params.getString("redirectUrl") ?: return false
|
||||
val storeKey = event.params.getString("responseStoreKey") ?: return false
|
||||
event.params.getString("authorizeUrl") ?: return false
|
||||
event.params.getString("redirectUrl") ?: return false
|
||||
|
||||
var listener: Any? = null
|
||||
listener = object {
|
||||
@ -155,26 +126,41 @@ class UserActionManager(val app: App) {
|
||||
fun onOAuthLoginResult(result: OAuthLoginResult) {
|
||||
EventBus.getDefault().unregister(listener)
|
||||
when {
|
||||
result.isError -> onFailure?.invoke()
|
||||
result.isError -> callback.onFailure?.invoke()
|
||||
result.responseUrl != null -> {
|
||||
val args = Bundle(
|
||||
finishAction(activity, event, callback, Bundle(
|
||||
storeKey to result.responseUrl,
|
||||
)
|
||||
if (extras != null)
|
||||
args.putAll(extras)
|
||||
|
||||
if (onSuccess != null)
|
||||
onSuccess(args)
|
||||
else
|
||||
EdziennikTask.syncProfile(profileId, arguments = args.toJsonObject()).enqueue(activity)
|
||||
))
|
||||
}
|
||||
else -> callback.onCancel?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().register(listener)
|
||||
|
||||
val intent = Intent(activity, OAuthLoginActivity::class.java).putExtras(params)
|
||||
val intent = Intent(activity, OAuthLoginActivity::class.java).putExtras(event.params)
|
||||
activity.startActivity(intent)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun finishAction(
|
||||
activity: AppCompatActivity,
|
||||
event: UserActionRequiredEvent,
|
||||
callback: UserActionCallback,
|
||||
data: Bundle,
|
||||
) {
|
||||
val extras = event.params.getBundle("extras")
|
||||
if (extras != null)
|
||||
data.putAll(extras)
|
||||
|
||||
if (callback.onSuccess != null)
|
||||
callback.onSuccess.invoke(data)
|
||||
else if (event.profileId != null)
|
||||
EdziennikTask.syncProfile(
|
||||
profileId = event.profileId,
|
||||
arguments = data.toJsonObject(),
|
||||
).enqueue(activity)
|
||||
else
|
||||
callback.onFailure?.invoke()
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Save Debug Logs" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/librusCaptchaButton"
|
||||
style="@style/Widget.MaterialComponents.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="LIBRUS® Captcha" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/refreshWidget"
|
||||
style="@style/Widget.MaterialComponents.Button"
|
||||
|
@ -31,11 +31,6 @@
|
||||
<string name="error_112" translatable="false">ERROR_NOT_IMPLEMENTED</string>
|
||||
<string name="error_113" translatable="false">ERROR_FILE_DOWNLOAD</string>
|
||||
|
||||
<string name="error_115" translatable="false">ERROR_NO_STUDENTS_IN_ACCOUNT</string>
|
||||
|
||||
<string name="error_3000" translatable="false">ERROR_CAPTCHA_NEEDED</string>
|
||||
<string name="error_3001" translatable="false">ERROR_CAPTCHA_LIBRUS_PORTAL</string>
|
||||
|
||||
<string name="error_5000" translatable="false">ERROR_API_PDO_ERROR</string>
|
||||
<string name="error_5001" translatable="false">ERROR_API_INVALID_CLIENT</string>
|
||||
<string name="error_5002" translatable="false">ERROR_API_INVALID_ARGUMENT</string>
|
||||
@ -174,8 +169,6 @@
|
||||
<string name="error_631" translatable="false">ERROR_PODLASIE_API_OTHER</string>
|
||||
<string name="error_632" translatable="false">ERROR_PODLASIE_API_DATA_MISSING</string>
|
||||
|
||||
<string name="error_701" translatable="false">ERROR_USOS_OAUTH_LOGIN_REQUEST</string>
|
||||
|
||||
<string name="error_801" translatable="false">ERROR_TEMPLATE_WEB_OTHER</string>
|
||||
|
||||
<string name="error_900" translatable="false">EXCEPTION_API_TASK</string>
|
||||
@ -223,11 +216,6 @@
|
||||
<string name="error_112_reason">Nie zaimplementowano</string>
|
||||
<string name="error_113_reason">Wystąpił błąd podczas pobierania pliku. Dziennik może być przeciążony lub mieć przerwę techniczną.</string>
|
||||
|
||||
<string name="error_115_reason">Brak uczniów przypisanych do konta</string>
|
||||
|
||||
<string name="error_3000_reason">Wymagane rozwiązanie zadania Captcha</string>
|
||||
<string name="error_3001_reason">LIBRUS®️: wymagane rozwiązanie zadania Captcha</string>
|
||||
|
||||
<string name="error_5000_reason">ERROR_API_PDO_ERROR</string>
|
||||
<string name="error_5001_reason">Nieprawidłowy ID klienta API</string>
|
||||
<string name="error_5002_reason">API: nieprawidłowy argument</string>
|
||||
@ -366,8 +354,6 @@
|
||||
<string name="error_631_reason">ERROR_PODLASIE_API_OTHER</string>
|
||||
<string name="error_632_reason">Brak danych. Zgłoś błąd programiście.</string>
|
||||
|
||||
<string name="error_701_reason">Wymagane logowanie w przeglądarce</string>
|
||||
|
||||
<string name="error_801_reason">ERROR_TEMPLATE_WEB_OTHER</string>
|
||||
|
||||
<string name="error_900_reason">Błąd synchronizacji. Upewnij się, że masz połączenie z internetem, a następnie zgłoś błąd.</string>
|
||||
|
Loading…
Reference in New Issue
Block a user