HapeLee 3 месяцев назад
Родитель
Сommit
4a1a5727a6

+ 0 - 6
app/src/main/AndroidManifest.xml

@@ -276,12 +276,6 @@
             android:configChanges="locale|layoutDirection|uiMode|screenLayout"
             android:configChanges="locale|layoutDirection|uiMode|screenLayout"
             android:enableOnBackInvokedCallback="true"
             android:enableOnBackInvokedCallback="true"
             android:launchMode="singleTop" />
             android:launchMode="singleTop" />
-        <!-- WebView界面 -->
-        <activity
-            android:name=".ui.browser.WebViewActivity"
-            android:enableOnBackInvokedCallback="true"
-            android:configChanges="locale|layoutDirection|uiMode|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
-            android:launchMode="standard" />
         <!-- 字典管理 -->
         <!-- 字典管理 -->
         <activity
         <activity
             android:name=".ui.dict.rule.DictRuleActivity"
             android:name=".ui.dict.rule.DictRuleActivity"

+ 8 - 12
app/src/main/java/io/legado/app/help/source/SourceVerificationHelp.kt

@@ -6,7 +6,7 @@ import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.CacheManager
 import io.legado.app.help.CacheManager
 import io.legado.app.help.IntentData
 import io.legado.app.help.IntentData
 import io.legado.app.ui.association.VerificationCodeActivity
 import io.legado.app.ui.association.VerificationCodeActivity
-import io.legado.app.ui.browser.WebViewActivity
+import io.legado.app.ui.main.MainActivity
 import io.legado.app.utils.isMainThread
 import io.legado.app.utils.isMainThread
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivity
 import splitties.init.appCtx
 import splitties.init.appCtx
@@ -85,17 +85,13 @@ object SourceVerificationHelp {
     ) {
     ) {
         source ?: throw NoStackTraceException("startBrowser parameter source cannot be null")
         source ?: throw NoStackTraceException("startBrowser parameter source cannot be null")
         require(url.length < 64 * 1024) { "startBrowser parameter url too long" }
         require(url.length < 64 * 1024) { "startBrowser parameter url too long" }
-        appCtx.startActivity<WebViewActivity> {
-            putExtra("title", title)
-            putExtra("url", url)
-            putExtra("sourceOrigin", source.getKey())
-            putExtra("sourceName", source.getTag())
-            putExtra("sourceType", source.getSourceType())
-            putExtra("sourceVerificationEnable", saveResult)
-            putExtra("refetchAfterSuccess", refetchAfterSuccess)
-            putExtra("html", html)
-            IntentData.put(getVerificationResultKey(source), Thread.currentThread())
-        }
+        IntentData.put(getVerificationResultKey(source), Thread.currentThread())
+        appCtx.startActivity(
+            MainActivity.createWebViewIntent(
+                appCtx, title, url, source.getKey(), source.getTag(), source.getSourceType(),
+                saveResult == true, refetchAfterSuccess != false, html,
+            )
+        )
     }
     }
 
 
 
 

+ 11 - 10
app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt

@@ -72,7 +72,6 @@ import io.legado.app.ui.book.manga.recyclerview.ScrollTimer
 import io.legado.app.ui.book.manga.recyclerview.WebtoonFrame
 import io.legado.app.ui.book.manga.recyclerview.WebtoonFrame
 import io.legado.app.ui.book.read.MangaMenu
 import io.legado.app.ui.book.read.MangaMenu
 import io.legado.app.ui.book.toc.TocActivityResult
 import io.legado.app.ui.book.toc.TocActivityResult
-import io.legado.app.ui.browser.WebViewActivity
 import io.legado.app.ui.config.otherConfig.OtherConfig
 import io.legado.app.ui.config.otherConfig.OtherConfig
 import io.legado.app.ui.config.readConfig.ReadConfig
 import io.legado.app.ui.config.readConfig.ReadConfig
 import io.legado.app.ui.login.SourceLoginType
 import io.legado.app.ui.login.SourceLoginType
@@ -92,7 +91,6 @@ import io.legado.app.utils.isAbsUrl
 import io.legado.app.utils.isTrue
 import io.legado.app.utils.isTrue
 import io.legado.app.utils.observeEvent
 import io.legado.app.utils.observeEvent
 import io.legado.app.utils.showDialogFragment
 import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.startActivity
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toggleSystemBar
 import io.legado.app.utils.toggleSystemBar
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
@@ -699,14 +697,17 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
                     analyzeRule.evalJS(payAction).toString()
                     analyzeRule.evalJS(payAction).toString()
                 }.onSuccess(IO) {
                 }.onSuccess(IO) {
                     if (it.isAbsUrl()) {
                     if (it.isAbsUrl()) {
-                        startActivity<WebViewActivity> {
-                            val bookSource = ReadManga.bookSource
-                            putExtra("title", getString(R.string.chapter_pay))
-                            putExtra("url", it)
-                            putExtra("sourceOrigin", bookSource?.bookSourceUrl)
-                            putExtra("sourceName", bookSource?.bookSourceName)
-                            putExtra("sourceType", bookSource?.getSourceType())
-                        }
+                        val bookSource = ReadManga.bookSource
+                        startActivity(
+                            MainActivity.createWebViewIntent(
+                                this@ReadMangaActivity,
+                                getString(R.string.chapter_pay),
+                                it,
+                                bookSource?.bookSourceUrl,
+                                bookSource?.bookSourceName,
+                                bookSource?.getSourceType(),
+                            )
+                        )
                     } else if (it.isTrue()) {
                     } else if (it.isTrue()) {
                         //购买成功后刷新目录
                         //购买成功后刷新目录
                         ReadManga.book?.let {
                         ReadManga.book?.let {

+ 17 - 16
app/src/main/java/io/legado/app/ui/book/read/MangaMenu.kt

@@ -8,31 +8,30 @@ import android.view.LayoutInflater
 import android.view.animation.Animation
 import android.view.animation.Animation
 import android.widget.FrameLayout
 import android.widget.FrameLayout
 import androidx.appcompat.widget.PopupMenu
 import androidx.appcompat.widget.PopupMenu
-import androidx.core.view.ViewCompat
 import androidx.core.view.HapticFeedbackConstantsCompat
 import androidx.core.view.HapticFeedbackConstantsCompat
+import androidx.core.view.ViewCompat
 import androidx.core.view.isGone
 import androidx.core.view.isGone
 import androidx.core.view.isVisible
 import androidx.core.view.isVisible
+import androidx.lifecycle.lifecycleScope
 import com.google.android.material.slider.Slider
 import com.google.android.material.slider.Slider
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.databinding.ViewMangaMenuBinding
 import io.legado.app.databinding.ViewMangaMenuBinding
+import io.legado.app.domain.gateway.ReadSettingsGateway
 import io.legado.app.help.source.getSourceType
 import io.legado.app.help.source.getSourceType
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadManga
 import io.legado.app.model.ReadManga
-import io.legado.app.ui.browser.WebViewActivity
 import io.legado.app.ui.config.readConfig.ReadConfig
 import io.legado.app.ui.config.readConfig.ReadConfig
-import io.legado.app.domain.gateway.ReadSettingsGateway
-import androidx.lifecycle.lifecycleScope
-import kotlinx.coroutines.launch
-import org.koin.core.context.GlobalContext
+import io.legado.app.ui.main.MainActivity
 import io.legado.app.utils.activity
 import io.legado.app.utils.activity
 import io.legado.app.utils.applyNavigationBarPadding
 import io.legado.app.utils.applyNavigationBarPadding
 import io.legado.app.utils.gone
 import io.legado.app.utils.gone
 import io.legado.app.utils.invisible
 import io.legado.app.utils.invisible
 import io.legado.app.utils.loadAnimation
 import io.legado.app.utils.loadAnimation
 import io.legado.app.utils.openUrl
 import io.legado.app.utils.openUrl
-import io.legado.app.utils.startActivity
 import io.legado.app.utils.visible
 import io.legado.app.utils.visible
+import kotlinx.coroutines.launch
+import org.koin.core.context.GlobalContext
 
 
 class MangaMenu @JvmOverloads constructor(
 class MangaMenu @JvmOverloads constructor(
     context: Context,
     context: Context,
@@ -194,15 +193,17 @@ class MangaMenu @JvmOverloads constructor(
             if (ReadConfig.readUrlInBrowser) {
             if (ReadConfig.readUrlInBrowser) {
                 context.openUrl(tvChapterUrl.text.toString().substringBefore(",{"))
                 context.openUrl(tvChapterUrl.text.toString().substringBefore(",{"))
             } else {
             } else {
-                context.startActivity<WebViewActivity> {
-                    val url = tvChapterUrl.text.toString()
-                    val bookSource = ReadBook.bookSource
-                    putExtra("title", tvChapterName.text)
-                    putExtra("url", url)
-                    putExtra("sourceOrigin", bookSource?.bookSourceUrl)
-                    putExtra("sourceName", bookSource?.bookSourceName)
-                    putExtra("sourceType", bookSource?.getSourceType())
-                }
+                val bookSource = ReadBook.bookSource
+                context.startActivity(
+                    MainActivity.createWebViewIntent(
+                        context,
+                        tvChapterName.text.toString(),
+                        tvChapterUrl.text.toString(),
+                        bookSource?.bookSourceUrl,
+                        bookSource?.bookSourceName,
+                        bookSource?.getSourceType(),
+                    )
+                )
             }
             }
         }
         }
         val chapterViewLongClickListener = OnLongClickListener {
         val chapterViewLongClickListener = OnLongClickListener {

+ 4 - 9
app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt

@@ -62,7 +62,6 @@ import io.legado.app.ui.book.read.sheet.ReaderBookSheetRoute
 import io.legado.app.ui.book.read.sheet.TextSelectMenuConfigSheet
 import io.legado.app.ui.book.read.sheet.TextSelectMenuConfigSheet
 import io.legado.app.ui.book.searchContent.SearchContentResult
 import io.legado.app.ui.book.searchContent.SearchContentResult
 import io.legado.app.ui.book.toc.TocActivityResult
 import io.legado.app.ui.book.toc.TocActivityResult
-import io.legado.app.ui.browser.WebViewActivity
 import io.legado.app.ui.login.SourceLoginType
 import io.legado.app.ui.login.SourceLoginType
 import io.legado.app.ui.main.MainActivity
 import io.legado.app.ui.main.MainActivity
 import io.legado.app.ui.replace.ReplaceEditRoute
 import io.legado.app.ui.replace.ReplaceEditRoute
@@ -348,14 +347,10 @@ fun ReadBookRouteScreen(
                             }
                             }
                             is ReadBookEffect.OpenWebView -> {
                             is ReadBookEffect.OpenWebView -> {
                                 context.startActivity(
                                 context.startActivity(
-                                    Intent(context, WebViewActivity::class.java).apply {
-                                        putExtra("title", effect.title)
-                                        putExtra("url", effect.url)
-                                        putExtra("sourceOrigin", effect.sourceOrigin)
-                                        putExtra("sourceName", effect.sourceName)
-                                        effect.sourceType?.let { putExtra("sourceType", it) }
-                                        effect.html?.let { putExtra("html", it) }
-                                    }
+                                    MainActivity.createWebViewIntent(
+                                        context, effect.title, effect.url, effect.sourceOrigin,
+                                        effect.sourceName, effect.sourceType, html = effect.html,
+                                    )
                                 )
                                 )
                             }
                             }
                             is ReadBookEffect.RunSourceCustomButton -> {
                             is ReadBookEffect.RunSourceCustomButton -> {

+ 0 - 319
app/src/main/java/io/legado/app/ui/browser/WebViewActivity.kt

@@ -1,319 +0,0 @@
-package io.legado.app.ui.browser
-
-import android.annotation.SuppressLint
-import android.content.pm.ActivityInfo
-import android.net.Uri
-import android.net.http.SslError
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.webkit.CookieManager
-import android.webkit.SslErrorHandler
-import android.webkit.URLUtil
-import android.webkit.WebChromeClient
-import android.webkit.WebResourceRequest
-import android.webkit.WebSettings
-import android.webkit.WebView
-import android.webkit.WebViewClient
-import androidx.activity.addCallback
-import org.koin.androidx.viewmodel.ext.android.viewModel
-import androidx.core.view.size
-import io.legado.app.R
-import io.legado.app.base.VMBaseActivity
-import io.legado.app.constant.AppConst
-import io.legado.app.databinding.ActivityWebViewBinding
-import io.legado.app.help.config.AppConfig
-import io.legado.app.help.http.CookieStore
-import io.legado.app.help.source.SourceVerificationHelp
-import io.legado.app.lib.dialogs.SelectItem
-import io.legado.app.lib.dialogs.alert
-import io.legado.app.lib.dialogs.selector
-//import io.legado.app.lib.theme.accentColor
-import io.legado.app.model.Download
-import io.legado.app.ui.association.OnLineImportActivity
-import io.legado.app.utils.gone
-import io.legado.app.utils.invisible
-import io.legado.app.utils.keepScreenOn
-import io.legado.app.utils.longSnackbar
-import io.legado.app.utils.openUrl
-import io.legado.app.utils.sendToClip
-import io.legado.app.utils.setDarkeningAllowed
-import io.legado.app.utils.startActivity
-import io.legado.app.utils.toggleSystemBar
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
-import java.net.URLDecoder
-import io.legado.app.help.http.CookieManager as AppCookieManager
-
-class WebViewActivity : VMBaseActivity<ActivityWebViewBinding, WebViewModel>() {
-
-    override val binding by viewBinding(ActivityWebViewBinding::inflate)
-    override val viewModel by viewModel<WebViewModel>()
-    private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null
-    private var webPic: String? = null
-    private var isCloudflareChallenge = false
-    private var isFullScreen = false
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        binding.titleBar.title = intent.getStringExtra("title") ?: getString(R.string.loading)
-        binding.titleBar.subtitle = intent.getStringExtra("sourceName")
-        viewModel.initData(intent) {
-            val url = viewModel.baseUrl
-            val headerMap = viewModel.headerMap
-            initWebView(url, headerMap)
-            val html = viewModel.html
-            if (html.isNullOrEmpty()) {
-                binding.webView.loadUrl(url, headerMap)
-            } else {
-                binding.webView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url)
-            }
-        }
-        onBackPressedDispatcher.addCallback(this) {
-            if (binding.customWebView.size > 0) {
-                customWebViewCallback?.onCustomViewHidden()
-                return@addCallback
-            } else if (binding.webView.canGoBack()
-                && binding.webView.copyBackForwardList().size > 1
-            ) {
-                binding.webView.goBack()
-                return@addCallback
-            }
-            if (isFullScreen) {
-                toggleFullScreen()
-                return@addCallback
-            }
-            finish()
-        }
-    }
-
-    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
-        menuInflater.inflate(R.menu.web_view, menu)
-        return super.onCompatCreateOptionsMenu(menu)
-    }
-
-    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
-        if (viewModel.sourceOrigin.isNotEmpty()) {
-            menu.findItem(R.id.menu_disable_source)?.isVisible = true
-            menu.findItem(R.id.menu_delete_source)?.isVisible = true
-        }
-        return super.onPrepareOptionsMenu(menu)
-    }
-
-    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
-        when (item.itemId) {
-            R.id.menu_open_in_browser -> openUrl(viewModel.baseUrl)
-            R.id.menu_copy_url -> sendToClip(viewModel.baseUrl)
-            R.id.menu_ok -> {
-                if (viewModel.sourceVerificationEnable) {
-                    viewModel.saveVerificationResult(binding.webView) {
-                        finish()
-                    }
-                } else {
-                    finish()
-                }
-            }
-
-            R.id.menu_full_screen -> toggleFullScreen()
-            R.id.menu_disable_source -> {
-                viewModel.disableSource {
-                    finish()
-                }
-            }
-
-            R.id.menu_delete_source -> {
-                alert(R.string.draw) {
-                    setMessage(getString(R.string.sure_del) + "\n" + viewModel.sourceName)
-                    noButton()
-                    yesButton {
-                        viewModel.deleteSource {
-                            finish()
-                        }
-                    }
-                }
-            }
-        }
-        return super.onCompatOptionsItemSelected(item)
-    }
-
-    //实现starBrowser调起页面全屏
-    private fun toggleFullScreen() {
-        isFullScreen = !isFullScreen
-
-        toggleSystemBar(!isFullScreen)
-
-        if (isFullScreen) {
-            supportActionBar?.hide()
-        } else {
-            supportActionBar?.show()
-        }
-    }
-
-    @SuppressLint("SetJavaScriptEnabled")
-    private fun initWebView(url: String, headerMap: HashMap<String, String>) {
-        //binding.progressBar.fontColor = accentColor
-        binding.webView.webChromeClient = CustomWebChromeClient()
-        binding.webView.webViewClient = CustomWebViewClient()
-        binding.webView.settings.apply {
-            setDarkeningAllowed(AppConfig.isNightTheme)
-            mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
-            domStorageEnabled = true
-            allowContentAccess = true
-            useWideViewPort = true
-            loadWithOverviewMode = true
-            javaScriptEnabled = true
-            builtInZoomControls = true
-            displayZoomControls = false
-            headerMap[AppConst.UA_NAME]?.let {
-                userAgentString = it
-            }
-        }
-        AppCookieManager.applyToWebView(url)
-        binding.webView.setOnLongClickListener {
-            val hitTestResult = binding.webView.hitTestResult
-            if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
-                hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
-            ) {
-                hitTestResult.extra?.let { webPic ->
-                    selector(
-                        arrayListOf(
-                            SelectItem(getString(R.string.action_save), "save"),
-                        )
-                    ) { _, charSequence, _ ->
-                        when (charSequence.value) {
-                            "save" -> saveImage(webPic)
-                        }
-                    }
-                    return@setOnLongClickListener false
-                }
-            }
-            return@setOnLongClickListener false
-        }
-        binding.webView.setDownloadListener { downloadUrl, _, contentDisposition, _, _ ->
-            var fileName = URLUtil.guessFileName(downloadUrl, contentDisposition, null)
-            fileName = URLDecoder.decode(fileName, "UTF-8")
-            binding.llView.longSnackbar(fileName, getString(R.string.action_download)) {
-                Download.start(this, downloadUrl, fileName)
-            }
-        }
-    }
-
-    private fun saveImage(webPic: String) {
-        this.webPic = webPic
-        viewModel.saveImage(webPic)
-    }
-
-    override fun finish() {
-        SourceVerificationHelp.checkResult(viewModel.sourceOrigin)
-        super.finish()
-    }
-
-    override fun onDestroy() {
-        super.onDestroy()
-        binding.webView.destroy()
-    }
-
-    inner class CustomWebChromeClient : WebChromeClient() {
-
-        override fun onProgressChanged(view: WebView?, newProgress: Int) {
-            super.onProgressChanged(view, newProgress)
-            binding.progressBar.setProgress(newProgress)
-            binding.progressBar.gone(newProgress == 100)
-        }
-
-        override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {
-            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
-            binding.llView.invisible()
-            binding.customWebView.addView(view)
-            customWebViewCallback = callback
-            keepScreenOn(true)
-            toggleSystemBar(false)
-        }
-
-        override fun onHideCustomView() {
-            binding.customWebView.removeAllViews()
-            binding.llView.visible()
-            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
-            keepScreenOn(false)
-            toggleSystemBar(true)
-        }
-    }
-
-    inner class CustomWebViewClient : WebViewClient() {
-        override fun shouldOverrideUrlLoading(
-            view: WebView?,
-            request: WebResourceRequest?
-        ): Boolean {
-            request?.let {
-                return shouldOverrideUrlLoading(it.url)
-            }
-            return true
-        }
-
-        @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "KotlinRedundantDiagnosticSuppress")
-        override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
-            url?.let {
-                return shouldOverrideUrlLoading(Uri.parse(it))
-            }
-            return true
-        }
-
-        override fun onPageFinished(view: WebView?, url: String?) {
-            super.onPageFinished(view, url)
-            val cookieManager = CookieManager.getInstance()
-            url?.let {
-                CookieStore.setCookie(it, cookieManager.getCookie(it))
-            }
-            view?.title?.let { title ->
-                if (title != url && title != view.url && title.isNotBlank()) {
-                    binding.titleBar.title = title
-                } else {
-                    binding.titleBar.title = intent.getStringExtra("title")
-                }
-                view.evaluateJavascript("!!window._cf_chl_opt") {
-                    if (it == "true") {
-                        isCloudflareChallenge = true
-                    } else if (isCloudflareChallenge && viewModel.sourceVerificationEnable) {
-                        viewModel.saveVerificationResult(binding.webView) {
-                            finish()
-                        }
-                    }
-                }
-            }
-        }
-
-        private fun shouldOverrideUrlLoading(url: Uri): Boolean {
-            when (url.scheme) {
-                "http", "https" -> {
-                    return false
-                }
-
-                "legado", "yuedu" -> {
-                    startActivity<OnLineImportActivity> {
-                        data = url
-                    }
-                    return true
-                }
-
-                else -> {
-                    binding.root.longSnackbar(R.string.jump_to_another_app, R.string.confirm) {
-                        openUrl(url)
-                    }
-                    return true
-                }
-            }
-        }
-
-        @SuppressLint("WebViewClientOnReceivedSslError")
-        override fun onReceivedSslError(
-            view: WebView?,
-            handler: SslErrorHandler?,
-            error: SslError?
-        ) {
-            handler?.proceed()
-        }
-
-    }
-
-}

+ 199 - 0
app/src/main/java/io/legado/app/ui/browser/WebViewRouteScreen.kt

@@ -0,0 +1,199 @@
+package io.legado.app.ui.browser
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.view.View
+import android.webkit.SslErrorHandler
+import android.webkit.WebChromeClient
+import android.webkit.WebResourceRequest
+import android.webkit.WebSettings
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.OpenInBrowser
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import io.legado.app.R
+import io.legado.app.constant.AppConst
+import io.legado.app.help.http.CookieManager
+import io.legado.app.help.http.CookieStore
+import io.legado.app.help.source.SourceVerificationHelp
+import io.legado.app.ui.widget.components.AppScaffold
+import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBar
+import io.legado.app.ui.widget.components.topbar.TopBarActionButton
+import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
+import io.legado.app.utils.openUrl
+import android.webkit.CookieManager as AndroidCookieManager
+
+@SuppressLint("SetJavaScriptEnabled", "WebViewClientOnReceivedSslError")
+@Composable
+fun WebViewRouteScreen(
+    intent: Intent,
+    viewModel: WebViewModel,
+    onFinish: () -> Unit,
+) {
+    val context = LocalContext.current
+    var ready by remember { mutableStateOf(false) }
+    var progress by remember { mutableIntStateOf(0) }
+    var webView by remember { mutableStateOf<WebView?>(null) }
+    var customView by remember { mutableStateOf<View?>(null) }
+    var customViewCallback by remember { mutableStateOf<WebChromeClient.CustomViewCallback?>(null) }
+    var cloudflareChallenge by remember { mutableStateOf(false) }
+
+    LaunchedEffect(intent) {
+        viewModel.initData(intent) { ready = true }
+    }
+
+    fun finishScreen() {
+        if (viewModel.sourceVerificationEnable) {
+            SourceVerificationHelp.checkResult(viewModel.sourceOrigin)
+        }
+        onFinish()
+    }
+
+    fun finishWithVerification() {
+        val currentWebView = webView ?: return finishScreen()
+        viewModel.saveVerificationResult(currentWebView, ::finishScreen)
+    }
+
+    BackHandler {
+        when {
+            customView != null -> customViewCallback?.onCustomViewHidden()
+            webView?.canGoBack() == true && (webView?.copyBackForwardList()?.size
+                ?: 0) > 1 -> webView?.goBack()
+
+            else -> finishScreen()
+        }
+    }
+
+    AppScaffold(
+        topBar = {
+            GlassTopAppBar(
+                title = viewModel.sourceName.ifBlank {
+                    intent.getStringExtra("title") ?: context.getString(R.string.loading)
+                },
+                navigationIcon = { TopBarNavigationButton(onClick = ::finishScreen) },
+                actions = {
+                    TopBarActionButton(
+                        onClick = { webView?.reload() },
+                        imageVector = Icons.Default.Refresh,
+                        contentDescription = context.getString(R.string.refresh),
+                    )
+                    TopBarActionButton(
+                        onClick = { webView?.url?.let(context::openUrl) },
+                        imageVector = Icons.Default.OpenInBrowser,
+                        contentDescription = context.getString(R.string.open_in_browser),
+                    )
+                    TopBarActionButton(
+                        onClick = ::finishWithVerification,
+                        imageVector = Icons.Default.Check,
+                        contentDescription = context.getString(R.string.ok),
+                    )
+                },
+            )
+        },
+    ) { paddingValues ->
+        Box(Modifier
+            .fillMaxSize()
+            .padding(paddingValues)) {
+            if (ready) {
+                AndroidView(
+                    modifier = Modifier.fillMaxSize(),
+                    factory = { viewContext ->
+                        WebView(viewContext).apply {
+                            settings.apply {
+                                mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
+                                domStorageEnabled = true
+                                allowContentAccess = true
+                                useWideViewPort = true
+                                loadWithOverviewMode = true
+                                builtInZoomControls = true
+                                displayZoomControls = false
+                                javaScriptEnabled = true
+                                viewModel.headerMap[AppConst.UA_NAME]?.let { userAgentString = it }
+                            }
+                            webViewClient = object : WebViewClient() {
+                                override fun shouldOverrideUrlLoading(
+                                    view: WebView,
+                                    request: WebResourceRequest
+                                ): Boolean =
+                                    request.url.scheme !in setOf("http", "https")
+
+                                override fun onPageFinished(view: WebView, url: String) {
+                                    AndroidCookieManager.getInstance().getCookie(url)
+                                        ?.let { CookieStore.setCookie(viewModel.sourceOrigin, it) }
+                                    if (viewModel.sourceVerificationEnable) view.evaluateJavascript(
+                                        "!!window._cf_chl_opt"
+                                    ) {
+                                        if (it == "true") {
+                                            cloudflareChallenge = true
+                                        } else if (cloudflareChallenge) {
+                                            finishWithVerification()
+                                        }
+                                    }
+                                }
+
+                                override fun onReceivedSslError(
+                                    view: WebView?,
+                                    handler: SslErrorHandler?,
+                                    error: android.net.http.SslError?
+                                ) {
+                                    handler?.proceed()
+                                }
+                            }
+                            webChromeClient = object : WebChromeClient() {
+                                override fun onProgressChanged(view: WebView?, newProgress: Int) {
+                                    progress = newProgress
+                                }
+
+                                override fun onShowCustomView(
+                                    view: View?,
+                                    callback: CustomViewCallback?
+                                ) {
+                                    customView = view; customViewCallback = callback
+                                }
+
+                                override fun onHideCustomView() {
+                                    customView = null; customViewCallback = null
+                                }
+                            }
+                            CookieManager.applyToWebView(viewModel.baseUrl)
+                            val html = viewModel.html
+                            if (html.isNullOrEmpty()) loadUrl(
+                                viewModel.baseUrl,
+                                viewModel.headerMap
+                            )
+                            else loadDataWithBaseURL(
+                                viewModel.baseUrl,
+                                html,
+                                "text/html",
+                                "utf-8",
+                                viewModel.baseUrl
+                            )
+                            webView = this
+                        }
+                    },
+                )
+            }
+            if (progress in 0..99) AppLinearProgressIndicator(Modifier.fillMaxWidth())
+        }
+    }
+    DisposableEffect(Unit) { onDispose { webView?.destroy() } }
+}

+ 3 - 1
app/src/main/java/io/legado/app/ui/login/SourceLoginSheets.kt

@@ -275,7 +275,7 @@ private fun SourceLoginWebSheet(
     onOpenExternalUrl: (String) -> Unit,
     onOpenExternalUrl: (String) -> Unit,
 ) {
 ) {
     AppModalBottomSheet(
     AppModalBottomSheet(
-        show = true,
+        show = state.mode == SourceLoginMode.Web && !state.loading,
         onDismissRequest = { onIntent(SourceLoginIntent.Back) },
         onDismissRequest = { onIntent(SourceLoginIntent.Back) },
         title = state.title,
         title = state.title,
         endAction = {
         endAction = {
@@ -286,6 +286,7 @@ private fun SourceLoginWebSheet(
             )
             )
         },
         },
         contentPaddingEnabled = false,
         contentPaddingEnabled = false,
+        sheetGesturesEnabled = false,
     ) {
     ) {
         SourceLoginWebView(state, onIntent, onOpenExternalUrl)
         SourceLoginWebView(state, onIntent, onOpenExternalUrl)
     }
     }
@@ -318,6 +319,7 @@ private fun SourceLoginWebView(
                         displayZoomControls = false
                         displayZoomControls = false
                         state.headers[AppConst.UA_NAME]?.let { userAgentString = it }
                         state.headers[AppConst.UA_NAME]?.let { userAgentString = it }
                     }
                     }
