Вопрос, чем отличается вызов LoadPage в LoadableData.Loading и Success? package ru.maxi.mobile.feature.webfeature.view

import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
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.CookieManager
import android.webkit.DownloadListener
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.view.MenuProvider
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.firebase.Firebase
import com.google.firebase.crashlytics.crashlytics
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import ru.maxi.mobile.BuildConfig
import ru.maxi.mobile.R
import ru.maxi.mobile.base.ui.MvvmBaseFragment
import ru.maxi.mobile.base.ui.manager.showConfirmDialog
import ru.maxi.mobile.core.data.LoadableData
import ru.maxi.mobile.core.navigation.parentRouter
import ru.maxi.mobile.databinding.FragmentWebviewBinding
import ru.maxi.mobile.feature.webfeature.view.model.WebFeaturePage
import ru.maxi.mobile.feature.webfeature.view.model.deepLink.WebCloseDialogData
import ru.maxi.mobile.presentation.common.NegativeButton
import ru.maxi.mobile.presentation.common.PositiveButton
import ru.maxi.mobile.presentation.common.screens.CommonScreens
import ru.maxi.mobile.ui.common.BackButtonListener
import ru.maxi.mobile.uikit.utils.lifecycleAwareLazy
import java.io.File

/**
 * Экран Web-фичи
 */
@AndroidEntryPoint
class WebFeatureFragment : MvvmBaseFragment(R.layout.fragment_webview), BackButtonListener {

    private val viewBinding by viewBinding(FragmentWebviewBinding::bind)

    private val viewModel by viewModels<WebFeatureViewModel>()

    private val menuProvider by lifecycleAwareLazy {
        WebFeatureMenuProvider()
    }

    override val analyticsName: String = "_WebFeature"

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

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

        setTitle("")
        bottomBarManager?.isVisible = false

        with(viewBinding) {
            webView.apply {
                clearAppCaches()
                settings.apply {
                    domStorageEnabled = true
                    cacheMode = WebSettings.LOAD_NO_CACHE
                    textZoom = 100
                    javaScriptEnabled = true
                }
                addJavascriptInterface(viewModel, "MaxiApp")
                webViewClient = CustomWebViewClient()
                setDownloadListener(CustomDownloadListener())
            }
            noConnectionView.setOnActionButtonClickListener { webView.reload() }
        }

        with(viewLifecycleOwner) {
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.STARTED) {
                    launch { viewModel.viewState.collect(::onViewStateChange) }
                    launch {
                        viewModel.shouldCloseScreen
                            .filter { it }
                            .collect { parentRouter.exit() }
                    }
                }
            }
        }
    }

    override fun onStop() {
        super.onStop()
        CookieManager.getInstance().flush()
    }

    override fun onDestroyView() {
        bottomBarManager?.isVisible = true
        super.onDestroyView()
    }

    override fun onBackPressed(): Boolean {
        if (!viewBinding.webView.canGoBack()) {
            val closeDialogData = viewModel.closeDialogData ?: return false
            showConfirmDialog(closeDialogData)
            return true
        }
        viewBinding.webView.goBack()
        return true
    }

    private fun WebView.clearAppCaches() {
        try {
            val webViewCacheDirs = listOf(
                File("${context.applicationInfo.dataDir}/app_webview"),
                File("${context.applicationInfo.dataDir}/app_hws_webview"),
            )
            webViewCacheDirs.forEach { webViewCacheDir ->
                val filesList = webViewCacheDir.listFiles()
                val folderToKeep = listOf("Default")
                val filesToKeep = listOf("Cookies", "Cookies-journal")
                filesList?.forEach {
                    if (!folderToKeep.contains(it.name)) {
                        it.deleteRecursively()
                        return@forEach
                    }
                    val filesInFolder = it.listFiles()
                    filesInFolder?.forEach { files ->
                        if (!filesToKeep.contains(files.name)) {
                            files.deleteRecursively()
                        }
                    }
                }
            }
        } catch (e: Exception) {
            Firebase.crashlytics.recordException(e)
        }
    }

    private fun showConfirmDialog(dialogData: WebCloseDialogData) {
        dialogManager.showConfirmDialog(
            dialogData.text,
            PositiveButton(dialogData.btnConfirm) {
                dialogData.btnConfirmAction
                    ?.let(::evaluateCloseDialogAction)
                    ?: viewModel.closeScreen()
            },
            NegativeButton(dialogData.btnCancel),
        )
    }

    private fun evaluateCloseDialogAction(javascriptAction: String) {
        blockingLoaderManager.isShowing = true
        viewBinding.webView.evaluateJavascript("(function() { $javascriptAction })();") {}
    }

    private fun setTitle(title: String) {
        actionBarManager.setTitle(title)
        actionBarManager.showBackButton()
    }

    private fun onViewStateChange(viewState: LoadableData<WebFeaturePage>) {
        when (viewState) {
            is LoadableData.Error -> with(viewBinding) {
                webView.isInvisible = true
                noConnectionView.isVisible = true
                loaderView.hide()
            }

            is LoadableData.Loading -> with(viewBinding) {
                val isInitialPage = webView.url == null
                webView.isInvisible = isInitialPage
                Log.d("TESTING", "onViewStateChange - LOADING isInitialPage = $isInitialPage")
                noConnectionView.isGone = true
                if (isInitialPage) {
                    loaderView.show()
                } else {
                    loaderView.hide()
                }
                viewState.value?.let(::loadPage)
            }

            is LoadableData.Success -> with(viewBinding) {
                Log.d("TESTING", "onViewStateChange - SUCCESS")
                webView.isVisible = true
                noConnectionView.isGone = true
                loaderView.hide()
            }
        }
    }

    private fun loadPage(page: WebFeaturePage) {
        Log.d("TESTING", "loadPage")
        val url = page.uri.toString()
        if (viewBinding.webView.url == url) return

        if (page.uri.rootHost == BuildConfig.MAXI_RETAIL_HOST) {
            val cookieUrl = "${page.uri.scheme}://${page.uri.host}"
            val cookies = listOf("access_token" to page.authToken, "city" to page.cityCode, "maxi_mp" to "true")
            setCookies(cookieUrl, cookies) {
                if (view == null) return@setCookies
                viewBinding.webView.loadUrl(url, mapOf("Cache-Control" to "no-store"))
            }
        } else {
            parentRouter.navigateTo(CommonScreens.ExternalUriScreen(page.uri))
            parentRouter.exit()
        }
    }

    private fun setCookies(cookieUrl: String, cookies: List<Pair<String, String?>>, onCompleted: () -> Unit) {
        val cookieManager = CookieManager.getInstance()
        cookieManager.setAcceptThirdPartyCookies(viewBinding.webView, true)
        cookieManager.setCookies(cookieUrl, cookies, onCompleted)
    }

    private fun CookieManager.setCookies(
        cookieUrl: String,
        cookies: List<Pair<String, String?>>,
        onCompleted: () -> Unit,
    ) {
        if (cookies.isEmpty()) {
            return onCompleted()
        }

        val (cookieName, cookieValue) = cookies.first()
        val cookieString = buildString {
            append("$cookieName=$cookieValue")
            append("; Secure")
            // удаляем куку, если значение null
            if (cookieValue == null) {
                append("; Max-Age=0")
            }
        }

        // Устанавливаем cookie по одной
        // и используем рекурсию в callback на установку cookie,
        // т.к. иначе установка списка cookies может происходить не корректно
        setCookie(cookieUrl, cookieString) {
            setCookies(cookieUrl, cookies.drop(1), onCompleted)
        }
    }

    private val Uri.rootHost: String?
        get() = host
            ?.split('.')
            ?.takeLast(2)
            ?.joinToString(separator = ".")

    private inner class WebFeatureMenuProvider : MenuProvider {

        private val closeActionId = R.id.action_close

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

        override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
            return when (menuItem.itemId) {
                closeActionId -> {
                    val closeDialogData = viewModel.closeDialogData
                    if (closeDialogData != null) {
                        showConfirmDialog(closeDialogData)
                    } else {
                        viewModel.closeScreen()
                    }

                    true
                }

                else -> false
            }
        }
    }

    private inner class CustomWebViewClient : WebViewClient() {

        override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
            val uri = request.url
            if (uri.rootHost != BuildConfig.MAXI_RETAIL_HOST) {
                val externalUrlScreen = CommonScreens.ExternalUriScreen(uri)
                parentRouter.navigateTo(externalUrlScreen)

                return true
            }

            return super.shouldOverrideUrlLoading(view, request)
        }

        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            val uri = url?.let(Uri::parse) ?: return
            viewModel.onPageStarted(uri)
        }

        override fun onPageFinished(view: WebView?, url: String?) {
            val uri = url?.let(Uri::parse) ?: return
            if (viewModel.onFinishLoad(uri)) {
                view?.title?.let(::setTitle)
            }
        }

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

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

    private inner class CustomDownloadListener : DownloadListener {
        override fun onDownloadStart(
            url: String?,
            userAgent: String?,
            contentDisposition: String?,
            mimeType: String?,
            contentLength: Long,
        ) {
            if (url != null) {
                try {
                    val intent = Intent(Intent.ACTION_VIEW)
                    intent.setDataAndType(Uri.parse(url), mimeType)
                    val packageManager = requireContext().packageManager
                    val validMimeType = if (intent.resolveActivity(packageManager) != null) {
                        mimeType
                    } else {
                        null
                    }
                    val externalUrlScreen = CommonScreens.ExternalUriScreen(Uri.parse(url), validMimeType)
                    parentRouter.navigateTo(externalUrlScreen)
                } catch (ignored: Exception) {
                }
            }
        }
    }
}