diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2c24b426..da531480 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -160,7 +160,11 @@
+ android:theme="@style/Theme.MaterialComponents.Light.DarkActionBar" />
+
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt
index cfecde91..56d3976a 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaDialog.kt
@@ -25,6 +25,7 @@ class RecaptchaDialog(
private val autoRetry: Boolean = true,
private val onSuccess: (recaptchaCode: String) -> Unit,
private val onFailure: (() -> Unit)? = null,
+ private val onServerError: (() -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog(activity, onShowListener, onDismissListener) {
@@ -44,7 +45,11 @@ class RecaptchaDialog(
override suspend fun onBeforeShow(): Boolean {
val (title, text, bitmap) = withContext(Dispatchers.Default) {
- val html = loadCaptchaHtml() ?: return@withContext null
+ val html = loadCaptchaHtml()
+ if (html == null) {
+ onServerError?.invoke()
+ return@withContext null
+ }
return@withContext loadCaptchaData(html)
} ?: run {
onFailure?.invoke()
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt
index a927347d..ca876ddb 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/captcha/RecaptchaPromptDialog.kt
@@ -19,6 +19,7 @@ class RecaptchaPromptDialog(
private val referer: String,
private val onSuccess: (recaptchaCode: String) -> Unit,
private val onCancel: (() -> Unit)?,
+ private val onServerError: (() -> Unit)? = null,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog(activity, onShowListener, onDismissListener) {
@@ -62,7 +63,8 @@ class RecaptchaPromptDialog(
b.checkbox.background = checkboxBackground
b.checkbox.foreground = checkboxForeground
b.progress.visibility = View.GONE
- }
+ },
+ onServerError = onServerError,
).show()
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaActivity.kt
new file mode 100644
index 00000000..2e58da07
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaActivity.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2023-3-24.
+ */
+
+package pl.szczodrzynski.edziennik.ui.login.recaptcha
+
+import android.annotation.SuppressLint
+import android.graphics.Color
+import android.os.Build
+import android.os.Bundle
+import android.util.Base64
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.webkit.JavascriptInterface
+import android.webkit.WebView
+import android.widget.FrameLayout
+import androidx.appcompat.app.AppCompatActivity
+import org.greenrobot.eventbus.EventBus
+import pl.szczodrzynski.edziennik.R
+import pl.szczodrzynski.edziennik.data.api.SYSTEM_USER_AGENT
+import pl.szczodrzynski.edziennik.utils.Themes
+import java.nio.charset.Charset
+
+class RecaptchaActivity : AppCompatActivity() {
+ companion object {
+ private const val TAG = "RecaptchaActivity"
+
+ private const val CODE = """
+ PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3Lmdvb2ds
+ ZS5jb20vcmVjYXB0Y2hhL2FwaS5qcz9vbmxvYWQ9cmVhZHkmcmVuZGVyPWV4cGxpY2l0Ij48L3Nj
+ cmlwdD48L2hlYWQ+PGJvZHk+PGJyPjxkaXYgaWQ9ImdyIiBzdHlsZT0icG9zaXRpb246YWJzb2x1
+ dGU7dG9wOjUwJTt0cmFuc2Zvcm06dHJhbnNsYXRlKDAsLTUwJSk7Ij48L2Rpdj48YnI+PHNjcmlw
+ dD5mdW5jdGlvbiByZWFkeSgpe2dyZWNhcHRjaGEucmVuZGVyKCJnciIse3NpdGVrZXk6IlNJVEVL
+ RVkiLHRoZW1lOiJUSEVNRSIsY2FsbGJhY2s6ZnVuY3Rpb24oZSl7d2luZG93LmlmLmNhbGxiYWNr
+ KGUpO30sImV4cGlyZWQtY2FsbGJhY2siOndpbmRvdy5pZi5leHBpcmVkQ2FsbGJhY2ssImVycm9y
+ LWNhbGxiYWNrIjp3aW5kb3cuaWYuZXJyb3JDYWxsYmFja30pO308L3NjcmlwdD48L2JvZHk+PC9o
+ dG1sPg==
+ """
+ }
+
+ private var isSuccessful = false
+ private lateinit var jsInterface: CaptchaCallbackInterface
+
+ interface CaptchaCallbackInterface {
+ @JavascriptInterface
+ fun callback(recaptchaResponse: String)
+
+ @JavascriptInterface
+ fun expiredCallback()
+
+ @JavascriptInterface
+ fun errorCallback()
+ }
+
+ @SuppressLint("AddJavascriptInterface", "SetJavaScriptEnabled")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setTitle(R.string.recaptcha_dialog_title)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ WebView.setWebContentsDebuggingEnabled(true)
+ }
+
+ val siteKey = intent.getStringExtra("siteKey") ?: return
+ val referer = intent.getStringExtra("referer") ?: return
+ val userAgent = intent.getStringExtra("userAgent") ?: SYSTEM_USER_AGENT
+
+ val htmlContent = Base64.decode(CODE, Base64.DEFAULT)
+ .toString(Charset.defaultCharset())
+ .replace("THEME", if (Themes.isDark) "dark" else "light")
+ .replace("SITEKEY", siteKey)
+
+ jsInterface = object : CaptchaCallbackInterface {
+ @JavascriptInterface
+ override fun callback(recaptchaResponse: String) {
+ isSuccessful = true
+ EventBus.getDefault().post(
+ RecaptchaResult(
+ isError = false,
+ code = recaptchaResponse,
+ )
+ )
+ finish()
+ }
+
+ @JavascriptInterface
+ override fun expiredCallback() {
+ isSuccessful = false
+ }
+
+ @JavascriptInterface
+ override fun errorCallback() {
+ isSuccessful = false
+ EventBus.getDefault().post(
+ RecaptchaResult(
+ isError = true,
+ code = null,
+ )
+ )
+ finish()
+ }
+ }
+
+ val webView = WebView(this).apply {
+ setBackgroundColor(Color.TRANSPARENT)
+ settings.javaScriptEnabled = true
+ settings.userAgentString = userAgent
+ addJavascriptInterface(jsInterface, "if")
+ loadDataWithBaseURL(
+ referer,
+ htmlContent,
+ "text/html",
+ "UTF-8",
+ null,
+ )
+ // setLayerType(WebView.LAYER_TYPE_SOFTWARE, null)
+ layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ }
+ setContentView(webView)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ if (!isSuccessful)
+ EventBus.getDefault().post(
+ RecaptchaResult(
+ isError = false,
+ code = null,
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaResult.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaResult.kt
new file mode 100644
index 00000000..ae5fae19
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/login/recaptcha/RecaptchaResult.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2023-3-24.
+ */
+
+package pl.szczodrzynski.edziennik.ui.login.recaptcha
+
+data class RecaptchaResult(
+ val isError: Boolean,
+ val code: String?,
+)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt
index 3e8761c8..9ca4c233 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/managers/UserActionManager.kt
@@ -22,6 +22,8 @@ import pl.szczodrzynski.edziennik.ext.*
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.ui.login.recaptcha.RecaptchaActivity
+import pl.szczodrzynski.edziennik.ui.login.recaptcha.RecaptchaResult
import pl.szczodrzynski.edziennik.utils.Utils.d
class UserActionManager(val app: App) {
@@ -107,10 +109,45 @@ class UserActionManager(val app: App) {
))
},
onCancel = callback.onCancel,
+ onServerError = {
+ executeRecaptchaActivity(activity, event, callback)
+ },
).show()
return true
}
+ private fun executeRecaptchaActivity(
+ activity: AppCompatActivity,
+ event: UserActionRequiredEvent,
+ callback: UserActionCallback,
+ ): Boolean {
+ event.params.getString("siteKey") ?: return false
+ event.params.getString("referer") ?: return false
+
+ var listener: Any? = null
+ listener = object {
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onRecaptchaResult(result: RecaptchaResult) {
+ EventBus.getDefault().unregister(listener)
+ when {
+ result.isError -> callback.onFailure?.invoke()
+ result.code != null -> {
+ finishAction(activity, event, callback, Bundle(
+ "recaptchaCode" to result.code,
+ "recaptchaTime" to System.currentTimeMillis(),
+ ))
+ }
+ else -> callback.onCancel?.invoke()
+ }
+ }
+ }
+ EventBus.getDefault().register(listener)
+
+ val intent = Intent(activity, RecaptchaActivity::class.java).putExtras(event.params)
+ activity.startActivity(intent)
+ return true
+ }
+
private fun executeOauth(
activity: AppCompatActivity,
event: UserActionRequiredEvent,
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1e8e44d0..4620f9b2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1543,6 +1543,7 @@
TODO
USOS - wymagane logowanie z użyciem przeglądarki
Zaloguj się
+ reCAPTCHA
Nie można załadować danych aplikacji
{cmd-share-variant} udostępnione w klasie
{cmd-share-variant} udostępnione przez Ciebie