1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 05:29:08 -05:00

Make some improvements in captcha dialog (#2393)

* Add improvements retrying after captcha solved

* Add showAuthDialog from BaseActivity instead of displaying this dialog manually

* Add getCookieStore() with removeAll impl in WebkitCookieManagerProxy

* Add debounce to captcha dialog showing logic

* Add refresh button to captcha dialog

* Destroy webview along with captcha dialog

* Add clear webkit cookies button to debug menu

* Add captcha error message

* Update captcha verified message
This commit is contained in:
Mikołaj Pich 2024-01-14 14:09:04 +01:00 committed by GitHub
parent a98e8398fd
commit 096fe359e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 179 additions and 29 deletions

View File

@ -65,8 +65,6 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
range = lesson.start..lesson.end, range = lesson.start..lesson.end,
requestCode = getRequestCode(lesson.start, studentId) requestCode = getRequestCode(lesson.start, studentId)
) )
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
} }
} }
} }

View File

@ -8,7 +8,6 @@ import android.widget.Toast
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.android.material.elevation.SurfaceColors import com.google.android.material.elevation.SurfaceColors
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
import javax.inject.Inject import javax.inject.Inject
@ -49,7 +48,7 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment(), BaseView
} }
override fun showAuthDialog() { override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") (activity as? BaseActivity<*, *>)?.showAuthDialog()
} }
override fun showErrorDetailsDialog(error: Throwable) { override fun showErrorDetailsDialog(error: Throwable) {

View File

@ -7,7 +7,6 @@ import androidx.viewbinding.ViewBinding
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.lifecycleAwareVariable
abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragment(layoutId), abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragment(layoutId),
@ -52,7 +51,7 @@ abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragme
} }
override fun showAuthDialog() { override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") (activity as? BaseActivity<*, *>)?.showAuthDialog()
} }
override fun openClearLoginView() { override fun openClearLoginView() {

View File

@ -5,12 +5,11 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.DialogCaptchaBinding import io.github.wulkanowy.databinding.DialogCaptchaBinding
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
@ -23,8 +22,13 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
@Inject @Inject
lateinit var sdk: Sdk lateinit var sdk: Sdk
private var webView: WebView? = null
companion object { companion object {
const val CAPTCHA_SUCCESS = "captcha_success"
private const val CAPTCHA_URL = "captcha_url" private const val CAPTCHA_URL = "captcha_url"
private const val CAPTCHA_CHECK_JS = "document.getElementById('challenge-running') == null"
fun newInstance(url: String?): CaptchaDialog { fun newInstance(url: String?): CaptchaDialog {
return CaptchaDialog().apply { return CaptchaDialog().apply {
arguments = bundleOf(CAPTCHA_URL to url) arguments = bundleOf(CAPTCHA_URL to url)
@ -41,8 +45,14 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
isCancelable = false
binding.captchaRefresh.setOnClickListener {
binding.captchaWebview.loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
}
binding.captchaClose.setOnClickListener { dismiss() }
with(binding.captchaWebview) { with(binding.captchaWebview) {
webView = this
with(settings) { with(settings) {
javaScriptEnabled = true javaScriptEnabled = true
userAgentString = sdk.userAgent userAgentString = sdk.userAgent
@ -50,23 +60,27 @@ class CaptchaDialog : BaseDialogFragment<DialogCaptchaBinding>() {
webViewClient = object : WebViewClient() { webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) { override fun onPageFinished(view: WebView?, url: String?) {
view?.evaluateJavascript("document.getElementById('challenge-running') == undefined") { view?.evaluateJavascript(CAPTCHA_CHECK_JS) {
if (it == "true") { if (it == "true") {
dismiss() onChallengeAccepted()
} else Timber.e("JS result: $it") }
} }
} }
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
super.onReceivedError(view, request, error)
}
} }
loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty()) loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty())
} }
} }
private fun onChallengeAccepted() {
runCatching { parentFragmentManager.setFragmentResult(CAPTCHA_SUCCESS, bundleOf()) }
.onFailure { Timber.e(it) }
showMessage(getString(R.string.captcha_verified_message))
dismiss()
}
override fun onDestroy() {
webView?.destroy()
super.onDestroy()
}
} }

View File

@ -18,6 +18,7 @@ import io.github.wulkanowy.databinding.FragmentDashboardBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog.Companion.CAPTCHA_SUCCESS
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment
@ -36,6 +37,7 @@ import io.github.wulkanowy.utils.getErrorString
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -62,6 +64,9 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt() return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt()
} }
override val isViewEmpty
get() = dashboardAdapter.itemCount == 0
companion object { companion object {
fun newInstance() = DashboardFragment() fun newInstance() = DashboardFragment()
@ -77,6 +82,13 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentDashboardBinding.bind(view) binding = FragmentDashboardBinding.bind(view)
presenter.onAttachView(this) presenter.onAttachView(this)
initializeCaptchaResultObserver()
}
private fun initializeCaptchaResultObserver() {
childFragmentManager.setFragmentResultListener(CAPTCHA_SUCCESS, this) { _, _ ->
presenter.onRetryAfterCaptcha()
}
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View File

@ -239,6 +239,14 @@ class DashboardPresenter @Inject constructor(
loadData(selectedDashboardTiles, forceRefresh = true) loadData(selectedDashboardTiles, forceRefresh = true)
} }
fun onRetryAfterCaptcha() {
view?.run {
showErrorView(false)
showProgress(true)
}
loadData(selectedDashboardTiles, forceRefresh = true)
}
fun onViewReselected() { fun onViewReselected() {
Timber.i("Dashboard view is reselected") Timber.i("Dashboard view is reselected")
view?.run { view?.run {

View File

@ -6,6 +6,8 @@ interface DashboardView : BaseView {
val tileWidth: Int val tileWidth: Int
val isViewEmpty: Boolean
fun initView() fun initView()
fun updateData(data: List<DashboardItem>) fun updateData(data: List<DashboardItem>)
@ -27,6 +29,5 @@ interface DashboardView : BaseView {
fun popViewToRoot() fun popViewToRoot()
fun openNotificationsCenterView() fun openNotificationsCenterView()
fun openInternetBrowser(url: String) fun openInternetBrowser(url: String)
} }

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.debug
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.webkit.CookieManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
@ -58,6 +59,10 @@ class DebugFragment : BaseFragment<FragmentDebugBinding>(R.layout.fragment_debug
(activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance()) (activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance())
} }
override fun clearWebkitCookies() {
CookieManager.getInstance().removeAllCookies(null)
}
override fun onDestroyView() { override fun onDestroyView() {
presenter.onDetachView() presenter.onDetachView()
super.onDestroyView() super.onDestroyView()

View File

@ -15,6 +15,7 @@ class DebugPresenter @Inject constructor(
val items = listOf( val items = listOf(
DebugItem(R.string.logviewer_title), DebugItem(R.string.logviewer_title),
DebugItem(R.string.notification_debug_title), DebugItem(R.string.notification_debug_title),
DebugItem(R.string.debug_cookies_clear),
) )
override fun onAttachView(view: DebugView) { override fun onAttachView(view: DebugView) {
@ -31,6 +32,7 @@ class DebugPresenter @Inject constructor(
when (item.title) { when (item.title) {
R.string.logviewer_title -> view?.openLogViewer() R.string.logviewer_title -> view?.openLogViewer()
R.string.notification_debug_title -> view?.openNotificationsDebug() R.string.notification_debug_title -> view?.openNotificationsDebug()
R.string.debug_cookies_clear -> view?.clearWebkitCookies()
else -> Timber.d("Unknown debug item: $item") else -> Timber.d("Unknown debug item: $item")
} }
} }

View File

@ -11,4 +11,6 @@ interface DebugView : BaseView {
fun openLogViewer() fun openLogViewer()
fun openNotificationsDebug() fun openNotificationsDebug()
fun clearWebkitCookies()
} }

View File

@ -7,6 +7,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.setFragmentResultListener
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.AdminMessage
@ -14,6 +15,7 @@ import io.github.wulkanowy.data.pojos.RegisterUser
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginFormBinding import io.github.wulkanowy.databinding.FragmentLoginFormBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder import io.github.wulkanowy.ui.modules.dashboard.viewholders.AdminMessageViewHolder
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginData import io.github.wulkanowy.ui.modules.login.LoginData
@ -72,6 +74,13 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentLoginFormBinding.bind(view) binding = FragmentLoginFormBinding.bind(view)
presenter.onAttachView(this) presenter.onAttachView(this)
initializeCaptchaResultObserver()
}
private fun initializeCaptchaResultObserver() {
setFragmentResultListener(CaptchaDialog.CAPTCHA_SUCCESS) { _, _ ->
presenter.onRetryAfterCaptcha()
}
} }
override fun initView() { override fun initView() {

View File

@ -152,6 +152,10 @@ class LoginFormPresenter @Inject constructor(
) )
} }
fun onRetryAfterCaptcha() {
onSignInClick()
}
fun onSignInClick() { fun onSignInClick() {
val loginData = getLoginData() val loginData = getLoginData()

View File

@ -16,6 +16,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -30,6 +31,8 @@ import io.github.wulkanowy.databinding.ActivityMainBinding
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
@ -40,10 +43,17 @@ import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.safelyPopFragments import io.github.wulkanowy.utils.safelyPopFragments
import io.github.wulkanowy.utils.setOnViewChangeListener import io.github.wulkanowy.utils.setOnViewChangeListener
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainView, class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainView,
@ -73,6 +83,8 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
private val navController = private val navController =
FragNavController(supportFragmentManager, R.id.main_fragment_container) FragNavController(supportFragmentManager, R.id.main_fragment_container)
private val captchaVerificationEvent = MutableSharedFlow<String?>()
companion object { companion object {
private const val EXTRA_START_DESTINATION = "start_destination_json" private const val EXTRA_START_DESTINATION = "start_destination_json"
@ -144,6 +156,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
initializeToolbar() initializeToolbar()
initializeBottomNavigation(startMenuIndex, rootAppMenuItems) initializeBottomNavigation(startMenuIndex, rootAppMenuItems)
initializeNavController(startMenuIndex, rootUpdatedDestinations) initializeNavController(startMenuIndex, rootUpdatedDestinations)
initializeCaptchaVerificationEvent()
} }
private fun initializeNavController( private fun initializeNavController(
@ -323,6 +336,27 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
.show() .show()
} }
@OptIn(FlowPreview::class)
private fun initializeCaptchaVerificationEvent() {
captchaVerificationEvent
.debounce(1.seconds)
.onEach { url ->
Timber.d("Showing captcha dialog for: $url")
showDialogFragment(CaptchaDialog.newInstance(url))
}
.launchIn(lifecycleScope)
}
override fun onCaptchaVerificationRequired(url: String?) {
lifecycleScope.launch {
captchaVerificationEvent.emit(url)
}
}
override fun showAuthDialog() {
showDialogFragment(AuthDialog.newInstance())
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState) navController.onSaveInstanceState(outState)

View File

@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject import javax.inject.Inject
@ -72,7 +71,7 @@ class AdvancedFragment : PreferenceFragmentCompat(),
} }
override fun showAuthDialog() { override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") (activity as? BaseActivity<*, *>)?.showAuthDialog()
} }
override fun onResume() { override fun onResume() {

View File

@ -9,7 +9,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import javax.inject.Inject import javax.inject.Inject
@ -88,7 +87,7 @@ class AppearanceFragment : PreferenceFragmentCompat(),
} }
override fun showAuthDialog() { override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") (activity as? BaseActivity<*, *>)?.showAuthDialog()
} }
override fun onResume() { override fun onResume() {

View File

@ -21,7 +21,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
@ -158,7 +157,7 @@ class NotificationsFragment : PreferenceFragmentCompat(),
} }
override fun showAuthDialog() { override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") (activity as? BaseActivity<*, *>)?.showAuthDialog()
} }
override fun showFixSyncDialog() { override fun showFixSyncDialog() {

View File

@ -10,7 +10,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.base.ErrorDialog import io.github.wulkanowy.ui.base.ErrorDialog
import io.github.wulkanowy.ui.modules.auth.AuthDialog
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject import javax.inject.Inject
@ -109,7 +108,7 @@ class SyncFragment : PreferenceFragmentCompat(),
} }
override fun showAuthDialog() { override fun showAuthDialog() {
AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") (activity as? BaseActivity<*, *>)?.showAuthDialog()
} }
override fun onResume() { override fun onResume() {

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import android.content.res.Resources import android.content.res.Resources
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException import io.github.wulkanowy.sdk.scrapper.exception.ServiceUnavailableException
@ -34,6 +35,7 @@ fun Resources.getErrorString(error: Throwable): String = when (error) {
is FeatureNotAvailableException -> R.string.error_feature_not_available is FeatureNotAvailableException -> R.string.error_feature_not_available
is VulcanException -> R.string.error_unknown_uonet is VulcanException -> R.string.error_unknown_uonet
is ScrapperException -> R.string.error_unknown_app is ScrapperException -> R.string.error_unknown_app
is CloudflareVerificationException -> R.string.error_cloudflare_captcha
is SSLHandshakeException -> when { is SSLHandshakeException -> when {
error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime
else -> R.string.error_timeout else -> R.string.error_timeout

View File

@ -1,6 +1,8 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import java.net.CookiePolicy import java.net.CookiePolicy
import java.net.CookieStore
import java.net.HttpCookie
import java.net.URI import java.net.URI
import android.webkit.CookieManager as WebkitCookieManager import android.webkit.CookieManager as WebkitCookieManager
import java.net.CookieManager as JavaCookieManager import java.net.CookieManager as JavaCookieManager
@ -36,4 +38,21 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
if (cookie != null) res["Cookie"] = listOf(cookie) if (cookie != null) res["Cookie"] = listOf(cookie)
return res return res
} }
override fun getCookieStore(): CookieStore {
val cookies = super.getCookieStore()
return object : CookieStore {
override fun add(uri: URI?, cookie: HttpCookie?) = cookies.add(uri, cookie)
override fun get(uri: URI?): List<HttpCookie> = cookies.get(uri)
override fun getCookies(): List<HttpCookie> = cookies.cookies
override fun getURIs(): List<URI> = cookies.urIs
override fun remove(uri: URI?, cookie: HttpCookie?): Boolean =
cookies.remove(uri, cookie)
override fun removeAll(): Boolean {
webkitCookieManager.removeAllCookies(null)
return true
}
}
}
} }

View File

@ -1,12 +1,51 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:minWidth="350dp"
tools:context=".ui.modules.captcha.CaptchaDialog"> tools:context=".ui.modules.captcha.CaptchaDialog">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:gravity="center_vertical"
android:text="@string/captcha_dialog_title"
app:layout_constraintBottom_toBottomOf="@id/captcha_close"
app:layout_constraintEnd_toStartOf="@id/captcha_refresh"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/captcha_refresh"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:contentDescription="@string/logviewer_refresh"
app:icon="@drawable/ic_refresh"
app:iconTint="?colorOnSurface"
app:layout_constraintEnd_toStartOf="@id/captcha_close"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/captcha_close"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:contentDescription="@string/all_close"
app:icon="@drawable/ic_all_close_circle"
app:iconTint="?colorOnSurface"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<WebView <WebView
android:id="@+id/captcha_webview" android:id="@+id/captcha_webview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/captcha_close" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,6 +14,7 @@
<string name="logviewer_title">Log viewer</string> <string name="logviewer_title">Log viewer</string>
<string name="debug_title">Debug</string> <string name="debug_title">Debug</string>
<string name="notification_debug_title">Notification debug</string> <string name="notification_debug_title">Notification debug</string>
<string name="debug_cookies_clear">Clear webview cookies</string>
<string name="contributors_title">Contributors</string> <string name="contributors_title">Contributors</string>
<string name="license_title">Licenses</string> <string name="license_title">Licenses</string>
<string name="message_title">Messages</string> <string name="message_title">Messages</string>
@ -833,6 +834,11 @@
<string name="auth_button_skip">Skip for now</string> <string name="auth_button_skip">Skip for now</string>
<!--Captcha-->
<string name="captcha_dialog_title">Verification is in progress. Wait…</string>
<string name="captcha_verified_message">Verified successfully</string>
<!--Errors--> <!--Errors-->
<string name="error_no_internet">No internet connection</string> <string name="error_no_internet">No internet connection</string>
<string name="error_invalid_device_datetime">An error occurred. Check your device clock</string> <string name="error_invalid_device_datetime">An error occurred. Check your device clock</string>
@ -842,6 +848,7 @@
<string name="error_service_unavailable">Maintenance underway UONET + register. Try again later</string> <string name="error_service_unavailable">Maintenance underway UONET + register. Try again later</string>
<string name="error_unknown_uonet">Unknown UONET + register error. Try again later</string> <string name="error_unknown_uonet">Unknown UONET + register error. Try again later</string>
<string name="error_unknown_app">Unknown application error. Please try again later</string> <string name="error_unknown_app">Unknown application error. Please try again later</string>
<string name="error_cloudflare_captcha">Captcha verification required</string>
<string name="error_unknown">An unexpected error occurred</string> <string name="error_unknown">An unexpected error occurred</string>
<string name="error_feature_disabled">Feature disabled by your school</string> <string name="error_feature_disabled">Feature disabled by your school</string>
<string name="error_feature_not_available">Feature not available. Login in a mode other than Mobile API</string> <string name="error_feature_not_available">Feature not available. Login in a mode other than Mobile API</string>