Мне для тестирования нужно, чтобы капча отображалась всегда. package ru.maxi.mobile.ui.auth

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.Lifecycle
import by.kirich1409.viewbindingdelegate.viewBinding
import com.axmor.maxi.Constants
import moxy.ktx.moxyPresenter
import ru.maxi.mobile.BuildConfig
import ru.maxi.mobile.R
import ru.maxi.mobile.app
import ru.maxi.mobile.base.ui.MvpBaseFragment
import ru.maxi.mobile.common.utils.android.setupDarkMode
import ru.maxi.mobile.databinding.FragmentAuthBinding
import ru.maxi.mobile.presentation.auth.presenter.AuthPresenter
import ru.maxi.mobile.presentation.auth.view.AuthView
import ru.maxi.mobile.ui.common.BackButtonListener
import ru.maxi.mobile.uikit.utils.lifecycleAwareLazy
import ru.tinkoff.decoro.MaskImpl
import ru.tinkoff.decoro.parser.PhoneNumberUnderscoreSlotsParser
import ru.tinkoff.decoro.watchers.FormatWatcher
import ru.tinkoff.decoro.watchers.MaskFormatWatcher
import javax.inject.Inject
import javax.inject.Provider

/**
 * Фрагмент для отображения экрана авторизации
 */
class AuthFragment : MvpBaseFragment(R.layout.fragment_auth), AuthView, BackButtonListener {

    @Inject
    lateinit var presenterProvider: Provider<AuthPresenter>

    private val viewBinding by viewBinding(FragmentAuthBinding::bind)

    private val presenter by moxyPresenter {
        presenterProvider.get()
    }

    private val menuProvider by lifecycleAwareLazy {
        AuthMenuProvider()
    }

    override val analyticsName: String = "_StartAuth"

    override fun onAttach(context: Context) {
        super.onAttach(context)
        app.authComponent.inject(this)
    }

    @SuppressLint("SetJavaScriptEnabled")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("TESTING", "AuthFragment onViewCreated")

        activity?.addMenuProvider(menuProvider, viewLifecycleOwner, Lifecycle.State.STARTED)

        with(viewBinding) {
            val slots = PhoneNumberUnderscoreSlotsParser().parseSlots(Constants.PHONE_MASK)
            val mask = MaskImpl.createNonTerminated(slots)
            mask.isForbidInputWhenFilled = true
            val formatWatcher: FormatWatcher = MaskFormatWatcher(mask)
            formatWatcher.installOnAndFill(authPhone)

            authPhone.doAfterTextChanged {
                val rawPhone = formatWatcher.mask.toUnformattedString()
                val number = rawPhone.drop(2)
                if (number.startsWith("7") || number.startsWith("8")) {
                    val formattedNumber = number.drop(1).take(10)
                    formatWatcher.refreshMask(formattedNumber)
                    presenter.onPhoneChanged(formatWatcher.mask.filled(), formattedNumber)
                    return@doAfterTextChanged
                }
                presenter.onPhoneChanged(formatWatcher.mask.filled(), number)
                val cursorPosition = authPhone.selectionStart
                if (it != null && cursorPosition < 3) {
                    authPhone.setSelection(3)
                }
            }
        }
        viewBinding.bthAuth.setOnClickListener {
            hideKeyboard(clearFocus = true)
            presenter.auth()
        }

        with(viewBinding) {
            captchaWebView.apply {
                settings.javaScriptEnabled = true
                addJavascriptInterface(presenter, "NativeClient")
                webViewClient = CustomWebViewClient()
                setBackgroundColor(Color.TRANSPARENT)
                setupDarkMode()
            }
        }
    }

    override fun setAuthButtonEnable(isEnabled: Boolean) {
        viewBinding.bthAuth.isEnabled = isEnabled
    }

    override fun onBackPressed(): Boolean {
        if (viewBinding.captchaWebView.isVisible) {
            hideCaptcha()
        } else {
            presenter.onBackPressed()
        }
        return true
    }

    override fun loadCaptcha() {
        viewBinding.captchaWebView.loadUrl(CAPTCHA_URL)
    }

    override fun showCaptcha() {
        viewBinding.captchaWebView.isVisible = true
    }

    override fun hideCaptcha() {
        viewBinding.captchaWebView.isVisible = false
    }

    companion object {
        private const val CAPTCHA_URL =
            "https://smartcaptcha.yandexcloud.net/webview?sitekey=" +
                "${BuildConfig.YANDEX_SMARTCAPTCHA_CLIENT_KEY}&invisible=true"

        /**
         * Получить инстанс фрагмента
         */
        fun getInstance(): AuthFragment {
            return AuthFragment()
        }
    }

    private inner class CustomWebViewClient : WebViewClient() {

        override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
            if (request.isForMainFrame || request.isRedirect) {
                presenter.onCaptchaLoadError()
            }
        }

        override fun onReceivedHttpError(
            view: WebView,
            request: WebResourceRequest,
            errorResponse: WebResourceResponse,
        ) {
            if (request.isForMainFrame || request.isRedirect) {
                presenter.onCaptchaLoadError()
            }
        }
    }

    private inner class AuthMenuProvider : MenuProvider {

        override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
            menuInflater.inflate(R.menu.auth_menu, menu)
        }

        override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
            if (menuItem.itemId == R.id.action_skip) {
                presenter.skipAuth()
                return true
            }
            return false
        }
    }
}
package ru.maxi.mobile.presentation.auth.presenter

import android.webkit.JavascriptInterface
import kotlinx.coroutines.launch
import moxy.InjectViewState
import moxy.MvpPresenter
import moxy.presenterScope
import ru.maxi.mobile.business.auth.AuthInteractor
import ru.maxi.mobile.common.features.business.FeatureProvider
import ru.maxi.mobile.core.data.ValidError
import ru.maxi.mobile.core.data.exception.BusinessLogicValidException
import ru.maxi.mobile.feature.city.CitySelectScreens
import ru.maxi.mobile.presentation.auth.AuthScreens
import ru.maxi.mobile.presentation.auth.view.AuthView
import ru.maxi.mobile.presentation.common.NegativeButton
import ru.maxi.mobile.presentation.common.PositiveButton
import javax.inject.Inject

/**
 * Презентер экрана авторизации
 */
@InjectViewState
class AuthPresenter @Inject constructor(
    private val authInteractor: AuthInteractor,
    private val featureProvider: FeatureProvider,
) : MvpPresenter<AuthView>() {

    private var extractedPhone: String? = null
    private var needCreateCard: Boolean = false

    override fun onFirstViewAttach() {
        super.onFirstViewAttach()
        viewState.setTitle(PAGE_TITLE)
        updateFeatures()
    }

    private fun updateFeatures() {
        presenterScope.launch {
            try {
                featureProvider.updateFeatures(forceUpdate = true)
            } catch (ignored: Exception) {
            }
        }
    }

    /**
     * Обработать событие ввода номера телефона
     *
     * @param maskFilled выполнены ли условия маски ввода
     * @param newValue новое значение номера телефона
     */
    fun onPhoneChanged(maskFilled: Boolean, newValue: String) {
        if (newValue == extractedPhone) return

        extractedPhone = newValue
        viewState.setAuthButtonEnable(maskFilled)
    }

    /**
     * Перейти на подтверждение номера телефона с флагом создания карты [needCreateCard]
     *
     * @param captchaToken токен прохождения капчи
     */
    fun auth(needCreateCard: Boolean = false, captchaToken: String? = null) {
        val phone = extractedPhone ?: return
        this.needCreateCard = needCreateCard

        presenterScope.launch {
            try {
                viewState.showBlockLoading()
                authInteractor.sendPhoneForAuth(phone, needCreateCard, captchaToken)
                viewState.hideBlockLoading()
                viewState.replaceScreen(AuthScreens.AuthConfirmationBySmsScreen())
            } catch (validException: BusinessLogicValidException) {
                when (validException.code) {
                    ValidError.TOO_MANY_AUTHORIZATION_CLAIM -> {
                        viewState.hideBlockLoading()
                        viewState.showDialogMessage(
                            validException.message,
                            PositiveButton(text = CLOSE_DIALOG_BUTTON_TEXT) {},
                        )
                    }

                    ValidError.CARD_BLOCKED -> {
                        viewState.hideBlockLoading()
                        viewState.showConfirmDialog(
                            validException.message,
                            PositiveButton(text = DIALOG_BLOCKED_CARD_POSITIVE_TEXT) {
                                auth(needCreateCard = true)
                            },
                            NegativeButton(text = DIALOG_BLOCKED_CARD_NEGATIVE_TEXT),
                        )
                    }

                    ValidError.NEED_TO_PASS_CAPTCHA -> viewState.loadCaptcha()

                    else -> {
                        viewState.hideBlockLoading()
                        viewState.showSnackbar(AUTH_ERROR_TEXT)
                    }
                }
            } catch (e: Exception) {
                viewState.hideBlockLoading()
                viewState.showSnackbar(AUTH_ERROR_TEXT)
            }
        }
    }

    /**
     * Пропустить авторизацю
     */
    fun skipAuth() {
        authInteractor.setAuthSkipped()
        viewState.navigateTo(CitySelectScreens.CitySelectScreen(true))
    }

    /**
     * Обработка действие кнопки назад
     */
    fun onBackPressed() {
        viewState.finishNavigationChain()
    }

    /**
     * Получить токен [token] прохождения капчи
     */
    @Suppress("Unused")
    @JavascriptInterface
    fun onGetToken(token: String) {
        presenterScope.launch {
            viewState.hideCaptcha()
            viewState.hideBlockLoading()
        }
        auth(needCreateCard = needCreateCard, captchaToken = token)
    }

    /**
     * Показать окно с капчей
     */
    @Suppress("Unused")
    @JavascriptInterface
    fun onChallengeVisible() {
        presenterScope.launch {
            viewState.showCaptcha()
            viewState.hideBlockLoading()
        }
    }

    /**
     * Закрыть окно с капчей
     */
    @Suppress("Unused")
    @JavascriptInterface
    fun onChallengeHidden() {
        presenterScope.launch {
            viewState.hideCaptcha()
            viewState.hideBlockLoading()
        }
    }

    /**
     * Обработать ошибку загрузки капчи
     */
    fun onCaptchaLoadError() {
        viewState.hideCaptcha()
    }

    companion object {
        private const val PAGE_TITLE = "Авторизация"
        private const val CLOSE_DIALOG_BUTTON_TEXT = "Закрыть"
        private const val AUTH_ERROR_TEXT = "Не удалось выполнить запрос"
        private const val DIALOG_BLOCKED_CARD_POSITIVE_TEXT = "Создать карту"
        private const val DIALOG_BLOCKED_CARD_NEGATIVE_TEXT = "Отмена"
    }
}