From adcb8a46936e51263eed74a1ead4b76cd3aaf8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E7=A5=A5=E4=BF=8A?= Date: Thu, 12 Oct 2023 13:45:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=BA=E5=9D=9B=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E8=AF=A6=E6=83=85WebView=E9=A2=84=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/gh/common/view/RichEditor.java | 104 ++++++++++++++++++ .../forum/home/CommunityHomeFragment.kt | 4 +- .../newsdetail/NewsDetailAdapter.java | 16 +-- .../qa/answer/detail/AnswerDetailFragment.kt | 17 ++- .../qa/article/detail/ArticleDetailAdapter.kt | 2 - .../detail/ArticleDetailContentViewHolder.kt | 90 +++++++-------- .../article/detail/ArticleDetailFragment.kt | 9 +- ...ger.kt => ArticleDetailWebCacheManager.kt} | 41 +++++-- .../qa/editor/OnLinkClickListener.kt | 16 ++- .../newdetail/NewQuestionDetailFragment.kt | 9 +- .../QuestionDetailContentViewHolder.kt | 86 ++++++++------- .../layout/item_article_detail_content.xml | 4 +- 12 files changed, 268 insertions(+), 130 deletions(-) rename app/src/main/java/com/gh/gamecenter/qa/article/detail/{ArticleWebCacheManager.kt => ArticleDetailWebCacheManager.kt} (72%) diff --git a/app/src/main/java/com/gh/common/view/RichEditor.java b/app/src/main/java/com/gh/common/view/RichEditor.java index ebee4a30c1..5a173af7b8 100644 --- a/app/src/main/java/com/gh/common/view/RichEditor.java +++ b/app/src/main/java/com/gh/common/view/RichEditor.java @@ -128,6 +128,10 @@ public class RichEditor extends WebView { private WebResourceRequestInterceptor mWebResourceRequestInterceptor; private PageFinishedListener mPageFinishedListener; + private OnLinkClickListener mOnLinkClickListener; + + private ImageListener mImageListener; + public RichEditor(Context context) { this(context, null); } @@ -146,6 +150,8 @@ public class RichEditor extends WebView { ExtensionsKt.fixUiModeIfNeeded(this); addJavascriptInterface(new NativeCallBack(), "NativeCallBack"); + addJavascriptInterface(new LinkClickNativeCallback(), "OnLinkClickListener"); + addJavascriptInterface(new ImageNativeCallback(), "imagelistener"); setVerticalScrollBarEnabled(false); setHorizontalScrollBarEnabled(false); @@ -195,6 +201,14 @@ public class RichEditor extends WebView { mInitialLayoutCallback = layoutCallback; } + public void setOnLinkClickListener(OnLinkClickListener onLinkClickListener) { + this.mOnLinkClickListener = onLinkClickListener; + } + + public void setImageListener(ImageListener imageListener) { + this.mImageListener = imageListener; + } + private void callback(String text) { mContents = text.replaceFirst(CALLBACK_SCHEME, ""); if (mTextChangeListener != null) { @@ -864,6 +878,96 @@ public class RichEditor extends WebView { } } + class LinkClickNativeCallback { + + /** + * 链接点击回调 + * @param content 回调内容 + */ + @JavascriptInterface + public void onClick(String content) { + if (mOnLinkClickListener != null) { + mOnLinkClickListener.onClick(content); + } + } + + /** + * 视频点击回调 + * @param content 回调内容 + */ + @JavascriptInterface + public void onVideoClick(String content) { + if (mOnLinkClickListener != null) { + mOnLinkClickListener.onVideoClick(content); + } + } + + /** + * 视频点击回调 + * @param url 视频链接 + * @param poster 视频封面 + */ + @JavascriptInterface + public void onVideoClick(String url, String poster) { + if (mOnLinkClickListener != null) { + mOnLinkClickListener.onVideoClick(url, poster); + } + } + + /** + * 视频点击回调 + * @param url 视频链接 + * @param poster 视频封面 + * @param position 视频下标位置 + */ + @JavascriptInterface + public void onVideoClick(String url, String poster, long position) { + if (mOnLinkClickListener != null) { + mOnLinkClickListener.onVideoClick(url, poster, position); + } + } + } + + class ImageNativeCallback { + /** + * 图片点击回调 + * @param url 图片链接 + */ + @JavascriptInterface + public void imageClick(String url) { + if (mImageListener != null) { + mImageListener.onImageClick(url); + } + } + + /** + * 图片加载回调 + * @param url 图片链接 + */ + @JavascriptInterface + public void imageArr(String url) { + if (mImageListener != null) { + mImageListener.onImageLoad(url); + } + } + } + + public interface OnLinkClickListener { + void onClick(String content); + + void onVideoClick(String content); + + void onVideoClick(String url, String poster); + + void onVideoClick(String url, String poster, long position); + } + + public interface ImageListener { + void onImageLoad(String url); + + void onImageClick(String url); + } + @Override public void invalidate() { super.invalidate(); diff --git a/app/src/main/java/com/gh/gamecenter/forum/home/CommunityHomeFragment.kt b/app/src/main/java/com/gh/gamecenter/forum/home/CommunityHomeFragment.kt index 4909f99166..050ad78793 100644 --- a/app/src/main/java/com/gh/gamecenter/forum/home/CommunityHomeFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/forum/home/CommunityHomeFragment.kt @@ -32,7 +32,7 @@ import com.gh.gamecenter.feature.entity.ArticleEntity import com.gh.gamecenter.feature.entity.ForumVideoEntity import com.gh.gamecenter.forum.search.ForumOrUserSearchActivity import com.gh.gamecenter.fragment.MainWrapperFragment -import com.gh.gamecenter.qa.article.detail.ArticleWebCacheManager +import com.gh.gamecenter.qa.article.detail.ArticleDetailWebCacheManager import com.gh.gamecenter.qa.article.edit.ArticleEditActivity import com.gh.gamecenter.qa.questions.edit.QuestionEditActivity import com.gh.gamecenter.qa.video.publish.VideoPublishActivity @@ -62,7 +62,7 @@ class CommunityHomeFragment : LazyFragment() { override fun onFragmentFirstVisible() { - ArticleWebCacheManager.init(requireContext()) + ArticleDetailWebCacheManager.init(requireContext().applicationContext) mViewModel = viewModelProvider() diff --git a/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailAdapter.java b/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailAdapter.java index 9ebafa0097..3b968bcaf8 100644 --- a/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailAdapter.java +++ b/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailAdapter.java @@ -255,8 +255,8 @@ public class NewsDetailAdapter extends BaseRecyclerAdapter { mRichEditor = viewHolder.binding.newsdetailItemWvContent; if (viewHolder.binding.newsdetailItemWvContent.getTag() == null) { - viewHolder.binding.newsdetailItemWvContent.addJavascriptInterface(new OnLinkClickListener(mContext, mEntrance, "", mNewsDetailEntity.getTitle(), "新闻详情"), "OnLinkClickListener"); - viewHolder.binding.newsdetailItemWvContent.addJavascriptInterface(new JsInterface(viewHolder.binding.newsdetailItemWvContent), "imagelistener"); + viewHolder.binding.newsdetailItemWvContent.setOnLinkClickListener(new OnLinkClickListener(mContext, mEntrance, "", mNewsDetailEntity.getTitle(), "新闻详情")); + viewHolder.binding.newsdetailItemWvContent.setImageListener(new ImageListener(viewHolder.binding.newsdetailItemWvContent)); mWebSettings = viewHolder.binding.newsdetailItemWvContent.getSettings(); mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); @@ -776,15 +776,15 @@ public class NewsDetailAdapter extends BaseRecyclerAdapter { } } - public class JsInterface { + public class ImageListener implements RichEditor.ImageListener { private RichEditor editor; - public JsInterface(RichEditor editor) { + public ImageListener(RichEditor editor) { this.editor = editor; } - @JavascriptInterface - public void imageClick(String url) { + @Override + public void onImageClick(String url) { if (url.contains("web_load_dfimg_icon.png")) { editor.post(() -> editor.replaceAllDfImageExcludeGif()); } else { @@ -801,8 +801,8 @@ public class NewsDetailAdapter extends BaseRecyclerAdapter { } } - @JavascriptInterface - public void imageArr(String url) { + @Override + public void onImageLoad(String url) { String defUrl = url.split("\\?")[0]; //web_load_dfimg_icon.png 查看大图按钮 if (!mNewsImgs.contains(defUrl) && !url.contains("web_load_dfimg_icon.png")) { diff --git a/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt index f24c6f9588..154cd81ec2 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt @@ -124,10 +124,9 @@ open class AnswerDetailFragment : ToolbarFragment() { @SuppressLint("AddJavascriptInterface") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - mBinding.richEditor.addJavascriptInterface(JsInterface(), "imagelistener") - mBinding.richEditor.addJavascriptInterface( - OnLinkClickListener(requireContext(), "", "", mEntrance, "回答详情"), - "OnLinkClickListener" + mBinding.richEditor.setImageListener(ImageListener()) + mBinding.richEditor.setOnLinkClickListener( + OnLinkClickListener(requireContext(), "", "", mEntrance, "回答详情") ) mSkeletonScreen = Skeleton.bind(mBinding.skeletonMask).load(R.layout.fragment_answer_detail_skeleton).shimmer(false).show() @@ -1256,9 +1255,9 @@ open class AnswerDetailFragment : ToolbarFragment() { dialog.setContentView(viewDialog) } - inner class JsInterface { - @JavascriptInterface - fun imageClick(url: String) { + inner class ImageListener : RichEditor.ImageListener { + + override fun onImageClick(url: String) { when { url.contains("web_load_dfimg_icon.png") -> mBaseHandler.post { mBinding.richEditor.replaceAllDfImageExcludeGif() } //url.contains(RichEditor.IMAGE_FLAG_THUMBNAIL) -> mBaseHandler.post { mBinding.richEditor.replaceDfImageByUrl(url) } @@ -1281,8 +1280,8 @@ open class AnswerDetailFragment : ToolbarFragment() { } } - @JavascriptInterface - fun imageArr(url: String) { + + override fun onImageLoad(url: String) { val defUrl = url.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] if (!mAnswersImages!!.contains(defUrl) && !url.contains("web_load_dfimg_icon.png")) { mAnswersImages.add(defUrl) diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailAdapter.kt b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailAdapter.kt index 420225e79c..f04c029b13 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailAdapter.kt @@ -8,14 +8,12 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView -import androidx.viewpager.widget.ViewPager.LayoutParams import androidx.viewpager.widget.ViewPager.OnPageChangeListener import com.gh.common.util.PackageUtils import com.gh.gamecenter.R import com.gh.gamecenter.adapter.viewholder.UnAvaliableWebviewViewHolder import com.gh.gamecenter.common.baselist.LoadStatus import com.gh.gamecenter.common.databinding.TabItemBinding -import com.gh.gamecenter.common.utils.dip2px import com.gh.gamecenter.common.utils.toBinding import com.gh.gamecenter.common.utils.visibleIf import com.gh.gamecenter.core.utils.MtaHelper diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailContentViewHolder.kt b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailContentViewHolder.kt index ad132f75c4..5a0a705dda 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailContentViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailContentViewHolder.kt @@ -7,8 +7,6 @@ import android.text.SpannableStringBuilder import android.text.Spanned import android.view.Gravity import android.view.View -import android.webkit.JavascriptInterface -import android.webkit.WebResourceResponse import android.webkit.WebView import android.widget.ImageView import android.widget.LinearLayout @@ -42,6 +40,26 @@ class ArticleDetailContentViewHolder( var viewModel: ArticleDetailViewModel ) : RecyclerView.ViewHolder(binding.root) { + val richEditor = ArticleDetailWebCacheManager + .attachToRichEditor(binding.richEditorContainer) + .apply { + setInputEnabled(false) + setPadding(16, 4, 16, 4) + enableForceDark(DarkModeUtils.isDarkModeOn(binding.root.context)) + setTransparentBackground() + setLayoutCallback { viewModel.articleRenderedLiveData.postValue(true) } + setPageFinishedListener { + viewModel.articlePageFinishedLiveData.postValue(true) + } + setChromeClientListener(object : RichEditor.WebChromeClientListener { + override fun onPageFinished(view: WebView?, url: String?) {} + + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + return DefaultUrlHandler.interceptUrl(binding.root.context, url ?: "", "帖子详情") + } + }) + } + private var mEntrance = "" val articleImgUrlList = ArrayList() @@ -59,41 +77,6 @@ class ArticleDetailContentViewHolder( titleTv.setTextColor(R.color.text_title.toColor(binding.root.context)) gameName.setTextColor(R.color.text_subtitleDesc.toColor(binding.root.context)) - richEditor.enableForceDark(DarkModeUtils.isDarkModeOn(binding.root.context)) - richEditor.setTransparentBackground() - richEditor.setInputEnabled(false) - richEditor.setPadding(16, 4, 16, 4) - richEditor.addJavascriptInterface(JsInterface(article.status ?: ""), "imagelistener") - richEditor.addJavascriptInterface( - OnLinkClickListener( - root.context, - article.title, - article.status ?: "", - mEntrance, - "帖子详情" - ), "OnLinkClickListener" - ) - richEditor.setLayoutCallback(object : EmptyCallback { - override fun onCallback() { - viewModel.articleRenderedLiveData.postValue(true) - } - }) - richEditor.setPageFinishedListener { - viewModel.articlePageFinishedLiveData.postValue(true) - } - richEditor.setChromeClientListener(object : RichEditor.WebChromeClientListener { - override fun onPageFinished(view: WebView?, url: String?) { - - } - - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - return DefaultUrlHandler.interceptUrl(binding.root.context, url ?: "", "帖子详情") - } - }) - richEditor.setWebResourceRequestInterceptor { view, url -> - ArticleWebCacheManager.shouldInterceptRequest(view, url) - } - approvalStatusTv.goneIf(article.status == "pass") statusContainer.goneIf(article.status == "pass") when (article.status) { @@ -186,6 +169,16 @@ class ArticleDetailContentViewHolder( userNameTv.text = article.user.name userIconIv.display(article.user.border, article.user.icon, article.user.auth?.icon) richEditor.setContentOwner(article.me.isContentOwner) + richEditor.setImageListener(ImageListener(article.status ?: "")) + richEditor.setOnLinkClickListener( + OnLinkClickListener( + root.context, + article.title, + article.status ?: "", + mEntrance, + "帖子详情" + ) + ) // 避免列表频繁刷新 if (richEditor.currentContent != article.content) { richEditor.setHtml(article.content, true) @@ -325,7 +318,7 @@ class ArticleDetailContentViewHolder( * 回调列表视频播放结束时的时间 */ fun onVideoPlayedCallback(url: String, position: Int) { - binding.richEditor.onVideoPlayedCallback(url, position) + richEditor.onVideoPlayedCallback(url, position) } fun updateFollowBtn(isFollowed: Boolean) { @@ -344,12 +337,12 @@ class ArticleDetailContentViewHolder( } - inner class JsInterface(val status: String) { - @JavascriptInterface - fun imageClick(url: String) { + inner class ImageListener(val status: String) : RichEditor.ImageListener { + + override fun onImageClick(url: String) { when { url.contains("web_load_dfimg_icon.png") -> { - runOnUiThread { binding.richEditor.replaceAllDfImageExcludeGif() } + runOnUiThread { richEditor.replaceAllDfImageExcludeGif() } } // url.contains(RichEditor.IMAGE_FLAG_THUMBNAIL) -> { // runOnUiThread { binding.richEditor.replaceDfImageByUrl(url) } @@ -378,12 +371,21 @@ class ArticleDetailContentViewHolder( } } - @JavascriptInterface - fun imageArr(url: String) { + override fun onImageLoad(url: String) { val defUrl = url.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] if (!articleImgUrlList.contains(defUrl) && !url.contains("web_load_dfimg_icon.png")) { articleImgUrlList.add(defUrl) } } } + + fun destroy() { + richEditor.setLayoutCallback(null) + richEditor.setImageListener(null) + richEditor.setOnLinkClickListener(null) + richEditor.setChromeClientListener(null) + richEditor.setContentOwner(false) + richEditor.setPageFinishedListener(null) + ArticleDetailWebCacheManager.detachFromRichEditor(binding.richEditorContainer) + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt index 127b5ada72..1d92252971 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt @@ -26,7 +26,6 @@ import com.gh.gamecenter.common.baselist.LoadType import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.constant.EntranceConsts import com.gh.gamecenter.common.entity.AdditionalParamsEntity -import com.gh.gamecenter.feature.entity.CommentEntity import com.gh.gamecenter.common.entity.CommunityEntity import com.gh.gamecenter.common.entity.NormalShareEntity import com.gh.gamecenter.common.eventbus.EBReuse @@ -41,6 +40,7 @@ import com.gh.gamecenter.eventbus.EBDeleteCommentDetail import com.gh.gamecenter.eventbus.EBDeleteDetail import com.gh.gamecenter.eventbus.EBTopCommunityChanged import com.gh.gamecenter.feature.entity.ArticleDraftEntity +import com.gh.gamecenter.feature.entity.CommentEntity import com.gh.gamecenter.feature.entity.Permissions import com.gh.gamecenter.login.user.UserManager import com.gh.gamecenter.qa.article.edit.ArticleEditActivity @@ -119,11 +119,11 @@ class ArticleDetailFragment : BaseCommentFragment 0) { if (imageSet.size == articleImgUrlList.size) { - binding.richEditor.replaceAllDfImage() + richEditor.replaceAllDfImage() } else { for (i in imageSet) { val url = articleImgUrlList[i.toInt()] - binding.richEditor.replaceDfImageByUrl(url) + richEditor.replaceDfImageByUrl(url) } } } @@ -165,6 +165,9 @@ class ArticleDetailFragment : BaseCommentFragment shouldInterceptRequest(view, url) } + } + parent.addView( + richEditor, + ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + ) + return cacheRichEditor + } + + fun detachFromRichEditor(parent: ViewGroup) { + this.cacheRichEditor.setHtml("", true) + this.cacheRichEditor.setWebResourceRequestInterceptor(null) + (this.cacheRichEditor.context as MutableContextWrapper).baseContext = parent.context.applicationContext + parent.removeView(this.cacheRichEditor) + } + /** * 拦截WebView的资源请求,并返回URL对应的缓存数据 */ - fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? { + private fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? { val urlWithoutQueryParams = url.split("?")[0] val cacheFile = File(cacheDir, MD5Utils.getUrlMD5(urlWithoutQueryParams)) diff --git a/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt b/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt index 5aaa9220b8..84daee1315 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt @@ -5,6 +5,7 @@ import android.webkit.JavascriptInterface import com.gh.gamecenter.common.base.activity.BaseActivity import com.gh.gamecenter.core.AppExecutor import com.gh.common.util.* +import com.gh.common.view.RichEditor import com.gh.gamecenter.GameDetailActivity import com.gh.gamecenter.core.utils.GsonUtils import com.gh.gamecenter.common.utils.clickToastByStatus @@ -25,9 +26,9 @@ class OnLinkClickListener( val status: String = "", val entrance: String, val path: String, -) { - @JavascriptInterface - fun onClick(content: String) { +) : RichEditor.OnLinkClickListener { + + override fun onClick(content: String) { AppExecutor.uiExecutor.execute { tryWithDefaultCatch { val insertEntity = GsonUtils.fromJson(content, EditorInsertEntity::class.java) @@ -70,8 +71,7 @@ class OnLinkClickListener( } } - @JavascriptInterface - fun onVideoClick(content: String) { + override fun onVideoClick(content: String) { AppExecutor.uiExecutor.execute { tryWithDefaultCatch { val videoEntity = GsonUtils.fromJson(content, MyVideoEntity::class.java) @@ -82,15 +82,13 @@ class OnLinkClickListener( } } - @JavascriptInterface - fun onVideoClick(url: String, poster: String) { + override fun onVideoClick(url: String, poster: String) { clickToastByStatus(status) { FullScreenVideoActivity.start(context, title, url, poster) } } - @JavascriptInterface - fun onVideoClick(url: String, poster: String, position: Long) { + override fun onVideoClick(url: String, poster: String, position: Long) { clickToastByStatus(status) { FullScreenVideoActivity.start(context, title, url, poster, position) } diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt index af6e924b37..e7af4fa99d 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt @@ -108,11 +108,11 @@ class NewQuestionDetailFragment : mAdapter?.questionDetailVH?.run { if (questionImgUrlList.size > 0) { if (imageSet.size == questionImgUrlList.size) { - binding.richEditor.replaceAllDfImage() + richEditor.replaceAllDfImage() } else { for (i in imageSet) { val url = questionImgUrlList[i.toInt()] - binding.richEditor.replaceDfImageByUrl(url) + richEditor.replaceDfImageByUrl(url) } } } @@ -742,6 +742,11 @@ class NewQuestionDetailFragment : mBinding.root.setBackgroundColor(Color.TRANSPARENT) } + override fun onDestroyView() { + super.onDestroyView() + mAdapter?.questionDetailVH?.destroy() + } + override fun onDarkModeChanged() { super.onDarkModeChanged() mAdapter?.let { it.notifyItemRangeChanged(0, it.itemCount) } diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/QuestionDetailContentViewHolder.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/QuestionDetailContentViewHolder.kt index bd5da563e2..1023db95ae 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/QuestionDetailContentViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/QuestionDetailContentViewHolder.kt @@ -5,8 +5,6 @@ import android.app.Activity import android.text.SpannableStringBuilder import android.text.Spanned import android.view.View -import android.webkit.JavascriptInterface -import android.webkit.WebResourceResponse import android.webkit.WebView import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity @@ -27,7 +25,7 @@ import com.gh.gamecenter.core.runOnUiThread import com.gh.gamecenter.core.utils.* import com.gh.gamecenter.databinding.ItemArticleDetailContentBinding import com.gh.gamecenter.login.user.UserManager -import com.gh.gamecenter.qa.article.detail.ArticleWebCacheManager +import com.gh.gamecenter.qa.article.detail.ArticleDetailWebCacheManager import com.gh.gamecenter.qa.article.detail.EditHistoryDialog import com.gh.gamecenter.qa.editor.OnLinkClickListener import com.gh.gamecenter.qa.entity.QuestionsDetailEntity @@ -38,43 +36,30 @@ class QuestionDetailContentViewHolder( var viewModel: NewQuestionDetailViewModel ) : RecyclerView.ViewHolder(binding.root) { + val richEditor = ArticleDetailWebCacheManager + .attachToRichEditor(binding.richEditorContainer) + .apply { + setInputEnabled(false) + setPadding(16, 4, 16, 4) + enableForceDark(DarkModeUtils.isDarkModeOn(binding.root.context)) + setTransparentBackground() + setLayoutCallback { viewModel.questionRenderedLiveData.postValue(true) } + setPageFinishedListener { viewModel.questionPageFinishedLiveData.postValue(true) } + setChromeClientListener(object : RichEditor.WebChromeClientListener { + override fun onPageFinished(view: WebView?, url: String?) {} + + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + return DefaultUrlHandler.interceptUrl(binding.root.context, url ?: "", "问题详情") + } + }) + } + private var mEntrance = "" val questionImgUrlList = ArrayList() @SuppressLint("AddJavascriptInterface") fun bindView(question: QuestionsDetailEntity) { binding.run { - richEditor.setInputEnabled(false) - richEditor.enableForceDark(DarkModeUtils.isDarkModeOn(binding.root.context)) - richEditor.setTransparentBackground() - richEditor.setPadding(16, 4, 16, 4) - richEditor.addJavascriptInterface(JsInterface(question.status), "imagelistener") - richEditor.addJavascriptInterface( - OnLinkClickListener( - root.context, question.title - ?: "", question.status, mEntrance, "问题详情" - ), "OnLinkClickListener" - ) - richEditor.setLayoutCallback(object : EmptyCallback { - override fun onCallback() { - viewModel.questionRenderedLiveData.postValue(true) - } - }) - richEditor.setPageFinishedListener { - viewModel.questionPageFinishedLiveData.postValue(true) - } - richEditor.setChromeClientListener(object : RichEditor.WebChromeClientListener { - override fun onPageFinished(view: WebView?, url: String?) { - - } - - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - return DefaultUrlHandler.interceptUrl(binding.root.context, url ?: "", "问题详情") - } - }) - richEditor.setWebResourceRequestInterceptor { view, url -> - ArticleWebCacheManager.shouldInterceptRequest(view, url) - } approvalStatusTv.goneIf(question.status == "pass") statusContainer.goneIf(question.status == "pass") when (question.status) { @@ -156,6 +141,16 @@ class QuestionDetailContentViewHolder( userNameTv.text = question.user.name userIconIv.display(question.user.border, question.user.icon, question.user.auth?.icon) richEditor.setContentOwner(question.me.isContentOwner) + richEditor.setImageListener(ImageListener(question.status)) + richEditor.setOnLinkClickListener( + OnLinkClickListener( + root.context, + question.title ?: "", + question.status, + mEntrance, + "问题详情" + ) + ) // 避免列表频繁刷新 if (richEditor.currentContent != question.description) { richEditor.setHtml(question.description, true) @@ -280,15 +275,15 @@ class QuestionDetailContentViewHolder( * 回调列表视频播放结束时的时间 */ fun onVideoPlayedCallback(url: String, position: Int) { - binding.richEditor.onVideoPlayedCallback(url, position) + richEditor.onVideoPlayedCallback(url, position) } - inner class JsInterface(val status: String) { - @JavascriptInterface - fun imageClick(url: String) { + inner class ImageListener(val status: String) : RichEditor.ImageListener { + + override fun onImageClick(url: String) { when { url.contains("web_load_dfimg_icon.png") -> { - runOnUiThread { binding.richEditor.replaceAllDfImageExcludeGif() } + runOnUiThread { richEditor.replaceAllDfImageExcludeGif() } } // url.contains(RichEditor.IMAGE_FLAG_THUMBNAIL) -> { // runOnUiThread { binding.richEditor.replaceDfImageByUrl(url) } @@ -317,12 +312,21 @@ class QuestionDetailContentViewHolder( } } - @JavascriptInterface - fun imageArr(url: String) { + override fun onImageLoad(url: String) { val defUrl = url.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] if (!questionImgUrlList.contains(defUrl) && !url.contains("web_load_dfimg_icon.png")) { questionImgUrlList.add(defUrl) } } } + + fun destroy() { + richEditor.setLayoutCallback(null) + richEditor.setImageListener(null) + richEditor.setOnLinkClickListener(null) + richEditor.setChromeClientListener(null) + richEditor.setContentOwner(false) + richEditor.setPageFinishedListener(null) + ArticleDetailWebCacheManager.detachFromRichEditor(binding.richEditorContainer) + } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_article_detail_content.xml b/app/src/main/res/layout/item_article_detail_content.xml index 9674a85814..64801e3540 100644 --- a/app/src/main/res/layout/item_article_detail_content.xml +++ b/app/src/main/res/layout/item_article_detail_content.xml @@ -217,8 +217,8 @@ android:textStyle="bold" tools:text="这是一个很长很长很长很长很长很长很长很长很长很长的标题" /> -