feat: 枝网查重(插件)
This commit is contained in:
parent
ec65fbdb08
commit
9a26537f12
|
|
@ -7,6 +7,14 @@
|
||||||
"author": "huanchengfly",
|
"author": "huanchengfly",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"main_class": "com.huanchengfly.tieba.post.plugins.PluginCommentLookup"
|
"main_class": "com.huanchengfly.tieba.post.plugins.PluginCommentLookup"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AsoulCnki",
|
||||||
|
"name": "枝网查重",
|
||||||
|
"desc": "调用枝网查重 API 查询重复小作文",
|
||||||
|
"author": "huanchengfly",
|
||||||
|
"version": "1.0",
|
||||||
|
"main_class": "com.huanchengfly.tieba.post.plugins.asoulcnki.PluginAsoulCnki"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -77,8 +77,8 @@ class BaseApplication : Application(), IApp {
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
LitePal.initialize(this)
|
LitePal.initialize(this)
|
||||||
FlurryAgent.Builder()
|
FlurryAgent.Builder()
|
||||||
.withCaptureUncaughtExceptions(true)
|
.withCaptureUncaughtExceptions(true)
|
||||||
.build(this, "ZMRX6W76WNF95ZHT857X")
|
.build(this, "ZMRX6W76WNF95ZHT857X")
|
||||||
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
||||||
private var clipBoardHash: Int = 0
|
private var clipBoardHash: Int = 0
|
||||||
private fun updateClipBoardHashCode() {
|
private fun updateClipBoardHashCode() {
|
||||||
|
|
@ -517,7 +517,33 @@ class BaseApplication : Application(), IApp {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCurrentContext(): Context {
|
||||||
|
return mActivityList.lastOrNull() ?: this
|
||||||
|
}
|
||||||
|
|
||||||
override fun launchUrl(url: String) {
|
override fun launchUrl(url: String) {
|
||||||
launchUrl(mActivityList.lastOrNull() ?: this, url)
|
launchUrl(getCurrentContext(), url)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showLoadingDialog(): Dialog {
|
||||||
|
return LoadingDialog(getCurrentContext()).apply { show() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toastShort(text: String) {
|
||||||
|
getCurrentContext().toastShort(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showAlertDialog(builder: AlertDialog.Builder.() -> Unit): AlertDialog {
|
||||||
|
val dialog = AlertDialog.Builder(getCurrentContext())
|
||||||
|
.apply(builder)
|
||||||
|
.create()
|
||||||
|
if (getCurrentContext() !is BaseActivity || (getCurrentContext() as BaseActivity).isActivityRunning) {
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copyText(text: String) {
|
||||||
|
TiebaUtil.copyText(getCurrentContext(), text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,16 +5,19 @@ import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Build
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.huanchengfly.tieba.post.utils.GsonUtil
|
import com.huanchengfly.tieba.post.utils.GsonUtil
|
||||||
import com.huanchengfly.tieba.post.utils.MD5Util
|
import com.huanchengfly.tieba.post.utils.MD5Util
|
||||||
|
|
||||||
|
|
||||||
fun Float.dpToPx(): Int =
|
fun Float.dpToPx(): Int =
|
||||||
dpToPxFloat().toInt()
|
dpToPxFloat().toInt()
|
||||||
|
|
||||||
|
|
@ -38,26 +41,21 @@ fun Int.pxToDp(): Int = this.toFloat().pxToDp()
|
||||||
|
|
||||||
fun Int.pxToSp(): Int = this.toFloat().pxToSp()
|
fun Int.pxToSp(): Int = this.toFloat().pxToSp()
|
||||||
|
|
||||||
inline fun <reified Data> String.fromJson() = GsonUtil.getGson().fromJson<Data>(this, Data::class.java)
|
inline fun <reified Data> String.fromJson(): Data {
|
||||||
|
val type = object : TypeToken<Data>() {}.type
|
||||||
|
return GsonUtil.getGson().fromJson(this, type)
|
||||||
|
}
|
||||||
|
|
||||||
fun Any.toJson(): String = Gson().toJson(this)
|
fun Any.toJson(): String = Gson().toJson(this)
|
||||||
|
|
||||||
fun String.toMD5(): String = MD5Util.toMd5(this)
|
fun String.toMD5(): String = MD5Util.toMd5(this)
|
||||||
|
|
||||||
fun Context.getColorCompat(@ColorRes id: Int): Int {
|
fun Context.getColorCompat(@ColorRes id: Int): Int {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
return ContextCompat.getColor(this, id)
|
||||||
resources.getColor(id, theme)
|
|
||||||
} else {
|
|
||||||
resources.getColor(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getColorStateListCompat(id: Int): ColorStateList {
|
fun Context.getColorStateListCompat(id: Int): ColorStateList {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
return AppCompatResources.getColorStateList(this, id)
|
||||||
resources.getColorStateList(id, theme)
|
|
||||||
} else {
|
|
||||||
resources.getColorStateList(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Activity> Context.goToActivity() {
|
inline fun <reified T : Activity> Context.goToActivity() {
|
||||||
|
|
|
||||||
|
|
@ -508,7 +508,6 @@ class ThreadActivity : BaseActivity(), View.OnClickListener, IThreadMenuFragment
|
||||||
totalPage = Integer.valueOf(threadContentBean.page.totalPage!!)
|
totalPage = Integer.valueOf(threadContentBean.page.totalPage!!)
|
||||||
refresh(pid)
|
refresh(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import com.huanchengfly.tieba.post.fragments.ConfirmDialogFragment;
|
||||||
import com.huanchengfly.tieba.post.fragments.MenuDialogFragment;
|
import com.huanchengfly.tieba.post.fragments.MenuDialogFragment;
|
||||||
import com.huanchengfly.tieba.post.models.PhotoViewBean;
|
import com.huanchengfly.tieba.post.models.PhotoViewBean;
|
||||||
import com.huanchengfly.tieba.post.models.ReplyInfoBean;
|
import com.huanchengfly.tieba.post.models.ReplyInfoBean;
|
||||||
|
import com.huanchengfly.tieba.post.plugins.PluginManager;
|
||||||
import com.huanchengfly.tieba.post.utils.AccountUtil;
|
import com.huanchengfly.tieba.post.utils.AccountUtil;
|
||||||
import com.huanchengfly.tieba.post.utils.BilibiliUtil;
|
import com.huanchengfly.tieba.post.utils.BilibiliUtil;
|
||||||
import com.huanchengfly.tieba.post.utils.DateTimeUtils;
|
import com.huanchengfly.tieba.post.utils.DateTimeUtils;
|
||||||
|
|
@ -178,9 +179,10 @@ public class RecyclerFloorAdapter extends CommonBaseAdapter<SubFloorListBean.Pos
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return PluginManager.INSTANCE.performPluginMenuClick(PluginManager.MENU_SUB_POST_ITEM, item.getItemId(), postInfo);
|
||||||
})
|
})
|
||||||
.setInitMenuCallback(menu -> {
|
.setInitMenuCallback(menu -> {
|
||||||
|
PluginManager.INSTANCE.initPluginMenu(menu, PluginManager.MENU_SUB_POST_ITEM);
|
||||||
if (TextUtils.equals(AccountUtil.getLoginInfo(mContext).getUid(), postInfo.getAuthor().getId())) {
|
if (TextUtils.equals(AccountUtil.getLoginInfo(mContext).getUid(), postInfo.getAuthor().getId())) {
|
||||||
menu.findItem(R.id.menu_delete).setVisible(true);
|
menu.findItem(R.id.menu_delete).setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import com.huanchengfly.tieba.post.api.models.ThreadContentBean
|
||||||
import com.huanchengfly.tieba.post.components.MyViewHolder
|
import com.huanchengfly.tieba.post.components.MyViewHolder
|
||||||
import com.huanchengfly.tieba.post.fragments.MenuDialogFragment
|
import com.huanchengfly.tieba.post.fragments.MenuDialogFragment
|
||||||
import com.huanchengfly.tieba.post.models.ReplyInfoBean
|
import com.huanchengfly.tieba.post.models.ReplyInfoBean
|
||||||
|
import com.huanchengfly.tieba.post.plugins.PluginManager
|
||||||
import com.huanchengfly.tieba.post.utils.*
|
import com.huanchengfly.tieba.post.utils.*
|
||||||
import com.huanchengfly.tieba.post.utils.NavigationHelper
|
import com.huanchengfly.tieba.post.utils.NavigationHelper
|
||||||
import com.huanchengfly.tieba.post.utils.TiebaUtil.reportPost
|
import com.huanchengfly.tieba.post.utils.TiebaUtil.reportPost
|
||||||
|
|
@ -120,13 +121,22 @@ class ThreadMainPostAdapter(
|
||||||
stringBuilder.append(contentBean.text)
|
stringBuilder.append(contentBean.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Util.showCopyDialog(context as BaseActivity?, stringBuilder.toString(), threadPostBean!!.id)
|
Util.showCopyDialog(
|
||||||
|
context as BaseActivity?,
|
||||||
|
stringBuilder.toString(),
|
||||||
|
threadPostBean!!.id
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
else -> PluginManager.performPluginMenuClick(
|
||||||
|
PluginManager.MENU_POST_ITEM,
|
||||||
|
item.itemId,
|
||||||
|
threadPostBean!!
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setInitMenuCallback { menu: Menu ->
|
.setInitMenuCallback { menu: Menu ->
|
||||||
|
PluginManager.initPluginMenu(menu, PluginManager.MENU_POST_ITEM)
|
||||||
menu.findItem(R.id.menu_delete).isVisible = false
|
menu.findItem(R.id.menu_delete).isVisible = false
|
||||||
}
|
}
|
||||||
.show((context as BaseActivity).supportFragmentManager, threadPostBean!!.id + "_Menu")
|
.show((context as BaseActivity).supportFragmentManager, threadPostBean!!.id + "_Menu")
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import com.huanchengfly.tieba.post.fragments.ConfirmDialogFragment
|
||||||
import com.huanchengfly.tieba.post.fragments.FloorFragment.Companion.newInstance
|
import com.huanchengfly.tieba.post.fragments.FloorFragment.Companion.newInstance
|
||||||
import com.huanchengfly.tieba.post.fragments.MenuDialogFragment
|
import com.huanchengfly.tieba.post.fragments.MenuDialogFragment
|
||||||
import com.huanchengfly.tieba.post.models.ReplyInfoBean
|
import com.huanchengfly.tieba.post.models.ReplyInfoBean
|
||||||
|
import com.huanchengfly.tieba.post.plugins.PluginManager
|
||||||
import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils
|
import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils
|
||||||
import com.huanchengfly.tieba.post.utils.*
|
import com.huanchengfly.tieba.post.utils.*
|
||||||
import com.huanchengfly.tieba.post.utils.BilibiliUtil.replaceVideoNumberSpan
|
import com.huanchengfly.tieba.post.utils.BilibiliUtil.replaceVideoNumberSpan
|
||||||
|
|
@ -314,9 +315,14 @@ class ThreadReplyAdapter(context: Context) : BaseSingleTypeDelegateAdapter<PostL
|
||||||
return@setOnNavigationItemSelectedListener true
|
return@setOnNavigationItemSelectedListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
PluginManager.performPluginMenuClick(
|
||||||
|
PluginManager.MENU_SUB_POST_ITEM,
|
||||||
|
item.itemId,
|
||||||
|
subPostListItemBean
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.setInitMenuCallback { menu: Menu ->
|
.setInitMenuCallback { menu: Menu ->
|
||||||
|
PluginManager.initPluginMenu(menu, PluginManager.MENU_SUB_POST_ITEM)
|
||||||
menu.findItem(R.id.menu_report).isVisible = false
|
menu.findItem(R.id.menu_report).isVisible = false
|
||||||
if (TextUtils.equals(AccountUtil.getUid(context), subPostListItemBean.authorId)) {
|
if (TextUtils.equals(AccountUtil.getUid(context), subPostListItemBean.authorId)) {
|
||||||
menu.findItem(R.id.menu_delete).isVisible = true
|
menu.findItem(R.id.menu_delete).isVisible = true
|
||||||
|
|
@ -384,9 +390,14 @@ class ThreadReplyAdapter(context: Context) : BaseSingleTypeDelegateAdapter<PostL
|
||||||
return@setOnNavigationItemSelectedListener true
|
return@setOnNavigationItemSelectedListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
PluginManager.performPluginMenuClick(
|
||||||
|
PluginManager.MENU_POST_ITEM,
|
||||||
|
item.itemId,
|
||||||
|
postListItemBean
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.setInitMenuCallback { menu: Menu ->
|
.setInitMenuCallback { menu: Menu ->
|
||||||
|
PluginManager.initPluginMenu(menu, PluginManager.MENU_POST_ITEM)
|
||||||
if (TextUtils.equals(dataBean!!.user!!.id, postListItemBean.authorId) || TextUtils.equals(dataBean!!.user!!.id, dataBean!!.thread!!.author!!.id)) {
|
if (TextUtils.equals(dataBean!!.user!!.id, postListItemBean.authorId) || TextUtils.equals(dataBean!!.user!!.id, dataBean!!.thread!!.author!!.id)) {
|
||||||
menu.findItem(R.id.menu_delete).isVisible = true
|
menu.findItem(R.id.menu_delete).isVisible = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,22 @@ package com.huanchengfly.tieba.post.components.spans;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.huanchengfly.tieba.post.R;
|
import com.huanchengfly.tieba.post.R;
|
||||||
import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils;
|
import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils;
|
||||||
import com.huanchengfly.tieba.post.utils.NavigationHelper;
|
|
||||||
import com.huanchengfly.tieba.post.utils.UtilsKt;
|
import com.huanchengfly.tieba.post.utils.UtilsKt;
|
||||||
|
|
||||||
public class MyURLSpan extends ClickableSpan {
|
public class MyURLSpan extends ClickableSpan {
|
||||||
public String url;
|
public String url;
|
||||||
private Context context;
|
private Context context;
|
||||||
private NavigationHelper navigationHelper;
|
|
||||||
|
|
||||||
public MyURLSpan(Context context, String url) {
|
public MyURLSpan(Context context, String url) {
|
||||||
super();
|
super();
|
||||||
Log.i("MyURLSpan", "MyURLSpan: " + url);
|
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.navigationHelper = NavigationHelper.newInstance(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,29 @@ import android.content.Context
|
||||||
import com.huanchengfly.tieba.post.plugins.PluginMenuItem.ClickCallback
|
import com.huanchengfly.tieba.post.plugins.PluginMenuItem.ClickCallback
|
||||||
import com.huanchengfly.tieba.post.plugins.interfaces.IApp
|
import com.huanchengfly.tieba.post.plugins.interfaces.IApp
|
||||||
import com.huanchengfly.tieba.post.plugins.models.PluginManifest
|
import com.huanchengfly.tieba.post.plugins.models.PluginManifest
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
abstract class IPlugin(
|
abstract class IPlugin(
|
||||||
val app: IApp,
|
val app: IApp,
|
||||||
val manifest: PluginManifest
|
val manifest: PluginManifest
|
||||||
) {
|
) : CoroutineScope {
|
||||||
|
val job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.Main + job
|
||||||
|
|
||||||
val context: Context
|
val context: Context
|
||||||
get() = app.getAppContext()
|
get() = app.getCurrentContext()
|
||||||
|
|
||||||
open fun onCreate() {}
|
open fun onCreate() {}
|
||||||
|
|
||||||
open fun onEnable() {}
|
open fun onEnable() {}
|
||||||
|
|
||||||
open fun onDisable() {}
|
open fun onDisable() {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
open fun onDestroy() {}
|
open fun onDestroy() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import com.huanchengfly.tieba.post.api.models.ProfileBean
|
import com.huanchengfly.tieba.post.api.models.ProfileBean
|
||||||
|
import com.huanchengfly.tieba.post.api.models.SubFloorListBean
|
||||||
|
import com.huanchengfly.tieba.post.api.models.ThreadContentBean
|
||||||
import com.huanchengfly.tieba.post.fromJson
|
import com.huanchengfly.tieba.post.fromJson
|
||||||
import com.huanchengfly.tieba.post.plugins.interfaces.IApp
|
import com.huanchengfly.tieba.post.plugins.interfaces.IApp
|
||||||
import com.huanchengfly.tieba.post.plugins.models.BuiltInPlugins
|
import com.huanchengfly.tieba.post.plugins.models.BuiltInPlugins
|
||||||
|
|
@ -15,6 +17,8 @@ import kotlin.reflect.KClass
|
||||||
|
|
||||||
object PluginManager {
|
object PluginManager {
|
||||||
const val MENU_USER_ACTIVITY = "user_activity"
|
const val MENU_USER_ACTIVITY = "user_activity"
|
||||||
|
const val MENU_POST_ITEM = "post_item"
|
||||||
|
const val MENU_SUB_POST_ITEM = "sub_post_item"
|
||||||
const val MENU_NONE = "none"
|
const val MENU_NONE = "none"
|
||||||
|
|
||||||
lateinit var appInstance: IApp
|
lateinit var appInstance: IApp
|
||||||
|
|
@ -37,6 +41,8 @@ object PluginManager {
|
||||||
registeredPluginMenuItems.clear()
|
registeredPluginMenuItems.clear()
|
||||||
listOf(
|
listOf(
|
||||||
MENU_USER_ACTIVITY,
|
MENU_USER_ACTIVITY,
|
||||||
|
MENU_POST_ITEM,
|
||||||
|
MENU_SUB_POST_ITEM,
|
||||||
MENU_NONE
|
MENU_NONE
|
||||||
).forEach {
|
).forEach {
|
||||||
registeredPluginMenuItems[it] = mutableMapOf()
|
registeredPluginMenuItems[it] = mutableMapOf()
|
||||||
|
|
@ -56,7 +62,7 @@ object PluginManager {
|
||||||
fun initPluginMenu(menu: Menu, menuId: String) {
|
fun initPluginMenu(menu: Menu, menuId: String) {
|
||||||
val menuItems = registeredPluginMenuItems[menuId]!!
|
val menuItems = registeredPluginMenuItems[menuId]!!
|
||||||
menuItems.forEach {
|
menuItems.forEach {
|
||||||
menu.add(0, it.key, 0, it.value.title)
|
menu.add(0, it.key, 100, it.value.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,6 +202,8 @@ fun getMenuByData(dataClass: KClass<*>): String = getMenuByData(dataClass.java)
|
||||||
fun getMenuByData(dataClass: Class<*>): String {
|
fun getMenuByData(dataClass: Class<*>): String {
|
||||||
return when (dataClass.canonicalName) {
|
return when (dataClass.canonicalName) {
|
||||||
ProfileBean::class.java.canonicalName -> PluginManager.MENU_USER_ACTIVITY
|
ProfileBean::class.java.canonicalName -> PluginManager.MENU_USER_ACTIVITY
|
||||||
|
ThreadContentBean.PostListItemBean::class.java.canonicalName -> PluginManager.MENU_POST_ITEM
|
||||||
|
SubFloorListBean.PostInfo::class.java.canonicalName -> PluginManager.MENU_SUB_POST_ITEM
|
||||||
else -> "none"
|
else -> "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.huanchengfly.tieba.post.plugins.asoulcnki;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.huanchengfly.tieba.post.R;
|
||||||
|
import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils;
|
||||||
|
import com.huanchengfly.tieba.post.utils.UtilsKt;
|
||||||
|
|
||||||
|
public class MyURLSpan extends ClickableSpan {
|
||||||
|
public String url;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public MyURLSpan(Context context, String url) {
|
||||||
|
super();
|
||||||
|
this.url = url;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(@NonNull TextPaint ds) {
|
||||||
|
super.updateDrawState(ds);
|
||||||
|
ds.setColor(ThemeUtils.getColorByAttr(this.context, R.attr.colorAccent));
|
||||||
|
ds.setUnderlineText(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(@NonNull View view) {
|
||||||
|
UtilsKt.launchUrl(context, url);
|
||||||
|
//context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
package com.huanchengfly.tieba.post.plugins.asoulcnki
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.huanchengfly.tieba.post.R
|
||||||
|
import com.huanchengfly.tieba.post.api.models.ThreadContentBean
|
||||||
|
import com.huanchengfly.tieba.post.components.LinkTouchMovementMethod
|
||||||
|
import com.huanchengfly.tieba.post.components.spans.MyImageSpan
|
||||||
|
import com.huanchengfly.tieba.post.plugins.IPlugin
|
||||||
|
import com.huanchengfly.tieba.post.plugins.asoulcnki.api.CheckApi
|
||||||
|
import com.huanchengfly.tieba.post.plugins.asoulcnki.models.CheckApiBody
|
||||||
|
import com.huanchengfly.tieba.post.plugins.interfaces.IApp
|
||||||
|
import com.huanchengfly.tieba.post.plugins.models.PluginManifest
|
||||||
|
import com.huanchengfly.tieba.post.plugins.registerMenuItem
|
||||||
|
import com.huanchengfly.tieba.post.toJson
|
||||||
|
import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils
|
||||||
|
import com.huanchengfly.tieba.post.utils.DisplayUtil
|
||||||
|
import com.huanchengfly.tieba.post.utils.Util
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class PluginAsoulCnki(app: IApp, manifest: PluginManifest) : IPlugin(app, manifest) {
|
||||||
|
override fun onEnable() {
|
||||||
|
super.onEnable()
|
||||||
|
registerMenuItem<ThreadContentBean.PostListItemBean>(
|
||||||
|
"asoul_cnki_check",
|
||||||
|
context.getString(R.string.plugin_asoul_cnki_check)
|
||||||
|
) { data ->
|
||||||
|
val dialog = app.showLoadingDialog()
|
||||||
|
val body = CheckApiBody(getPostTextContent(data)).toJson()
|
||||||
|
launch(Dispatchers.IO + job) {
|
||||||
|
val result =
|
||||||
|
CheckApi.instance.checkAsync(body.toRequestBody("application/json, charset=utf-8".toMediaTypeOrNull()))
|
||||||
|
.await()
|
||||||
|
launch(Dispatchers.Main + job) {
|
||||||
|
dialog.cancel()
|
||||||
|
if (result.code == 0) {
|
||||||
|
val numberFormatter = NumberFormat.getNumberInstance().apply {
|
||||||
|
maximumFractionDigits = 2
|
||||||
|
minimumFractionDigits = 2
|
||||||
|
}
|
||||||
|
app.showAlertDialog {
|
||||||
|
setTitle("查重结果")
|
||||||
|
val percent = "${numberFormatter.format(result.data.rate * 100.0)}%"
|
||||||
|
val resultForCopy = context.getString(
|
||||||
|
R.string.plugin_asoul_cnki_result,
|
||||||
|
formatDateTime("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
percent,
|
||||||
|
if (result.data.related.isNotEmpty()) {
|
||||||
|
context.getString(
|
||||||
|
R.string.plugin_asoul_cnki_related,
|
||||||
|
result.data.related[0].replyUrl,
|
||||||
|
result.data.related[0].reply.mName,
|
||||||
|
formatDateTime(
|
||||||
|
"yyyy-MM-dd HH:mm",
|
||||||
|
result.data.related[0].reply.ctime * 1000L
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val view = View.inflate(
|
||||||
|
context,
|
||||||
|
R.layout.plugin_asoul_cnki_dialog_check_result,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
val percentView = view.findViewById<TextView>(R.id.check_result_percent)
|
||||||
|
val progress =
|
||||||
|
view.findViewById<ProgressBar>(R.id.check_result_progress)
|
||||||
|
val relatedView = view.findViewById<View>(R.id.check_result_related)
|
||||||
|
val relatedTitle =
|
||||||
|
view.findViewById<TextView>(R.id.check_result_related_title)
|
||||||
|
val relatedContent =
|
||||||
|
view.findViewById<TextView>(R.id.check_result_related_content)
|
||||||
|
percentView.text = context.getString(
|
||||||
|
R.string.plugin_asoul_cnki_check_result_percent,
|
||||||
|
percent
|
||||||
|
)
|
||||||
|
progress.progress = (result.data.rate * 10000).toInt()
|
||||||
|
if (result.data.related.isNullOrEmpty()) {
|
||||||
|
relatedView.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
relatedView.visibility = View.VISIBLE
|
||||||
|
relatedTitle.text = context.getString(
|
||||||
|
R.string.plugin_asoul_cnki_check_result_related,
|
||||||
|
result.data.related.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val relatedContentText = SpannableStringBuilder()
|
||||||
|
result.data.related.forEach {
|
||||||
|
relatedContentText.appendLink("${it.reply.mName} 的评论", it.replyUrl)
|
||||||
|
.append("\n")
|
||||||
|
}
|
||||||
|
relatedContent.apply {
|
||||||
|
text = relatedContentText
|
||||||
|
movementMethod = LinkTouchMovementMethod.getInstance()
|
||||||
|
}
|
||||||
|
setView(view)
|
||||||
|
setPositiveButton(R.string.btn_copy_check_result) { _, _ ->
|
||||||
|
app.copyText(resultForCopy)
|
||||||
|
}
|
||||||
|
setNegativeButton(R.string.btn_close, null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.toastShort("查重失败 ${result.code}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SpannableStringBuilder.appendLink(
|
||||||
|
text: CharSequence,
|
||||||
|
url: String
|
||||||
|
): SpannableStringBuilder {
|
||||||
|
val spannableStringBuilder = SpannableStringBuilder()
|
||||||
|
val size = DisplayUtil.sp2px(context, 14f)
|
||||||
|
val bitmap = Util.tintBitmap(
|
||||||
|
Bitmap.createScaledBitmap(
|
||||||
|
Util.getBitmapFromVectorDrawable(
|
||||||
|
context,
|
||||||
|
R.drawable.ic_link
|
||||||
|
),
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
ThemeUtils.getColorByAttr(context, R.attr.colorAccent)
|
||||||
|
)
|
||||||
|
spannableStringBuilder.append(
|
||||||
|
"[链接]",
|
||||||
|
MyImageSpan(context, bitmap),
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
spannableStringBuilder.append(" ")
|
||||||
|
spannableStringBuilder.append(
|
||||||
|
text,
|
||||||
|
MyURLSpan(context, url),
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
return append(spannableStringBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPostTextContent(item: ThreadContentBean.PostListItemBean): String {
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
for (contentBean in item.content!!) {
|
||||||
|
when (contentBean.type) {
|
||||||
|
"2" -> contentBean.setText("#(" + contentBean.c + ")")
|
||||||
|
"3", "20" -> contentBean.setText("[图片]\n")
|
||||||
|
"10" -> contentBean.setText("[语音]\n")
|
||||||
|
}
|
||||||
|
if (contentBean.text != null) {
|
||||||
|
stringBuilder.append(contentBean.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stringBuilder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDateTime(
|
||||||
|
pattern: String,
|
||||||
|
timestamp: Long = System.currentTimeMillis()
|
||||||
|
): String {
|
||||||
|
return SimpleDateFormat(pattern, Locale.getDefault()).format(Date(timestamp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.huanchengfly.tieba.post.plugins.asoulcnki.api
|
||||||
|
|
||||||
|
import com.huanchengfly.tieba.post.api.retrofit.NullOnEmptyConverterFactory
|
||||||
|
import com.huanchengfly.tieba.post.api.retrofit.adapter.DeferredCallAdapterFactory
|
||||||
|
import com.huanchengfly.tieba.post.api.retrofit.converter.gson.GsonConverterFactory
|
||||||
|
import okhttp3.ConnectionPool
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
object CheckApi {
|
||||||
|
private val connectionPool = ConnectionPool()
|
||||||
|
|
||||||
|
val instance: ICheckApi by lazy {
|
||||||
|
Retrofit.Builder()
|
||||||
|
.baseUrl("https://asoulcnki.asia/")
|
||||||
|
.addCallAdapterFactory(DeferredCallAdapterFactory())
|
||||||
|
.addConverterFactory(NullOnEmptyConverterFactory())
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.client(OkHttpClient.Builder().apply {
|
||||||
|
connectionPool(connectionPool)
|
||||||
|
}.build())
|
||||||
|
.build()
|
||||||
|
.create(ICheckApi::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.huanchengfly.tieba.post.plugins.asoulcnki.api
|
||||||
|
|
||||||
|
import com.huanchengfly.tieba.post.api.ParamExpression
|
||||||
|
import com.huanchengfly.tieba.post.api.forEachNonNull
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class CommonHeaderInterceptor(private vararg val additionHeaders: ParamExpression) : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val headers = request.headers
|
||||||
|
|
||||||
|
return chain.proceed(request.newBuilder().apply {
|
||||||
|
additionHeaders.forEachNonNull { name, value ->
|
||||||
|
if (headers[name] == null) addHeader(name, value)
|
||||||
|
}
|
||||||
|
}.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.huanchengfly.tieba.post.plugins.asoulcnki.api
|
||||||
|
|
||||||
|
import com.huanchengfly.tieba.post.plugins.asoulcnki.models.CheckResult
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.Headers
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
interface ICheckApi {
|
||||||
|
@POST("/v1/api/check")
|
||||||
|
@Headers("content-type: application/json;charset=UTF-8")
|
||||||
|
fun checkAsync(
|
||||||
|
@Body requestBody: RequestBody
|
||||||
|
): Deferred<CheckResult>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.huanchengfly.tieba.post.plugins.asoulcnki.models
|
||||||
|
|
||||||
|
data class CheckApiBody(
|
||||||
|
val text: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.huanchengfly.tieba.post.plugins.asoulcnki.models
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CheckResult(
|
||||||
|
val code: Int, // 0
|
||||||
|
val `data`: Data,
|
||||||
|
val message: String // success
|
||||||
|
) {
|
||||||
|
@Keep
|
||||||
|
data class Data(
|
||||||
|
@SerializedName("end_time")
|
||||||
|
val endTime: Int, // 1629010807
|
||||||
|
val rate: Double, // 1.0
|
||||||
|
val related: List<Related>,
|
||||||
|
@SerializedName("start_time")
|
||||||
|
val startTime: Int // 1606137506
|
||||||
|
) {
|
||||||
|
@Keep
|
||||||
|
data class Related(
|
||||||
|
val rate: Double, // 1.0
|
||||||
|
val reply: Reply,
|
||||||
|
@SerializedName("reply_url")
|
||||||
|
val replyUrl: String // https://www.bilibili.com/video/av377092608/#reply5051494613
|
||||||
|
) {
|
||||||
|
@Keep
|
||||||
|
data class Reply(
|
||||||
|
val content: String, // 曾几何时,我也想像asoul的beeeeeeeela一样做幸福滤镜下的事至少在这层滤镜下,beeeeeeeela的一举一动都是随心所欲且浪漫真实的当我看到beeeeeeeela能像个二次元一样和弹幕大谈特谈50音,当我看到beeeeeeeela能够笑着在夜里唱着不知道练了多少遍的云烟成雨,当我看到她可以在失落后得到安抚和拥抱…以往的笑意消散殆尽,剩下的只有我对beeeeeeeela浪漫的感动和一种无中生有的失意了。我也想像她一样。但这是虚假的,每次在烂醉酩酊起来后依然会痛苦,每次在浪费时间的时候都能意识到,你不能感受到我感受到的东西。但就算是这样,没了你我可能就会完蛋了吧。因为我们需要一个梦。
|
||||||
|
val ctime: Int, // 1627881576
|
||||||
|
@SerializedName("dynamic_id")
|
||||||
|
val dynamicId: String, // 553473662133564230
|
||||||
|
@SerializedName("like_num")
|
||||||
|
val likeNum: Int, // 9
|
||||||
|
@SerializedName("m_name")
|
||||||
|
val mName: String, // 走出童年
|
||||||
|
val mid: Int, // 671239951
|
||||||
|
val oid: String, // 377092608
|
||||||
|
@SerializedName("origin_rpid")
|
||||||
|
val originRpid: String, // -1
|
||||||
|
val rpid: String, // 5051494613
|
||||||
|
@SerializedName("similar_count")
|
||||||
|
val similarCount: Int, // 1
|
||||||
|
@SerializedName("similar_like_sum")
|
||||||
|
val similarLikeSum: Int, // 424
|
||||||
|
@SerializedName("type_id")
|
||||||
|
val typeId: Int, // 1
|
||||||
|
val uid: Int // 672346917
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,21 @@
|
||||||
package com.huanchengfly.tieba.post.plugins.interfaces
|
package com.huanchengfly.tieba.post.plugins.interfaces
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
|
||||||
interface IApp {
|
interface IApp {
|
||||||
fun getAppContext(): Context
|
fun getAppContext(): Context
|
||||||
|
|
||||||
|
fun getCurrentContext(): Context
|
||||||
|
|
||||||
fun launchUrl(url: String)
|
fun launchUrl(url: String)
|
||||||
|
|
||||||
|
fun showLoadingDialog(): Dialog
|
||||||
|
|
||||||
|
fun toastShort(text: String)
|
||||||
|
|
||||||
|
fun copyText(text: String)
|
||||||
|
|
||||||
|
fun showAlertDialog(builder: AlertDialog.Builder.() -> Unit): AlertDialog
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue