From 4558885a65787ee25ad814818e3d48f5ace96a84 Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Thu, 23 Jun 2022 22:00:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=8E=E7=BD=91=E7=BB=9C=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E8=A1=A8=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tieba/post/BaseApplication.kt | 1 + .../post/adapters/RecyclerFloorAdapter.java | 3 + .../tieba/post/adapters/ThreadReplyAdapter.kt | 5 + .../post/components/spans/EmotionSpanV2.java | 53 +++++ .../tieba/post/models/EmotionCache.kt | 6 + .../tieba/post/utils/EmotionManager.kt | 203 ++++++++++++++++++ .../tieba/post/utils/EmotionUtil.java | 4 +- .../tieba/post/utils/ImageUtil.java | 7 +- .../tieba/post/utils/PostListAdapterHelper.kt | 5 + .../tieba/post/utils/StringUtil.java | 15 +- 10 files changed, 289 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/components/spans/EmotionSpanV2.java create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/models/EmotionCache.kt create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionManager.kt diff --git a/app/src/main/java/com/huanchengfly/tieba/post/BaseApplication.kt b/app/src/main/java/com/huanchengfly/tieba/post/BaseApplication.kt index 2b47c259..5ab865ac 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/BaseApplication.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/BaseApplication.kt @@ -101,6 +101,7 @@ class BaseApplication : Application(), IApp { ThemeUtils.init(ThemeDelegate) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) LitePal.initialize(this) + EmotionManager.init(this) registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { private var clipBoardHash: String? = null private fun updateClipBoardHashCode() { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/adapters/RecyclerFloorAdapter.java b/app/src/main/java/com/huanchengfly/tieba/post/adapters/RecyclerFloorAdapter.java index 792586bd..b3105e32 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/adapters/RecyclerFloorAdapter.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/adapters/RecyclerFloorAdapter.java @@ -37,6 +37,7 @@ import com.huanchengfly.tieba.post.plugins.PluginManager; import com.huanchengfly.tieba.post.utils.AccountUtil; import com.huanchengfly.tieba.post.utils.BilibiliUtil; import com.huanchengfly.tieba.post.utils.DateTimeUtils; +import com.huanchengfly.tieba.post.utils.EmotionManager; import com.huanchengfly.tieba.post.utils.EmotionUtil; import com.huanchengfly.tieba.post.utils.ImageUtil; import com.huanchengfly.tieba.post.utils.NavigationHelper; @@ -129,6 +130,7 @@ public class RecyclerFloorAdapter extends BaseSingleTypeAdapter { val emojiText = "#(" + contentBean.c + ")" + registerEmotion( + contentBean.text!!, + contentBean.c!! + ) builder.append(emojiText) } "4", "9" -> builder.append(contentBean.text) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/components/spans/EmotionSpanV2.java b/app/src/main/java/com/huanchengfly/tieba/post/components/spans/EmotionSpanV2.java new file mode 100644 index 00000000..d694e878 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/components/spans/EmotionSpanV2.java @@ -0,0 +1,53 @@ +package com.huanchengfly.tieba.post.components.spans; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.text.style.ImageSpan; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class EmotionSpanV2 extends ImageSpan { + public static final String TAG = EmotionSpanV2.class.getSimpleName(); + private final int size; + + public EmotionSpanV2(Drawable drawable, int size) { + super(drawable, ALIGN_BASELINE); + this.size = size; + } + + @Override + public Drawable getDrawable() { + Drawable drawable = super.getDrawable(); + drawable.setBounds(0, 0, size, size); + return drawable; + } + + @Override + public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) { + if (fm != null) { + Paint.FontMetricsInt fontFm = paint.getFontMetricsInt(); + + fm.ascent = fontFm.top; + fm.descent = fontFm.bottom; + + fm.top = fm.ascent; + fm.bottom = fm.descent; + } + + return size; + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, + float x, int top, int y, int bottom, @NonNull Paint paint) { + Paint.FontMetricsInt fm = paint.getFontMetricsInt(); + Drawable drawable = getDrawable(); + int transY = y - drawable.getBounds().bottom + fm.descent; + canvas.save(); + canvas.translate(x, transY); + drawable.draw(canvas); + canvas.restore(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/models/EmotionCache.kt b/app/src/main/java/com/huanchengfly/tieba/post/models/EmotionCache.kt new file mode 100644 index 00000000..37623007 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/models/EmotionCache.kt @@ -0,0 +1,6 @@ +package com.huanchengfly.tieba.post.models + +data class EmotionCache( + var ids: List = emptyList(), + var mapping: Map = emptyMap() +) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionManager.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionManager.kt new file mode 100644 index 00000000..1d7dab3b --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionManager.kt @@ -0,0 +1,203 @@ +package com.huanchengfly.tieba.post.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import com.bumptech.glide.Glide +import com.huanchengfly.tieba.post.BaseApplication +import com.huanchengfly.tieba.post.models.EmotionCache +import com.huanchengfly.tieba.post.toJson +import java.io.File +import java.lang.ref.WeakReference +import java.util.concurrent.Executors + +object EmotionManager { + private val DEFAULT_EMOTION_MAPPING: Map = mapOf( + "呵呵" to "image_emoticon1", + "哈哈" to "image_emoticon2", + "吐舌" to "image_emoticon3", + "啊" to "image_emoticon4", + "酷" to "image_emoticon5", + "怒" to "image_emoticon6", + "开心" to "image_emoticon7", + "汗" to "image_emoticon8", + "泪" to "image_emoticon9", + "黑线" to "image_emoticon10", + "鄙视" to "image_emoticon11", + "不高兴" to "image_emoticon12", + "真棒" to "image_emoticon13", + "钱" to "image_emoticon14", + "疑问" to "image_emoticon15", + "阴险" to "image_emoticon16", + "吐" to "image_emoticon17", + "咦" to "image_emoticon18", + "委屈" to "image_emoticon19", + "花心" to "image_emoticon20", + "呼~" to "image_emoticon21", + "笑眼" to "image_emoticon22", + "冷" to "image_emoticon23", + "太开心" to "image_emoticon24", + "滑稽" to "image_emoticon25", + "勉强" to "image_emoticon26", + "狂汗" to "image_emoticon27", + "乖" to "image_emoticon28", + "睡觉" to "image_emoticon29", + "惊哭" to "image_emoticon30", + "生气" to "image_emoticon31", + "惊讶" to "image_emoticon32", + "喷" to "image_emoticon33", + "爱心" to "image_emoticon34", + "心碎" to "image_emoticon35", + "玫瑰" to "image_emoticon36", + "礼物" to "image_emoticon37", + "彩虹" to "image_emoticon38", + "星星月亮" to "image_emoticon39", + "太阳" to "image_emoticon40", + "钱币" to "image_emoticon41", + "灯泡" to "image_emoticon42", + "茶杯" to "image_emoticon43", + "蛋糕" to "image_emoticon44", + "音乐" to "image_emoticon45", + "haha" to "image_emoticon46", + "胜利" to "image_emoticon47", + "大拇指" to "image_emoticon48", + "弱" to "image_emoticon49", + "OK" to "image_emoticon50", + "生气" to "image_emoticon61", + "沙发" to "image_emoticon77", + "手纸" to "image_emoticon78", + "香蕉" to "image_emoticon79", + "便便" to "image_emoticon80", + "药丸" to "image_emoticon81", + "红领巾" to "image_emoticon82", + "蜡烛" to "image_emoticon83", + "三道杠" to "image_emoticon84", + ) + + private lateinit var contextRef: WeakReference + private val emotionIds: MutableList = mutableListOf() + private val emotionMapping: MutableMap = mutableMapOf() + private val drawableCache: MutableMap = mutableMapOf() + + private val executor by lazy { Executors.newCachedThreadPool() } + + fun init(context: Context) { + contextRef = WeakReference(context) + val emotionCache = getEmotionDataCache() + if (emotionCache.ids.isEmpty()) { + for (i in 1..137) { + emotionIds.add("image_emoticon$i") + } + } else { + emotionIds.addAll(emotionCache.ids) + } + if (emotionCache.mapping.isEmpty()) { + emotionMapping.putAll(DEFAULT_EMOTION_MAPPING) + } else { + emotionMapping.putAll(emotionCache.mapping) + } + updateCache() + executor.submit { + fetchEmotions() + } + } + + private fun updateCache() { + val emotionDataCacheFile = File(getEmotionCacheDir(), "emotion_data_cache") + if (!emotionDataCacheFile.exists()) { + emotionDataCacheFile.createNewFile() + } + FileUtil.writeFile( + emotionDataCacheFile, + EmotionCache(emotionIds, emotionMapping).toJson(), + false + ) + } + + private fun getContext(): Context { + return contextRef.get() ?: BaseApplication.instance + } + + fun getEmotionDataCache(): EmotionCache { + val emotionDataCacheFile = File(getEmotionCacheDir(), "emotion_data_cache") + if (emotionDataCacheFile.exists()) { + return GsonUtil.getGson() + .fromJson(emotionDataCacheFile.reader(), EmotionCache::class.java) + } + return EmotionCache() + } + + fun getEmotionCacheDir(): File { + return File(getContext().externalCacheDir ?: getContext().cacheDir, "emotion") + } + + fun getEmotionFile(id: String): File { + val emotionsCacheDir = getEmotionCacheDir() + if (emotionsCacheDir.exists() && emotionsCacheDir.isFile) { + emotionsCacheDir.delete() + emotionsCacheDir.mkdirs() + } else if (!emotionsCacheDir.exists()) { + emotionsCacheDir.mkdirs() + } + return File(emotionsCacheDir, "$id.png") + } + + fun getEmotionIdByName(name: String): String? { + return emotionMapping[name] + } + + fun getEmotionDrawable(id: String?): Drawable? { + if (id == null) { + return null + } + if (drawableCache.containsKey(id)) { + return drawableCache[id] + } + val emotionFile = getEmotionFile(id) + if (!emotionFile.exists()) { + return null + } + return BitmapDrawable( + getContext().resources, + emotionFile.inputStream() + ).also { drawableCache[id] = it } + } + + private fun fetchEmotions() { + emotionIds.forEach { + val emotionFile = getEmotionFile(it) + if (!emotionFile.exists()) { + try { + val emotionBitmap = + Glide.with(getContext()) + .asBitmap() + .load("http://static.tieba.baidu.com/tb/editor/images/client/$it.png") + .submit() + .get() + ImageUtil.bitmapToFile( + emotionBitmap, + emotionFile, + Bitmap.CompressFormat.PNG + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + + fun registerEmotion(id: String, name: String) { + val realId = if (id == "image_emoticon") "image_emoticon1" else id + var changed = false + if (!emotionIds.contains(realId)) { + emotionIds.add(realId) + changed = true + } + if (!emotionMapping.containsKey(name)) { + emotionMapping[name] = realId + changed = true + } + if (changed) updateCache() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionUtil.java index 8305bb39..ddf4e089 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionUtil.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/EmotionUtil.java @@ -24,9 +24,9 @@ public class EmotionUtil { public static final int EMOTION_EMOJI_WEB_TYPE = 5; @RegExp - private static final String REGEX_WEB = "\\(#([\u4e00-\u9fa5\\w\u007e])+\\)"; + private static final String REGEX_WEB = "\\(#(([\u4e00-\u9fa5\\w\u007e])+)\\)"; @RegExp - private static final String REGEX = "#\\(([一-龥\\w~])+\\)"; + private static final String REGEX = "#\\((([一-龥\\w~])+)\\)"; private static final Map EMPTY_MAP; private static final Map EMOTION_ALL_MAP; private static final Map EMOTION_CLASSIC_MAP; diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java index b7cc4bbf..28e757f5 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java @@ -136,9 +136,12 @@ public class ImageUtil { } public static File bitmapToFile(Bitmap bitmap, File output) { + return bitmapToFile(bitmap, output, Bitmap.CompressFormat.JPEG); + } + + public static File bitmapToFile(Bitmap bitmap, File output, Bitmap.CompressFormat format) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int quality = 100; - bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos); + bitmap.compress(format, 100, baos); try { FileOutputStream fos = new FileOutputStream(output); try { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/PostListAdapterHelper.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/PostListAdapterHelper.kt index ff1bd052..4e698248 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/PostListAdapterHelper.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/PostListAdapterHelper.kt @@ -26,6 +26,7 @@ import com.huanchengfly.tieba.post.isTablet import com.huanchengfly.tieba.post.models.PhotoViewBean import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils import com.huanchengfly.tieba.post.utils.BilibiliUtil.replaceVideoNumberSpan +import com.huanchengfly.tieba.post.utils.EmotionManager.registerEmotion import com.huanchengfly.tieba.post.widgets.MyImageView import com.huanchengfly.tieba.post.widgets.VideoPlayerStandard import com.huanchengfly.tieba.post.widgets.VoicePlayerView @@ -318,6 +319,10 @@ class PostListAdapterHelper( } "2" -> { val emojiText = "#(" + contentBean.c + ")" + registerEmotion( + contentBean.text!!, + contentBean.c!! + ) if (appendTextToLastTextView(views, emojiText)) { val textView: TextView = createTextView() textView.layoutParams = diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/StringUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/StringUtil.java index b6e7754a..e463970d 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/StringUtil.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/StringUtil.java @@ -1,6 +1,7 @@ package com.huanchengfly.tieba.post.utils; import android.content.Context; +import android.graphics.drawable.Drawable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextPaint; @@ -8,7 +9,7 @@ import android.text.TextUtils; import android.widget.TextView; import com.huanchengfly.tieba.post.R; -import com.huanchengfly.tieba.post.components.spans.EmotionSpan; +import com.huanchengfly.tieba.post.components.spans.EmotionSpanV2; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -31,16 +32,12 @@ public class StringUtil { while (matcherEmotion.find()) { String key = matcherEmotion.group(); int start = matcherEmotion.start(); - int imgRes = EmotionUtil.getImgByName(emotion_map_type, key); - if (imgRes != -1) { + String group1 = matcherEmotion.group(1); + Drawable emotionDrawable = EmotionManager.INSTANCE.getEmotionDrawable(EmotionManager.INSTANCE.getEmotionIdByName(group1)); + if (emotionDrawable != null) { TextPaint paint = tv.getPaint(); int size = Math.round(-paint.ascent() + paint.descent()); - /* - Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes); - Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true); - ImageSpan span = new MyImageSpan(context, scaleBitmap); - */ - EmotionSpan span = new EmotionSpan(tv.getContext(), imgRes, size); + EmotionSpanV2 span = new EmotionSpanV2(emotionDrawable, size); spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } }