Răsfoiți Sursa

[新增] 字典可显示图片 (@Luoyacheng)

HapeLee 10 luni în urmă
părinte
comite
129cd83639

+ 129 - 0
app/src/main/java/io/legado/app/help/GlideImageGetter.kt

@@ -0,0 +1,129 @@
+package io.legado.app.help
+
+import android.content.Context
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.text.Html
+import android.widget.TextView
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.transition.Transition
+import java.lang.ref.WeakReference
+
+class GlideImageGetter(private val context: Context, textView: TextView, private val originalHtml: String) :
+    Html.ImageGetter {
+    companion object {
+        fun create(context: Context, textView: TextView, html: String): GlideImageGetter {
+            return GlideImageGetter(context, textView, html)
+        }
+        private fun createEmptyDrawable(): Drawable {
+            return object : Drawable() {
+                override fun draw(canvas: android.graphics.Canvas) = Unit
+                override fun setAlpha(alpha: Int) = Unit
+                override fun setColorFilter(colorFilter: ColorFilter?) = Unit
+                @Deprecated("Deprecated in Java")
+                override fun getOpacity(): Int = PixelFormat.TRANSPARENT
+            }
+        }
+    }
+
+    private val loadedDrawables = mutableMapOf<String, Drawable>()
+    private val textViewRef = WeakReference(textView)
+    private val targets = mutableSetOf<CustomTarget<*>>()
+
+    override fun getDrawable(source: String?): Drawable {
+        if (source.isNullOrBlank()) {
+            return createEmptyDrawable()
+        }
+        val urlDrawable = GlideUrlDrawable()
+        val target = createImageTarget(urlDrawable, source)
+        targets.add(target)
+        Glide.with(context)
+            .load(source)
+            .into(target)
+        return urlDrawable
+    }
+
+    private fun createImageTarget(
+        urlDrawable: GlideUrlDrawable,
+        source: String
+    ): CustomTarget<Drawable> {
+        return object : CustomTarget<Drawable>() {
+            override fun onResourceReady(
+                resource: Drawable,
+                transition: Transition<in Drawable>?
+            ) {
+                targets.remove(this)
+                val textView = textViewRef.get() ?: return
+                val availableWidth = textView.width - textView.paddingLeft - textView.paddingRight
+                val maxWidth = availableWidth.takeIf { it > 0 } ?: 700
+                val drawableWidth = resource.intrinsicWidth.coerceAtLeast(1)
+                val drawableHeight = resource.intrinsicHeight.coerceAtLeast(1)
+                val scale = if (drawableWidth > maxWidth) {
+                    maxWidth.toFloat() / drawableWidth
+                } else {
+                    1f
+                }
+                val width = (drawableWidth * scale).toInt()
+                val height = (drawableHeight * scale).toInt()
+                resource.setBounds(0, 0, width, height)
+                urlDrawable.setDrawable(resource)
+                loadedDrawables[source] = urlDrawable
+                refreshTextView()
+            }
+
+            override fun onLoadCleared(placeholder: Drawable?) {
+                targets.remove(this)
+                urlDrawable.setDrawable(placeholder)
+                refreshTextView()
+            }
+
+            override fun onLoadFailed(errorDrawable: Drawable?) {
+                targets.remove(this)
+                val placeholder = errorDrawable ?: createEmptyDrawable()
+                urlDrawable.setDrawable(placeholder)
+                loadedDrawables[source] = urlDrawable
+                refreshTextView()
+            }
+
+            override fun onLoadStarted(placeholder: Drawable?) {
+                urlDrawable.setDrawable(placeholder)
+            }
+        }
+    }
+
+    fun clear() {
+        targets.forEach {
+            Glide.with(context).clear(it)
+        }
+        targets.clear()
+        textViewRef.clear()
+    }
+
+    private fun refreshTextView() {
+        val textView = textViewRef.get() ?: return
+        textView.post {
+            // 创建新的ImageGetter,但使用缓存
+            val cachedImageGetter = CachedImageGetter(loadedDrawables)
+            val newHtml =
+                Html.fromHtml(originalHtml,  Html.FROM_HTML_MODE_COMPACT, cachedImageGetter, null)
+            textView.text = newHtml
+        }
+    }
+
+    private class CachedImageGetter(
+        private val drawableCache: Map<String, Drawable>
+    ) : Html.ImageGetter {
+        override fun getDrawable(source: String?): Drawable {
+            if (source.isNullOrBlank()) {
+                return createEmptyDrawable()
+            }
+            drawableCache[source]?.let { return it }
+            return createEmptyDrawable().apply {
+                setBounds(0, 0, 1, 1)
+            }
+        }
+    }
+}

+ 33 - 0
app/src/main/java/io/legado/app/help/GlideUrlDrawable.kt

@@ -0,0 +1,33 @@
+package io.legado.app.help
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+
+class GlideUrlDrawable(): Drawable() {
+    private var mDrawable: Drawable? = null
+    fun setDrawable(drawable: Drawable?) {
+        this.mDrawable = drawable
+        drawable?.bounds?.let { bounds ->
+            setBounds(bounds)
+        }
+        invalidateSelf()
+    }
+
+    override fun draw(canvas: Canvas) {
+        mDrawable?.draw(canvas)
+    }
+    @Deprecated("Deprecated in Java")
+    override fun getOpacity(): Int {
+        return PixelFormat.TRANSLUCENT
+    }
+
+    override fun setAlpha(alpha: Int) {
+        mDrawable?.alpha = alpha
+    }
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        mDrawable?.colorFilter = colorFilter
+    }
+}

+ 11 - 1
app/src/main/java/io/legado/app/ui/dict/DictDialog.kt

@@ -9,6 +9,7 @@ import io.legado.app.R
 import io.legado.app.base.BaseBottomSheetDialogFragment
 import io.legado.app.base.BaseBottomSheetDialogFragment
 import io.legado.app.data.entities.DictRule
 import io.legado.app.data.entities.DictRule
 import io.legado.app.databinding.DialogDictBinding
 import io.legado.app.databinding.DialogDictBinding
+import io.legado.app.help.GlideImageGetter
 import io.legado.app.utils.gone
 import io.legado.app.utils.gone
 import io.legado.app.utils.setHtml
 import io.legado.app.utils.setHtml
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toastOnUi
@@ -28,8 +29,8 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
 
 
     private val viewModel by viewModels<DictViewModel>()
     private val viewModel by viewModels<DictViewModel>()
     private val binding by viewBinding(DialogDictBinding::bind)
     private val binding by viewBinding(DialogDictBinding::bind)
-
     private var word: String? = null
     private var word: String? = null
+    private var glideImageGetter: GlideImageGetter? = null
 
 
     override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
     override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
         binding.tvDict.movementMethod = LinkMovementMethod()
         binding.tvDict.movementMethod = LinkMovementMethod()
@@ -44,6 +45,12 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
         observeDicts()
         observeDicts()
     }
     }
 
 
+    override fun onDestroyView() {
+        glideImageGetter?.clear()
+        glideImageGetter = null
+        super.onDestroyView()
+    }
+
     private fun setupTabs() {
     private fun setupTabs() {
         binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
         binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
             override fun onTabReselected(tab: TabLayout.Tab) = Unit
             override fun onTabReselected(tab: TabLayout.Tab) = Unit
@@ -90,6 +97,9 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
             } else {
             } else {
                 binding.tvDict.visible()
                 binding.tvDict.visible()
                 binding.tvDict.setHtml(result)
                 binding.tvDict.setHtml(result)
+                glideImageGetter?.clear()
+                glideImageGetter = GlideImageGetter.create(requireContext(), binding.tvDict, result)
+                binding.tvDict.setHtml(result, glideImageGetter)
             }
             }
         }
         }
     }
     }

+ 3 - 4
app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt

@@ -5,6 +5,7 @@ import android.os.Bundle
 import android.view.View
 import android.view.View
 import android.view.textclassifier.TextClassifier
 import android.view.textclassifier.TextClassifier
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
+import com.bumptech.glide.Glide
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.BaseBottomSheetDialogFragment
 import io.legado.app.base.BaseBottomSheetDialogFragment
 import io.legado.app.databinding.DialogTextViewBinding
 import io.legado.app.databinding.DialogTextViewBinding
@@ -66,13 +67,11 @@ class TextDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_text_view) {
             val content = IntentData.get(it.getString("content")) ?: ""
             val content = IntentData.get(it.getString("content")) ?: ""
             when (it.getString("mode")) {
             when (it.getString("mode")) {
                 Mode.MD.name -> viewLifecycleOwner.lifecycleScope.launch {
                 Mode.MD.name -> viewLifecycleOwner.lifecycleScope.launch {
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                        binding.textView.setTextClassifier(TextClassifier.NO_OP)
-                    }
+                    binding.textView.setTextClassifier(TextClassifier.NO_OP)
                     val markwon: Markwon
                     val markwon: Markwon
                     val markdown = withContext(IO) {
                     val markdown = withContext(IO) {
                         markwon = Markwon.builder(requireContext())
                         markwon = Markwon.builder(requireContext())
-                            .usePlugin(GlideImagesPlugin.create(requireContext()))
+                            .usePlugin(GlideImagesPlugin.create(Glide.with(requireContext())))
                             .usePlugin(HtmlPlugin.create())
                             .usePlugin(HtmlPlugin.create())
                             .usePlugin(TablePlugin.create(requireContext()))
                             .usePlugin(TablePlugin.create(requireContext()))
                             .build()
                             .build()

+ 11 - 0
app/src/main/java/io/legado/app/utils/ViewExtensions.kt

@@ -39,6 +39,7 @@ import androidx.core.view.marginBottom
 import androidx.core.view.updateLayoutParams
 import androidx.core.view.updateLayoutParams
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
 import androidx.viewpager.widget.ViewPager
 import androidx.viewpager.widget.ViewPager
+import io.legado.app.help.GlideImageGetter
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.lib.theme.TintHelper
 import io.legado.app.lib.theme.TintHelper
 import io.legado.app.utils.canvasrecorder.CanvasRecorder
 import io.legado.app.utils.canvasrecorder.CanvasRecorder
@@ -234,6 +235,16 @@ fun TextView.setHtml(html: String) {
     }
     }
 }
 }
 
 
+@SuppressLint("ObsoleteSdkInt")
+fun TextView.setHtml(html: String, imageGetter: GlideImageGetter?) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+        text = Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT, imageGetter, null)
+    } else {
+        @Suppress("DEPRECATION")
+        text = Html.fromHtml(html, imageGetter, null)
+    }
+}
+
 fun TextView.setTextIfNotEqual(charSequence: CharSequence?) {
 fun TextView.setTextIfNotEqual(charSequence: CharSequence?) {
     if (text != charSequence) {
     if (text != charSequence) {
         text = charSequence
         text = charSequence