Skip to content

Commit

Permalink
Merge pull request #88 from anilibria/fix/new-new-crashes
Browse files Browse the repository at this point in the history
Fix/new new crashes
  • Loading branch information
RadiationX authored Dec 28, 2022
2 parents c5d9994 + c05e343 commit 0b4f872
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import ru.radiationx.shared_app.imageloader.showImageUrl
* Created by radiationx on 13.01.18.
*/
class SuggestionDelegate(
private val clickListener: (SuggestionItemState) -> Unit
private val clickListener: (SuggestionItemState) -> Unit,
) : AppAdapterDelegate<SuggestionListItem, ListItem, SuggestionDelegate.ViewHolder>(
R.layout.item_fast_search,
{ it is SuggestionListItem },
Expand All @@ -32,7 +32,7 @@ class SuggestionDelegate(

class ViewHolder(
itemView: View,
private val clickListener: (SuggestionItemState) -> Unit
private val clickListener: (SuggestionItemState) -> Unit,
) : RecyclerView.ViewHolder(itemView) {

private val binding by viewBinding<ItemFastSearchBinding>()
Expand All @@ -55,7 +55,8 @@ class SuggestionDelegate(
state.matchRanges.filterNot { it.isEmpty() }.forEach {
val span = StyleSpan(Typeface.BOLD)
val flags = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
spannableTitle.setSpan(span, it.first, it.last, flags)
val endIndex = (it.last + 1).coerceAtMost(state.title.length)
spannableTitle.setSpan(span, it.first, endIndex, flags)
}
binding.itemTitle.setText(spannableTitle, TextView.BufferType.SPANNABLE)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,41 @@
package ru.radiationx.anilibria.ui.fragments.comments

import android.app.AlertDialog
import android.graphics.Bitmap
import android.net.http.SslError
import android.os.Build
import android.os.Bundle
import android.os.Message
import android.util.Log
import android.view.View
import android.webkit.*
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient
import android.webkit.WebViewClient
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import by.kirich1409.viewbindingdelegate.viewBinding
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import ru.radiationx.anilibria.R
import ru.radiationx.anilibria.apptheme.AppThemeController
import ru.radiationx.anilibria.databinding.FragmentVkCommentsBinding
import ru.radiationx.anilibria.extension.generateWithTheme
import ru.radiationx.anilibria.extension.getWebStyleType
import ru.radiationx.anilibria.extension.isDark
import ru.radiationx.anilibria.model.loading.hasAnyLoading
import ru.radiationx.anilibria.ui.common.Templates
import ru.radiationx.anilibria.ui.common.webpage.WebPageStateWebViewClient
import ru.radiationx.anilibria.ui.common.webpage.WebPageViewState
import ru.radiationx.anilibria.ui.common.webpage.compositeWebViewClientOf
import ru.radiationx.anilibria.ui.fragments.BaseDimensionsFragment
import ru.radiationx.anilibria.ui.fragments.comments.webview.VkWebChromeClient
import ru.radiationx.anilibria.ui.fragments.comments.webview.VkWebViewClient
import ru.radiationx.anilibria.ui.widgets.ExtendedWebView
import ru.radiationx.data.MainClient
import ru.radiationx.data.datasource.remote.IClient
import ru.radiationx.quill.get
import ru.radiationx.quill.inject
import ru.radiationx.quill.viewModel
import ru.radiationx.shared.ktx.android.toBase64
import ru.radiationx.shared.ktx.android.toException
import ru.radiationx.shared_app.common.SystemUtils
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets


class VkCommentsFragment : BaseDimensionsFragment(R.layout.fragment_vk_comments) {
Expand Down Expand Up @@ -83,15 +78,21 @@ class VkCommentsFragment : BaseDimensionsFragment(R.layout.fragment_vk_comments)
webViewScrollPos = it.getInt(WEB_VIEW_SCROLL_Y, 0)
}

binding.webView.setJsLifeCycleListener(jsLifeCycleListener)
binding.webView.setJsLifeCycleListener(jsLifeCycleListener(binding.webView))
binding.webView.addJavascriptInterface(this, "KEK")

binding.webView.settings.apply {
this.databaseEnabled = true
}

binding.webView.webViewClient = composite
binding.webView.webChromeClient = vkWebChromeClient
binding.webView.webViewClient = composite(
viewModel = viewModel,
systemUtils = systemUtils,
networkClient = get(MainClient::class),
commentsCss = get(),
appThemeController = appThemeController
)
binding.webView.webChromeClient = VkWebChromeClient(viewModel)

val cookieManager = CookieManager.getInstance()

Expand Down Expand Up @@ -122,8 +123,8 @@ class VkCommentsFragment : BaseDimensionsFragment(R.layout.fragment_vk_comments)

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
binding.webView.let {
outState.putInt(WEB_VIEW_SCROLL_Y, it.scrollY)
if (view != null) {
outState.putInt(WEB_VIEW_SCROLL_Y, binding.webView.scrollY)
}
}

Expand All @@ -139,10 +140,10 @@ class VkCommentsFragment : BaseDimensionsFragment(R.layout.fragment_vk_comments)

override fun onDestroyView() {
super.onDestroyView()
binding.webView.endWork()
binding.webView.setJsLifeCycleListener(null)
binding.webView.webViewClient = WebViewClient()
binding.webView.webChromeClient = WebChromeClient()
binding.webView.endWork()
}

private fun showState(state: VkCommentsScreenState) {
Expand Down Expand Up @@ -175,13 +176,15 @@ class VkCommentsFragment : BaseDimensionsFragment(R.layout.fragment_vk_comments)
binding.webView.evalJs("ViewModel.setText('content','${comments.script.toBase64()}');")
}

private val jsLifeCycleListener = object : ExtendedWebView.JsLifeCycleListener {
private fun jsLifeCycleListener(
webView: ExtendedWebView,
) = object : ExtendedWebView.JsLifeCycleListener {
override fun onDomContentComplete(actions: ArrayList<String>?) {
}

override fun onPageComplete(actions: ArrayList<String>?) {
binding.webView.syncWithJs {
binding.webView.scrollTo(0, webViewScrollPos)
webView.syncWithJs {
webView.scrollTo(0, webViewScrollPos)
}
}
}
Expand All @@ -191,182 +194,17 @@ class VkCommentsFragment : BaseDimensionsFragment(R.layout.fragment_vk_comments)
Log.d("VkCommentsFragment.log", string)
}

private val vkWebChromeClient = object : WebChromeClient() {

private val jsErrorRegex = Regex("Uncaught (?:\\w+)Error:")
private val sourceRegex = Regex("https?://vk\\.com/")

override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
return super.onConsoleMessage(consoleMessage)
}

override fun onConsoleMessage(message: String?, lineNumber: Int, sourceID: String?) {
super.onConsoleMessage(message, lineNumber, sourceID)
val hasJsError = jsErrorRegex.containsMatchIn(message.orEmpty())
val isVkSource = sourceRegex.containsMatchIn(sourceID.orEmpty())
if (hasJsError && isVkSource) {
viewModel.notifyNewJsError()
}
}

override fun onCreateWindow(
view: WebView?,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: Message
): Boolean {
val newWebView = WebView(requireContext())
AlertDialog.Builder(requireContext())
.setView(newWebView)
.show()
val transport = resultMsg.obj as WebView.WebViewTransport
transport.webView = newWebView
resultMsg.sendToTarget()
return true
}
}

private val stateClient = WebPageStateWebViewClient {
viewModel.onNewPageState(it)
}

private val vkWebViewClient = object : WebViewClient() {

var loadingFinished = true
var redirect = false
var authCheckIntercepted = false

private val authRequestRegex = Regex("oauth\\.vk\\.com\\/authorize\\?|vk\\.com\\/login\\?")
private val authCheckRegex = Regex("vk\\.com\\/login\\?act=authcheck")
private val commentsRegex = Regex("widget_comments(?:\\.\\w+?)?\\.css")

@Suppress("DEPRECATION", "OverridingDeprecatedMember")
override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {
return tryInterceptAuthCheck(view, url)
?: tryInterceptComments(view, url)
?: super.shouldInterceptRequest(view, url)
}

private fun tryInterceptAuthCheck(view: WebView?, url: String?): WebResourceResponse? {
val needAuth = authCheckRegex.containsMatchIn(url.orEmpty())
if (needAuth && !authCheckIntercepted) {
authCheckIntercepted = true
val mobileUrl = if (!url.orEmpty().contains("/m.vk.com/")) {
url.orEmpty().replace("vk.com", "/m.vk.com/")
} else {
url.orEmpty()
}
viewModel.authRequest(mobileUrl)
}
return null
}

private fun tryInterceptComments(view: WebView?, url: String?): WebResourceResponse? {
val needIntercept = commentsRegex.containsMatchIn(url.orEmpty())
return if (needIntercept) {
val networkClient = get<IClient>(MainClient::class)
val cssSrc = try {
runBlocking { networkClient.get(url.orEmpty(), emptyMap()) }
} catch (ex: Throwable) {
Timber.e(ex)
return WebResourceResponse(
"text/css",
"utf-8",
ByteArrayInputStream(
ex.message.orEmpty().toByteArray(StandardCharsets.UTF_8)
)
)
}
var newCss = cssSrc

val commentsCss = get<VkCommentsCss>()
val fixCss = if (appThemeController.getTheme().isDark()) {
commentsCss.dark
} else {
commentsCss.light
}

newCss += fixCss

val newData = ByteArrayInputStream(newCss.toByteArray(StandardCharsets.UTF_8))
WebResourceResponse("text/css", "utf-8", newData)
} else {
null
}
}

@Suppress("OverridingDeprecatedMember")
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
val cookies = CookieManager.getInstance().getCookie(url)
if (!loadingFinished) {
redirect = true
}

loadingFinished = false

if (url.orEmpty().contains(authRequestRegex)) {
viewModel.authRequest(url.orEmpty())
return true
}
systemUtils.externalLink(url.orEmpty())
return true
}

override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
authCheckIntercepted = false
loadingFinished = false
}

override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)

if (!redirect) {
loadingFinished = true
}

if (loadingFinished && !redirect) {
//progressBar.visibility = View.GONE
viewModel.onPageLoaded()
} else {
redirect = false
}
}

override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
super.onReceivedSslError(view, handler, error)
viewModel.onPageCommitError(error.toException())
}

override fun onReceivedHttpError(
view: WebView?,
request: WebResourceRequest?,
errorResponse: WebResourceResponse?
) {
super.onReceivedHttpError(view, request, errorResponse)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && view?.url == request?.url?.toString()) {
viewModel.onPageCommitError(errorResponse.toException(request))
}
}

override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
super.onReceivedError(view, request, error)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && view?.url == request?.url?.toString()) {
viewModel.onPageCommitError(error.toException(request))
}
}
}

private val composite = compositeWebViewClientOf(
stateClient,
vkWebViewClient
private fun composite(
viewModel: VkCommentsViewModel,
systemUtils: SystemUtils,
networkClient: IClient,
commentsCss: VkCommentsCss,
appThemeController: AppThemeController,
) = compositeWebViewClientOf(
WebPageStateWebViewClient {
viewModel.onNewPageState(it)
},
VkWebViewClient(viewModel, systemUtils, networkClient, commentsCss, appThemeController)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ru.radiationx.anilibria.ui.fragments.comments.webview

import android.app.AlertDialog
import android.os.Message
import android.webkit.ConsoleMessage
import android.webkit.WebChromeClient
import android.webkit.WebView
import ru.radiationx.anilibria.ui.fragments.comments.VkCommentsViewModel

class VkWebChromeClient(
private val viewModel: VkCommentsViewModel,
) : WebChromeClient() {

private val jsErrorRegex = Regex("Uncaught (?:\\w+)Error:")
private val sourceRegex = Regex("https?://vk\\.com/")

override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
return super.onConsoleMessage(consoleMessage)
}

override fun onConsoleMessage(message: String?, lineNumber: Int, sourceID: String?) {
super.onConsoleMessage(message, lineNumber, sourceID)
val hasJsError = jsErrorRegex.containsMatchIn(message.orEmpty())
val isVkSource = sourceRegex.containsMatchIn(sourceID.orEmpty())
if (hasJsError && isVkSource) {
viewModel.notifyNewJsError()
}
}

override fun onCreateWindow(
view: WebView?,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: Message,
): Boolean {
if (view == null) {
return false
}
val newWebView = WebView(view.context)
AlertDialog.Builder(view.context)
.setView(newWebView)
.show()
val transport = resultMsg.obj as WebView.WebViewTransport
transport.webView = newWebView
resultMsg.sendToTarget()
return true
}
}
Loading

0 comments on commit 0b4f872

Please sign in to comment.