+                    isNestedScrollingEnabled = true
                     webViewClient = object : WebViewClient() {
                     webViewClient = object : WebViewClient() {
                         override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                         override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                             url?.let { currentIntent(SourceLoginIntent.WebPageStarted(it)) }
                             url?.let { currentIntent(SourceLoginIntent.WebPageStarted(it)) }

+ 15 - 0
app/src/main/java/io/legado/app/ui/main/MainActivity.kt

@@ -98,6 +98,21 @@ open class MainActivity : BaseComposeActivity() {
             bookUrl: String? = null,
             bookUrl: String? = null,
         ): Intent = MainIntent.createSourceLoginIntent(context, type, sourceKey, bookUrl)
         ): Intent = MainIntent.createSourceLoginIntent(context, type, sourceKey, bookUrl)
 
 
+        fun createWebViewIntent(
+            context: Context,
+            title: String? = null,
+            url: String,
+            sourceOrigin: String? = null,
+            sourceName: String? = null,
+            sourceType: Int? = null,
+            sourceVerificationEnable: Boolean = false,
+            refetchAfterSuccess: Boolean = true,
+            html: String? = null,
+        ): Intent = MainIntent.createWebViewIntent(
+            context, title, url, sourceOrigin, sourceName, sourceType,
+            sourceVerificationEnable, refetchAfterSuccess, html,
+        )
+
         fun createBookSourceManageIntent(context: Context) =
         fun createBookSourceManageIntent(context: Context) =
             MainIntent.createBookSourceManageIntent(context)
             MainIntent.createBookSourceManageIntent(context)
 
 

+ 30 - 0
app/src/main/java/io/legado/app/ui/main/MainIntent.kt

@@ -25,6 +25,14 @@ object MainIntent {
     const val EXTRA_SOURCE_LOGIN_TYPE = "source_login_type"
     const val EXTRA_SOURCE_LOGIN_TYPE = "source_login_type"
     const val EXTRA_SOURCE_LOGIN_KEY = "source_login_key"
     const val EXTRA_SOURCE_LOGIN_KEY = "source_login_key"
     const val EXTRA_EXPLORE_URL = "exploreUrl"
     const val EXTRA_EXPLORE_URL = "exploreUrl"
+    const val EXTRA_WEB_VIEW_TITLE = "title"
+    const val EXTRA_WEB_VIEW_URL = "url"
+    const val EXTRA_WEB_VIEW_SOURCE_ORIGIN = "sourceOrigin"
+    const val EXTRA_WEB_VIEW_SOURCE_NAME = "sourceName"
+    const val EXTRA_WEB_VIEW_SOURCE_TYPE = "sourceType"
+    const val EXTRA_WEB_VIEW_VERIFICATION = "sourceVerificationEnable"
+    const val EXTRA_WEB_VIEW_REFETCH = "refetchAfterSuccess"
+    const val EXTRA_WEB_VIEW_HTML = "html"
 
 
     const val EXTRA_RSS_SOURCE_URL = "extra_rss_source_url"
     const val EXTRA_RSS_SOURCE_URL = "extra_rss_source_url"
     const val EXTRA_RSS_SORT_URL = "extra_rss_sort_url"
     const val EXTRA_RSS_SORT_URL = "extra_rss_sort_url"
@@ -63,6 +71,28 @@ object MainIntent {
         putExtra(EXTRA_BOOK_URL, bookUrl)
         putExtra(EXTRA_BOOK_URL, bookUrl)
     }
     }
 
 
+    fun createWebViewIntent(
+        context: Context,
+        title: String? = null,
+        url: String,
+        sourceOrigin: String? = null,
+        sourceName: String? = null,
+        sourceType: Int? = null,
+        sourceVerificationEnable: Boolean = false,
+        refetchAfterSuccess: Boolean = true,
+        html: String? = null,
+    ): Intent = createLauncherIntent(context).apply {
+        putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_WEB_VIEW)
+        putExtra(EXTRA_WEB_VIEW_TITLE, title)
+        putExtra(EXTRA_WEB_VIEW_URL, url)
+        putExtra(EXTRA_WEB_VIEW_SOURCE_ORIGIN, sourceOrigin)
+        putExtra(EXTRA_WEB_VIEW_SOURCE_NAME, sourceName)
+        sourceType?.let { putExtra(EXTRA_WEB_VIEW_SOURCE_TYPE, it) }
+        putExtra(EXTRA_WEB_VIEW_VERIFICATION, sourceVerificationEnable)
+        putExtra(EXTRA_WEB_VIEW_REFETCH, refetchAfterSuccess)
+        putExtra(EXTRA_WEB_VIEW_HTML, html)
+    }
+
     fun createBookSourceManageIntent(context: Context): Intent =
     fun createBookSourceManageIntent(context: Context): Intent =
         createLauncherIntent(context).apply {
         createLauncherIntent(context).apply {
             putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_BOOK_SOURCE_MANAGE)
             putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_BOOK_SOURCE_MANAGE)

+ 21 - 0
app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt

@@ -86,6 +86,8 @@ import io.legado.app.ui.book.source.debug.BookSourceDebugViewModel
 import io.legado.app.ui.book.source.edit.BookSourceEditRoute
 import io.legado.app.ui.book.source.edit.BookSourceEditRoute
 import io.legado.app.ui.book.source.edit.BookSourceEditViewModel
 import io.legado.app.ui.book.source.edit.BookSourceEditViewModel
 import io.legado.app.ui.book.source.manage.BookSourceRouteScreen
 import io.legado.app.ui.book.source.manage.BookSourceRouteScreen
+import io.legado.app.ui.browser.WebViewModel
+import io.legado.app.ui.browser.WebViewRouteScreen
 import io.legado.app.ui.config.ConfigNavScreen
 import io.legado.app.ui.config.ConfigNavScreen
 import io.legado.app.ui.config.ai.AiConfigRouteScreen
 import io.legado.app.ui.config.ai.AiConfigRouteScreen
 import io.legado.app.ui.config.ai.AiModelEditRouteScreen
 import io.legado.app.ui.config.ai.AiModelEditRouteScreen
@@ -141,6 +143,25 @@ fun MainActivity.mainEntryProvider(
     onNavigateToRoute: (NavKey) -> Unit,
     onNavigateToRoute: (NavKey) -> Unit,
     onNavigateBack: () -> Unit,
     onNavigateBack: () -> Unit,
 ) = entryProvider {
 ) = entryProvider {
+    entry<MainRouteWebView> { route ->
+        val viewModel = koinViewModel<WebViewModel>(
+            key = "WebView:${route.url}:${route.sourceOrigin}:${route.sourceVerificationEnable}",
+        )
+        WebViewRouteScreen(
+            intent = Intent().apply {
+                putExtra("title", route.title)
+                putExtra("url", route.url)
+                putExtra("sourceOrigin", route.sourceOrigin)
+                putExtra("sourceName", route.sourceName)
+                route.sourceType?.let { putExtra("sourceType", it) }
+                putExtra("sourceVerificationEnable", route.sourceVerificationEnable)
+                putExtra("refetchAfterSuccess", route.refetchAfterSuccess)
+                putExtra("html", route.html)
+            },
+            viewModel = viewModel,
+            onFinish = onNavigateBack,
+        )
+    }
     entry<MainRouteSourceLogin>(
     entry<MainRouteSourceLogin>(
         metadata = ModalOverlaySceneStrategy.modalOverlay(),
         metadata = ModalOverlaySceneStrategy.modalOverlay(),
     ) { route ->
     ) { route ->

+ 13 - 0
app/src/main/java/io/legado/app/ui/main/MainNavKey.kt

@@ -17,6 +17,18 @@ data class MainRouteSourceLogin(
     val bookUrl: String? = null,
     val bookUrl: String? = null,
 ) : MainRoute
 ) : MainRoute
 
 
+@Serializable
+data class MainRouteWebView(
+    val title: String? = null,
+    val url: String,
+    val sourceOrigin: String? = null,
+    val sourceName: String? = null,
+    val sourceType: Int? = null,
+    val sourceVerificationEnable: Boolean = false,
+    val refetchAfterSuccess: Boolean = true,
+    val html: String? = null,
+) : MainRoute
+
 @Serializable
 @Serializable
 data object MainRouteBookSourceManage : MainRoute
 data object MainRouteBookSourceManage : MainRoute
 
 
@@ -215,6 +227,7 @@ data object MainRouteAbout : MainRoute
 object MainRouteConst {
 object MainRouteConst {
     const val ROUTE_MAIN = "main"
     const val ROUTE_MAIN = "main"
     const val ROUTE_SOURCE_LOGIN = "source/login"
     const val ROUTE_SOURCE_LOGIN = "source/login"
+    const val ROUTE_WEB_VIEW = "web/view"
     const val ROUTE_BOOK_SOURCE_MANAGE = "source/book/manage"
     const val ROUTE_BOOK_SOURCE_MANAGE = "source/book/manage"
     const val ROUTE_BOOK_SOURCE_EDIT = "source/book/edit"
     const val ROUTE_BOOK_SOURCE_EDIT = "source/book/edit"
     const val ROUTE_RSS_SOURCE_MANAGE = "source/rss/manage"
     const val ROUTE_RSS_SOURCE_MANAGE = "source/rss/manage"

+ 23 - 0
app/src/main/java/io/legado/app/ui/main/MainNavigator.kt

@@ -34,6 +34,8 @@ object MainNavigator {
                 backStack.add(route)
                 backStack.add(route)
             }
             }
 
 
+            is MainRouteWebView -> backStack.add(route)
+
             MainRouteBookSourceManage,
             MainRouteBookSourceManage,
             is MainRouteBookSourceEdit,
             is MainRouteBookSourceEdit,
             MainRouteRssSourceManage,
             MainRouteRssSourceManage,
@@ -332,6 +334,27 @@ object MainNavigator {
                 bookUrl = intent?.getStringExtra(MainIntent.EXTRA_BOOK_URL),
                 bookUrl = intent?.getStringExtra(MainIntent.EXTRA_BOOK_URL),
             )
             )
 
 
+            MainRouteConst.ROUTE_WEB_VIEW -> intent?.getStringExtra(MainIntent.EXTRA_WEB_VIEW_URL)
+                ?.takeIf { it.isNotBlank() }
+                ?.let { url ->
+                    MainRouteWebView(
+                        title = intent.getStringExtra(MainIntent.EXTRA_WEB_VIEW_TITLE),
+                        url = url,
+                        sourceOrigin = intent.getStringExtra(MainIntent.EXTRA_WEB_VIEW_SOURCE_ORIGIN),
+                        sourceName = intent.getStringExtra(MainIntent.EXTRA_WEB_VIEW_SOURCE_NAME),
+                        sourceType = if (intent.hasExtra(MainIntent.EXTRA_WEB_VIEW_SOURCE_TYPE)) {
+                            intent.getIntExtra(MainIntent.EXTRA_WEB_VIEW_SOURCE_TYPE, 0)
+                        } else null,
+                        sourceVerificationEnable = intent.getBooleanExtra(
+                            MainIntent.EXTRA_WEB_VIEW_VERIFICATION, false
+                        ),
+                        refetchAfterSuccess = intent.getBooleanExtra(
+                            MainIntent.EXTRA_WEB_VIEW_REFETCH, true
+                        ),
+                        html = intent.getStringExtra(MainIntent.EXTRA_WEB_VIEW_HTML),
+                    )
+                } ?: MainRouteHome
+
             MainRouteConst.ROUTE_BOOK_SOURCE_MANAGE -> MainRouteBookSourceManage
             MainRouteConst.ROUTE_BOOK_SOURCE_MANAGE -> MainRouteBookSourceManage
             MainRouteConst.ROUTE_BOOK_SOURCE_EDIT -> MainRouteBookSourceEdit(
             MainRouteConst.ROUTE_BOOK_SOURCE_EDIT -> MainRouteBookSourceEdit(
                 intent?.getStringExtra(MainIntent.EXTRA_SOURCE_URL)
                 intent?.getStringExtra(MainIntent.EXTRA_SOURCE_URL)

+ 31 - 6
app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt

@@ -55,6 +55,7 @@ fun AppModalBottomSheet(
     animateContentSize: Boolean = true,
     animateContentSize: Boolean = true,
     contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.modalWindowInsets },
     contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.modalWindowInsets },
     contentPaddingEnabled: Boolean = true,
     contentPaddingEnabled: Boolean = true,
+    sheetGesturesEnabled: Boolean = true,
     content: @Composable ColumnScope.() -> Unit
     content: @Composable ColumnScope.() -> Unit
 ) {
 ) {
     val colorScheme = LocalLegadoThemeColors.current.colorScheme
     val colorScheme = LocalLegadoThemeColors.current.colorScheme
@@ -72,7 +73,10 @@ fun AppModalBottomSheet(
                     ProvideAppDensity {
                     ProvideAppDensity {
                         ProvideAppContentColor(sheetContentColor) {
                         ProvideAppContentColor(sheetContentColor) {
                             CompositionLocalProvider(LocalUseMiuixWindowPopup provides true) {
                             CompositionLocalProvider(LocalUseMiuixWindowPopup provides true) {
-                                action()
+                                Box(
+                                    modifier = if (contentPaddingEnabled) Modifier
+                                    else Modifier.padding(start = 16.dp),
+                                ) { action() }
                             }
                             }
                         }
                         }
                     }
                     }
@@ -83,7 +87,10 @@ fun AppModalBottomSheet(
                     ProvideAppDensity {
                     ProvideAppDensity {
                         ProvideAppContentColor(sheetContentColor) {
                         ProvideAppContentColor(sheetContentColor) {
                             CompositionLocalProvider(LocalUseMiuixWindowPopup provides true) {
                             CompositionLocalProvider(LocalUseMiuixWindowPopup provides true) {
-                                action()
+                                Box(
+                                    modifier = if (contentPaddingEnabled) Modifier
+                                    else Modifier.padding(end = 16.dp),
+                                ) { action() }
                             }
                             }
                         }
                         }
                     }
                     }
@@ -94,7 +101,6 @@ fun AppModalBottomSheet(
             dragHandleColor = sheetDragHandleColor,
             dragHandleColor = sheetDragHandleColor,
             onDismissRequest = onDismissRequest,
             onDismissRequest = onDismissRequest,
             enableWindowDim = true,
             enableWindowDim = true,
-            allowDismiss = true
         ) {
         ) {
             ProvideAppDensity {
             ProvideAppDensity {
                 ProvideAppContentColor(sheetContentColor) {
                 ProvideAppContentColor(sheetContentColor) {
@@ -141,7 +147,8 @@ fun AppModalBottomSheet(
                     containerColor = sheetContainerColor,
                     containerColor = sheetContainerColor,
                     contentColor = sheetContentColor,
                     contentColor = sheetContentColor,
                     dragHandle = { BottomSheetDefaults.DragHandle(color = sheetDragHandleColor) },
                     dragHandle = { BottomSheetDefaults.DragHandle(color = sheetDragHandleColor) },
-                    contentWindowInsets = contentWindowInsets
+                    contentWindowInsets = contentWindowInsets,
+                    sheetGesturesEnabled = sheetGesturesEnabled,
                 ) {
                 ) {
                     ProvideAppDensity {
                     ProvideAppDensity {
                         Column(
                         Column(
@@ -171,7 +178,15 @@ fun AppModalBottomSheet(
                                     contentAlignment = Alignment.Center
                                     contentAlignment = Alignment.Center
                                 ) {
                                 ) {
                                     if (startAction != null) {
                                     if (startAction != null) {
-                                        Box(modifier = Modifier.align(Alignment.CenterStart)) {
+                                        Box(
+                                            modifier = Modifier
+                                                .align(Alignment.CenterStart)
+                                                .let {
+                                                    if (contentPaddingEnabled) it else it.padding(
+                                                        start = 16.dp
+                                                    )
+                                                }
+                                        ) {
                                             startAction()
                                             startAction()
                                         }
                                         }
                                     }
                                     }
@@ -189,7 +204,15 @@ fun AppModalBottomSheet(
                                     }
                                     }
 
 
                                     if (endAction != null) {
                                     if (endAction != null) {
-                                        Box(modifier = Modifier.align(Alignment.CenterEnd)) {
+                                        Box(
+                                            modifier = Modifier
+                                                .align(Alignment.CenterEnd)
+                                                .let {
+                                                    if (contentPaddingEnabled) it else it.padding(
+                                                        end = 16.dp
+                                                    )
+                                                }
+                                        ) {
                                             endAction()
                                             endAction()
                                         }
                                         }
                                     }
                                     }
@@ -220,6 +243,7 @@ fun <T> AppModalBottomSheet(
     endAction: @Composable (() -> Unit)? = null,
     endAction: @Composable (() -> Unit)? = null,
     animateContentSize: Boolean = true,
     animateContentSize: Boolean = true,
     contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.modalWindowInsets },
     contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.modalWindowInsets },
+    sheetGesturesEnabled: Boolean = true,
     content: @Composable ColumnScope.(T) -> Unit
     content: @Composable ColumnScope.(T) -> Unit
 ) {
 ) {
     var cachedData by remember { mutableStateOf(data) }
     var cachedData by remember { mutableStateOf(data) }
@@ -238,6 +262,7 @@ fun <T> AppModalBottomSheet(
         endAction = endAction,
         endAction = endAction,
         animateContentSize = animateContentSize,
         animateContentSize = animateContentSize,
         contentWindowInsets = contentWindowInsets,
         contentWindowInsets = contentWindowInsets,
+        sheetGesturesEnabled = sheetGesturesEnabled,
         content = {
         content = {
             if (currentData != null) {
             if (currentData != null) {
                 content(currentData)
                 content(currentData)