diff --git a/app/build.gradle b/app/build.gradle index 180df1a6..8d10ce92 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,8 +27,8 @@ android { testApplicationId "io.github.tests.wulkanowy" minSdkVersion 21 targetSdkVersion 34 - versionCode 143 - versionName "2.3.3" + versionCode 144 + versionName "2.3.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resValue "string", "app_name", "Wulkanowy" @@ -163,7 +163,7 @@ play { track = 'production' releaseStatus = ReleaseStatus.IN_PROGRESS userFraction = 0.99d - updatePriority = 3 + updatePriority = 1 enabled.set(false) } @@ -193,7 +193,7 @@ ext { } dependencies { - implementation 'io.github.wulkanowy:sdk:2.3.5' + implementation 'io.github.wulkanowy:sdk:2.3.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' @@ -238,6 +238,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" + implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" implementation "com.jakewharton.timber:timber:5.0.1" implementation 'com.github.Faierbel:slf4j-timber:2.0' diff --git a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt index bea3f706..950e817b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/DataModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/DataModule.kt @@ -21,6 +21,7 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.RemoteConfigHelper +import io.github.wulkanowy.utils.WebkitCookieManagerProxy import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -43,6 +44,7 @@ internal class DataModule { buildTag = android.os.Build.MODEL userAgentTemplate = remoteConfig.userAgentTemplate setSimpleHttpLogger { Timber.d(it) } + setAdditionalCookieManager(WebkitCookieManagerProxy()) // for debug only addInterceptor(chuckerInterceptor, network = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/Resource.kt b/app/src/main/java/io/github/wulkanowy/data/Resource.kt index 2c5bf0ea..d7c2aeed 100644 --- a/app/src/main/java/io/github/wulkanowy/data/Resource.kt +++ b/app/src/main/java/io/github/wulkanowy/data/Resource.kt @@ -1,6 +1,16 @@ package io.github.wulkanowy.data -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import timber.log.Timber @@ -131,7 +141,7 @@ inline fun networkBoundResource( query().map { Resource.Success(filterResult(it)) } } catch (throwable: Throwable) { onFetchFailed(throwable) - query().map { Resource.Error(throwable) } + flowOf(Resource.Error(throwable)) } } else { query().map { Resource.Success(filterResult(it)) } @@ -165,7 +175,7 @@ inline fun networkBoundResource( query().map { Resource.Success(mapResult(it)) } } catch (throwable: Throwable) { onFetchFailed(throwable) - query().map { Resource.Error(throwable) } + flowOf(Resource.Error(throwable)) } } else { query().map { Resource.Success(mapResult(it)) } diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index 42078d03..aae7882f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -65,8 +65,6 @@ class TimetableNotificationSchedulerHelper @Inject constructor( range = lesson.start..lesson.end, requestCode = getRequestCode(lesson.start, studentId) ) - - Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId") } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt index 026d38de..29996db7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.kt @@ -11,6 +11,7 @@ import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R import io.github.wulkanowy.ui.modules.auth.AuthDialog +import io.github.wulkanowy.ui.modules.captcha.CaptchaDialog import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.utils.FragmentLifecycleLogger import io.github.wulkanowy.utils.getThemeAttrColor @@ -77,6 +78,10 @@ abstract class BaseActivity, VB : ViewBinding> : .show() } + override fun onCaptchaVerificationRequired(url: String?) { + CaptchaDialog.newInstance(url).show(supportFragmentManager, "captcha_dialog") + } + override fun showDecryptionFailedDialog() { MaterialAlertDialogBuilder(this) .setTitle(R.string.main_session_expired) diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt index 50e4b05d..e63887b8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseDialogFragment.kt @@ -8,7 +8,6 @@ import android.widget.Toast import androidx.fragment.app.DialogFragment import androidx.viewbinding.ViewBinding 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.lifecycleAwareVariable import javax.inject.Inject @@ -32,6 +31,10 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } @@ -45,7 +48,7 @@ abstract class BaseDialogFragment : DialogFragment(), BaseView } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun showErrorDetailsDialog(error: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index cec2670b..ba346131 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -7,7 +7,6 @@ import androidx.viewbinding.ViewBinding import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import io.github.wulkanowy.R -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.utils.lifecycleAwareVariable abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId), @@ -43,12 +42,16 @@ abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragme (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun openClearLoginView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index ee92e4fc..d4cb20ca 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -29,6 +29,7 @@ open class BasePresenter( errorHandler.apply { showErrorMessage = view::showError onExpiredCredentials = view::showExpiredCredentialsDialog + onCaptchaVerificationRequired = view::onCaptchaVerificationRequired onDecryptionFailed = view::showDecryptionFailedDialog onNoCurrentStudent = view::openClearLoginView onPasswordChangeRequired = view::showChangePasswordSnackbar diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt index e97a6ab9..88d5754f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseView.kt @@ -8,6 +8,8 @@ interface BaseView { fun showExpiredCredentialsDialog() + fun onCaptchaVerificationRequired(url: String?) + fun showDecryptionFailedDialog() fun showAuthDialog() diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt index 56905709..e17c0c9e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/ErrorHandler.kt @@ -4,6 +4,7 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.sdk.scrapper.exception.AuthorizationRequiredException +import io.github.wulkanowy.sdk.scrapper.exception.CloudflareVerificationException import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException import io.github.wulkanowy.utils.getErrorString @@ -25,6 +26,8 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co var onAuthorizationRequired: () -> Unit = {} + var onCaptchaVerificationRequired: (url: String?) -> Unit = {} + fun dispatch(error: Throwable) { Timber.e(error, "An exception occurred while the Wulkanowy was running") proceed(error) @@ -38,6 +41,7 @@ open class ErrorHandler @Inject constructor(@ApplicationContext protected val co is BadCredentialsException -> onExpiredCredentials() is NoCurrentStudentException -> onNoCurrentStudent() is AuthorizationRequiredException -> onAuthorizationRequired() + is CloudflareVerificationException -> onCaptchaVerificationRequired(error.originalUrl) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt new file mode 100644 index 00000000..ed8293a9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/captcha/CaptchaDialog.kt @@ -0,0 +1,86 @@ +package io.github.wulkanowy.ui.modules.captcha + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.core.os.bundleOf +import dagger.hilt.android.AndroidEntryPoint +import io.github.wulkanowy.R +import io.github.wulkanowy.databinding.DialogCaptchaBinding +import io.github.wulkanowy.sdk.Sdk +import io.github.wulkanowy.ui.base.BaseDialogFragment +import timber.log.Timber +import javax.inject.Inject + +@AndroidEntryPoint +class CaptchaDialog : BaseDialogFragment() { + + @Inject + lateinit var sdk: Sdk + + private var webView: WebView? = null + + companion object { + const val CAPTCHA_SUCCESS = "captcha_success" + private const val CAPTCHA_URL = "captcha_url" + private const val CAPTCHA_CHECK_JS = "document.getElementById('challenge-running') == null" + + fun newInstance(url: String?): CaptchaDialog { + return CaptchaDialog().apply { + arguments = bundleOf(CAPTCHA_URL to url) + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = DialogCaptchaBinding.inflate(inflater).apply { binding = this }.root + + @SuppressLint("SetJavaScriptEnabled") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + isCancelable = false + binding.captchaRefresh.setOnClickListener { + binding.captchaWebview.loadUrl(arguments?.getString(CAPTCHA_URL).orEmpty()) + } + binding.captchaClose.setOnClickListener { dismiss() } + + with(binding.captchaWebview) { + webView = this + with(settings) { + javaScriptEnabled = true + userAgentString = sdk.userAgent + } + + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + view?.evaluateJavascript(CAPTCHA_CHECK_JS) { + if (it == "true") { + onChallengeAccepted() + } + } + } + } + + 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)) + dismissAllowingStateLoss() + } + + override fun onDestroy() { + webView?.destroy() + super.onDestroy() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt index 301262a0..bedbce23 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardFragment.kt @@ -18,6 +18,7 @@ import io.github.wulkanowy.databinding.FragmentDashboardBinding import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment 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.dashboard.adapters.DashboardAdapter 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.openInternetBrowser import io.github.wulkanowy.utils.toFormattedString +import timber.log.Timber import java.time.LocalDate import javax.inject.Inject @@ -62,6 +64,9 @@ class DashboardFragment : BaseFragment(R.layout.fragme return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt() } + override val isViewEmpty + get() = dashboardAdapter.itemCount == 0 + companion object { fun newInstance() = DashboardFragment() @@ -77,6 +82,13 @@ class DashboardFragment : BaseFragment(R.layout.fragme super.onViewCreated(view, savedInstanceState) binding = FragmentDashboardBinding.bind(view) presenter.onAttachView(this) + initializeCaptchaResultObserver() + } + + private fun initializeCaptchaResultObserver() { + childFragmentManager.setFragmentResultListener(CAPTCHA_SUCCESS, this) { _, _ -> + presenter.onRetryAfterCaptcha() + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt index c93dd9e7..74b427e7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt @@ -239,6 +239,14 @@ class DashboardPresenter @Inject constructor( loadData(selectedDashboardTiles, forceRefresh = true) } + fun onRetryAfterCaptcha() { + view?.run { + showErrorView(false) + showProgress(true) + } + loadData(selectedDashboardTiles, forceRefresh = true) + } + fun onViewReselected() { Timber.i("Dashboard view is reselected") view?.run { @@ -316,7 +324,7 @@ class DashboardPresenter @Inject constructor( ) { luckyNumberResource, messageResource, attendanceResource -> val resList = listOf(luckyNumberResource, messageResource, attendanceResource) - DashboardItem.HorizontalGroup( + resList to DashboardItem.HorizontalGroup( isLoading = resList.any { it is Resource.Loading }, error = resList.map { it.errorOrNull }.let { errors -> if (errors.all { it != null }) { @@ -341,9 +349,9 @@ class DashboardPresenter @Inject constructor( ) }) } - .filterNot { it.isLoading && forceRefresh } + .filterNot { (_, it) -> it.isLoading && forceRefresh } .distinctUntilChanged() - .onEach { + .onEach { (_, it) -> updateData(it, forceRefresh) if (it.isLoading) { @@ -361,7 +369,7 @@ class DashboardPresenter @Inject constructor( ) errorHandler.dispatch(it) } - .launch("horizontal_group ${if (forceRefresh) "-forceRefresh" else ""}") + .launchWithUniqueRefreshJob("horizontal_group", forceRefresh) } private fun loadGrades(student: Student, forceRefresh: Boolean) { @@ -854,6 +862,28 @@ class DashboardPresenter @Inject constructor( onEach { if (it is Resource.Success) { cancelJobs(jobName) + } else if (it is Resource.Error) { + cancelJobs(jobName) + } + }.launch(jobName) + } else { + launch(jobName) + } + } + + @JvmName("launchWithUniqueRefreshJobHorizontalGroup") + private fun Flow>, *>>.launchWithUniqueRefreshJob( + name: String, + forceRefresh: Boolean + ) { + val jobName = if (forceRefresh) "$name-forceRefresh" else name + + if (forceRefresh) { + onEach { (resources, _) -> + if (resources.all { it is Resource.Success<*> }) { + cancelJobs(jobName) + } else if (resources.any { it is Resource.Error<*> }) { + cancelJobs(jobName) } }.launch(jobName) } else { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt index 76788543..fe011c92 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardView.kt @@ -6,6 +6,8 @@ interface DashboardView : BaseView { val tileWidth: Int + val isViewEmpty: Boolean + fun initView() fun updateData(data: List) @@ -27,6 +29,5 @@ interface DashboardView : BaseView { fun popViewToRoot() fun openNotificationsCenterView() - fun openInternetBrowser(url: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt index 000916b1..9db01a30 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugFragment.kt @@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.debug import android.os.Bundle import android.view.View +import android.webkit.CookieManager import androidx.recyclerview.widget.LinearLayoutManager import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R @@ -58,6 +59,10 @@ class DebugFragment : BaseFragment(R.layout.fragment_debug (activity as? MainActivity)?.pushView(NotificationDebugFragment.newInstance()) } + override fun clearWebkitCookies() { + CookieManager.getInstance().removeAllCookies(null) + } + override fun onDestroyView() { presenter.onDetachView() super.onDestroyView() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt index 67ac8886..816b5985 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugPresenter.kt @@ -15,6 +15,7 @@ class DebugPresenter @Inject constructor( val items = listOf( DebugItem(R.string.logviewer_title), DebugItem(R.string.notification_debug_title), + DebugItem(R.string.debug_cookies_clear), ) override fun onAttachView(view: DebugView) { @@ -31,6 +32,7 @@ class DebugPresenter @Inject constructor( when (item.title) { R.string.logviewer_title -> view?.openLogViewer() R.string.notification_debug_title -> view?.openNotificationsDebug() + R.string.debug_cookies_clear -> view?.clearWebkitCookies() else -> Timber.d("Unknown debug item: $item") } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt index 9396ec6a..792d63d9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/DebugView.kt @@ -11,4 +11,6 @@ interface DebugView : BaseView { fun openLogViewer() fun openNotificationsDebug() + + fun clearWebkitCookies() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt index 8e9b86fa..975cad18 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormFragment.kt @@ -7,6 +7,7 @@ import android.view.View.GONE import android.view.View.VISIBLE import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.setFragmentResultListener import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R 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.databinding.FragmentLoginFormBinding 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.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginData @@ -72,6 +74,13 @@ class LoginFormFragment : BaseFragment(R.layout.fragme super.onViewCreated(view, savedInstanceState) binding = FragmentLoginFormBinding.bind(view) presenter.onAttachView(this) + initializeCaptchaResultObserver() + } + + private fun initializeCaptchaResultObserver() { + setFragmentResultListener(CaptchaDialog.CAPTCHA_SUCCESS) { _, _ -> + presenter.onRetryAfterCaptcha() + } } override fun initView() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt index ad535c38..c9ae4f27 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/form/LoginFormPresenter.kt @@ -152,6 +152,10 @@ class LoginFormPresenter @Inject constructor( ) } + fun onRetryAfterCaptcha() { + onSignInClick() + } + fun onSignInClick() { val loginData = getLoginData() diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index ba0ef405..62c16257 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -16,6 +16,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat 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.modules.Destination 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.utils.AnalyticsHelper 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.safelyPopFragments 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.json.Json import timber.log.Timber import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds @AndroidEntryPoint class MainActivity : BaseActivity(), MainView, @@ -73,6 +83,8 @@ class MainActivity : BaseActivity(), MainVie private val navController = FragNavController(supportFragmentManager, R.id.main_fragment_container) + private val captchaVerificationEvent = MutableSharedFlow() + companion object { private const val EXTRA_START_DESTINATION = "start_destination_json" @@ -144,6 +156,7 @@ class MainActivity : BaseActivity(), MainVie initializeToolbar() initializeBottomNavigation(startMenuIndex, rootAppMenuItems) initializeNavController(startMenuIndex, rootUpdatedDestinations) + initializeCaptchaVerificationEvent() } private fun initializeNavController( @@ -323,6 +336,27 @@ class MainActivity : BaseActivity(), MainVie .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) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index 19c4ef6b..f8d1323c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.settings import android.os.Bundle import androidx.preference.PreferenceFragmentCompat import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.modules.main.MainView import timber.log.Timber @@ -26,6 +27,8 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, Settin override fun showExpiredCredentialsDialog() {} + override fun onCaptchaVerificationRequired(url: String?) = Unit + override fun showDecryptionFailedDialog() {} override fun openClearLoginView() {} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt index 256b1337..3ef1a80a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/advanced/AdvancedFragment.kt @@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity 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.utils.AppInfo import javax.inject.Inject @@ -51,6 +50,10 @@ class AdvancedFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } @@ -68,7 +71,7 @@ class AdvancedFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt index 20423eb9..3d0c8052 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/appearance/AppearanceFragment.kt @@ -9,7 +9,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity 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.utils.AppInfo import javax.inject.Inject @@ -67,6 +66,10 @@ class AppearanceFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } @@ -84,7 +87,7 @@ class AppearanceFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt index 2ae983c2..0bf9ddad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/notifications/NotificationsFragment.kt @@ -21,7 +21,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity 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.utils.AppInfo import io.github.wulkanowy.utils.openInternetBrowser @@ -137,6 +136,10 @@ class NotificationsFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } @@ -154,7 +157,7 @@ class NotificationsFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun showFixSyncDialog() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt index 133b1ff4..d5714483 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/sync/SyncFragment.kt @@ -10,7 +10,6 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.wulkanowy.R import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.ErrorDialog -import io.github.wulkanowy.ui.modules.auth.AuthDialog import io.github.wulkanowy.ui.modules.main.MainView import javax.inject.Inject @@ -88,6 +87,10 @@ class SyncFragment : PreferenceFragmentCompat(), (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() } @@ -105,7 +108,7 @@ class SyncFragment : PreferenceFragmentCompat(), } override fun showAuthDialog() { - AuthDialog.newInstance().show(childFragmentManager, "auth_dialog") + (activity as? BaseActivity<*, *>)?.showAuthDialog() } override fun onResume() { diff --git a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt index a4c2537a..18fc10bb 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ExceptionExtension.kt @@ -3,6 +3,7 @@ package io.github.wulkanowy.utils import android.content.res.Resources import io.github.wulkanowy.R 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.ScrapperException 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 VulcanException -> R.string.error_unknown_uonet is ScrapperException -> R.string.error_unknown_app + is CloudflareVerificationException -> R.string.error_cloudflare_captcha is SSLHandshakeException -> when { error.isCausedByCertificateNotValidNow() -> R.string.error_invalid_device_datetime else -> R.string.error_timeout diff --git a/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt new file mode 100644 index 00000000..a5497871 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/WebkitCookieManagerProxy.kt @@ -0,0 +1,58 @@ +package io.github.wulkanowy.utils + +import java.net.CookiePolicy +import java.net.CookieStore +import java.net.HttpCookie +import java.net.URI +import android.webkit.CookieManager as WebkitCookieManager +import java.net.CookieManager as JavaCookieManager + +class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) { + + private val webkitCookieManager: WebkitCookieManager = WebkitCookieManager.getInstance() + + override fun put(uri: URI?, responseHeaders: Map>?) { + if (uri == null || responseHeaders == null) return + val url = uri.toString() + for (headerKey in responseHeaders.keys) { + if (headerKey == null || !( + headerKey.equals("Set-Cookie2", ignoreCase = true) || + headerKey.equals("Set-Cookie", ignoreCase = true) + ) + ) continue + + // process each of the headers + for (headerValue in responseHeaders[headerKey].orEmpty()) { + webkitCookieManager.setCookie(url, headerValue) + } + } + } + + override operator fun get( + uri: URI?, + requestHeaders: Map?>? + ): Map> { + require(!(uri == null || requestHeaders == null)) { "Argument is null" } + val res = mutableMapOf>() + val cookie = webkitCookieManager.getCookie(uri.toString()) + if (cookie != null) res["Cookie"] = listOf(cookie) + 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 = cookies.get(uri) + override fun getCookies(): List = cookies.cookies + override fun getURIs(): List = cookies.urIs + override fun remove(uri: URI?, cookie: HttpCookie?): Boolean = + cookies.remove(uri, cookie) + + override fun removeAll(): Boolean { + webkitCookieManager.removeAllCookies(null) + return true + } + } + } +} diff --git a/app/src/main/play/release-notes/pl-PL/default.txt b/app/src/main/play/release-notes/pl-PL/default.txt index 0a2eb68f..c2c30883 100644 --- a/app/src/main/play/release-notes/pl-PL/default.txt +++ b/app/src/main/play/release-notes/pl-PL/default.txt @@ -1,5 +1,6 @@ -Wersja 2.3.3 +Wersja 2.3.4 -— poprawiliśmy kolejne usterki przy odświeżaniu danych (teraz to powinno działać już dużo lepiej) +— dodaliśmy obsługę captchy, co umożliwi używanie apki np. na odmianie ResMan Rzeszów +— naprawiliśmy wyświetlanie frekwencji w szkołach używających eduOne (piszcie, jeśli nadal nie działa) Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases diff --git a/app/src/main/res/layout/dialog_captcha.xml b/app/src/main/res/layout/dialog_captcha.xml new file mode 100644 index 00000000..539aa0cc --- /dev/null +++ b/app/src/main/res/layout/dialog_captcha.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 8e60b7a6..b4f1f878 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -13,6 +13,7 @@ Prohlížeč protokolů Ladění Ladění oznámení + Vymazat soubory cookie webview Tvůrci Licence Zprávy @@ -833,6 +834,9 @@ Autorizace Pro provoz aplikace potřebujeme potvrdit vaši identitu. Zadejte PESEL žáka <b>%1$s</b> v níže uvedeném poli Zatím přeskočit + + Probíhá ověřování. Počkejte… + Úspěšně ověřeno Žádné internetové připojení Vyskytla se chyba. Zkontrolujte hodiny svého zařízení @@ -842,6 +846,7 @@ Probíhá údržba deníku UONET+. Zkuste to později znovu Neznámá chyba deniku UONET+. Prosím zkuste to znovu později Neznámá chyba aplikace. Prosím zkuste to znovu později + Vyžadováno ověření Captcha Vyskytla se neočekávaná chyba Funkce je deaktivována přes vaší školou Funkce není k dispozici. Přihlaste se v jiném režimu než Mobile API diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 01306662..ac616418 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -13,6 +13,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -743,6 +744,9 @@ Authorization To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below Skip for now + + Verification is in progress. Wait… + Verified successfully No internet connection An error occurred. Check your device clock @@ -752,6 +756,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 09173d38..ec6aa655 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -13,6 +13,7 @@ Log Viewer Debuggen Benachrichtigungen debuggen + Clear webview cookies Mitarbeiter Lizenzen Nachrichten @@ -743,6 +744,9 @@ Authorization To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below Skip for now + + Verification is in progress. Wait… + Verified successfully Keine Internetverbindung Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre Geräteuhr @@ -752,6 +756,7 @@ Wartung im Gange UONET + Klassenbuch. Versuchen Sie es später noch einmal Unbekannter UONET + Registerfehler. Versuchen Sie es später erneut Unbekannter Anwendungsfehler. Bitte versuchen Sie es später noch einmal + Captcha verification required Ein unerwarteter Fehler ist aufgetreten Funktion, die von Ihrer Schule deaktiviert wurde Feature in diesem Modus nicht verfügbar diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 01306662..ac616418 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -13,6 +13,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -743,6 +744,9 @@ Authorization To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below Skip for now + + Verification is in progress. Wait… + Verified successfully No internet connection An error occurred. Check your device clock @@ -752,6 +756,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 01306662..ac616418 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -13,6 +13,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -743,6 +744,9 @@ Authorization To operate the application, we need to confirm your identity. Please enter the student\'s PESEL <b>%1$s</b> in the field below Skip for now + + Verification is in progress. Wait… + Verified successfully No internet connection An error occurred. Check your device clock @@ -752,6 +756,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index fb9d170a..1b4fbe66 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -13,6 +13,7 @@ Przeglądarka logów Debugowanie Debugowanie powiadomień + Wyczyść ciasteczka webview Twórcy Licencje Wiadomości @@ -833,6 +834,9 @@ Autoryzacja Rodzicu, musimy mieć pewność, że Twój adres e-mail został powiązany z prawidłowym kontem ucznia. W celu autoryzacji konta podaj numer PESEL ucznia <b>%1$s</b> w polu poniżej Na razie pomiń + + Trwa weryfikacja. Czekaj… + Pomyślnie zweryfikowano Brak połączenia z internetem Wystąpił błąd. Sprawdź poprawność daty w urządzeniu @@ -842,6 +846,7 @@ Trwa przerwa techniczna dziennika UONET+. Spróbuj ponownie później Nieznany błąd dziennika UONET+. Spróbuj ponownie później Nieznany błąd aplikacji. Spróbuj ponownie później + Wymagana weryfikacja captcha Wystąpił nieoczekiwany błąd Funkcja wyłączona przez szkołę Funkcja niedostępna. Zaloguj się w trybie innym niż Mobilne API diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c604cd8b..feb08a03 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -13,6 +13,7 @@ Просмотр журнала Отладка Отладка уведомлений + Clear webview cookies Разработчики Лицензии Сообщения @@ -833,6 +834,9 @@ Авторизация Для работы приложения нам необходимо подтвердить вашу личность. Введите PESEL учащегося <b>%1$s</b> в поле ниже Пропустить сейчас + + Verification is in progress. Wait… + Verified successfully Интернет-соединение отсутствует Произошла ошибка. Проверьте время на вашем устройстве @@ -842,6 +846,7 @@ UONET+ проводит техническое обслуживание, повторите попытку позже Неизвестная ошибка дневника UONET+, повторите попытку позже Неизвестная ошибка приложения, повторите попытку позже + Captcha verification required Произошла непредвиденная ошибка Функция отключена вашей школой Функция недоступна в режиме Mobile API. Воспользуйтесь другим режимом diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index e02b1542..aaf04bc8 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -13,6 +13,7 @@ Prehliadač protokolov Ladenie Ladenie oznámení + Vymazať súbory cookie webview Tvorcovia Licencie Správy @@ -833,6 +834,9 @@ Autorizácia Na prevádzku aplikácie potrebujeme potvrdiť vašu identitu. Zadajte PESEL žiaka <b>%1$s</b> v nižšie uvedenom poli Zatiaľ preskočiť + + Overovanie prebieha. Počkajte… + Úspešne overené Žiadne internetové pripojenie Vyskytla sa chyba. Skontrolujte hodiny svojho zariadenia @@ -842,6 +846,7 @@ Prebieha údržba denníka UONET+. Skúste to neskôr znova Neznáma chyba dennika UONET+. Prosím skúste to znova neskôr Neznáma chyba aplikácie. Prosím skúste to znova neskôr + Vyžaduje sa overenie Captcha Vyskytla sa neočakávaná chyba Funkcia je deaktivovaná cez vašou školou Funkcia nie je k dispozícii. Prihláste sa v inom režime než Mobile API diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 32617f42..fffae003 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -13,6 +13,7 @@ Переглядач логів Відладка Відладка сповіщень + Очистити кукі веб - перегляду Розробники Ліцензії Листи @@ -833,6 +834,9 @@ Авторизувати Для роботи програми нам потрібно підтвердити вашу особу. Будь ласка, введіть число PESEL <b>%1$s</b> студента в поле нижче Поки що пропустити + + Верифікація в процесі. Чекайте… + Верифікація завершена Немає з\'єднання з інтернетом Сталася помилка. Перевірте годинник пристрою @@ -842,6 +846,7 @@ UONET+ проводить технічне осблуговування, спробуйте пізніше Невідома помилка щоденника UONET+, спробуйте пізніше Невідома помилка програми, спробуйте пізніше + Необхідна перевірка Captcha Відбулася несподівана помилка Функція вимкнена вашою школою Функція недоступна в режимі Mobile API. Увійдіть в інший режим diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 72910b85..60d85606 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Log viewer Debug Notification debug + Clear webview cookies Contributors Licenses Messages @@ -833,6 +834,11 @@ Skip for now + + Verification is in progress. Wait… + Verified successfully + + No internet connection An error occurred. Check your device clock @@ -842,6 +848,7 @@ Maintenance underway UONET + register. Try again later Unknown UONET + register error. Try again later Unknown application error. Please try again later + Captcha verification required An unexpected error occurred Feature disabled by your school Feature not available. Login in a mode other than Mobile API diff --git a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt index d7d83e6c..30b9e6b7 100644 --- a/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt +++ b/app/src/play/java/io/github/wulkanowy/ui/modules/settings/ads/AdsFragment.kt @@ -105,6 +105,10 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView { (activity as? BaseActivity<*, *>)?.showExpiredCredentialsDialog() } + override fun onCaptchaVerificationRequired(url: String?) { + (activity as? BaseActivity<*, *>)?.onCaptchaVerificationRequired(url) + } + override fun showDecryptionFailedDialog() { (activity as? BaseActivity<*, *>)?.showDecryptionFailedDialog() }