From c306846e7ef6d1189461599910387ba052623fbd Mon Sep 17 00:00:00 2001 From: HuanChengFly <609486518@qq.com> Date: Wed, 11 Aug 2021 17:10:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E7=B3=BB=E7=BB=9F(BE?= =?UTF-8?q?TA)=20&=20=E6=9F=A5=E8=AF=A2=E5=8F=91=E8=A8=80=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/proguard-rules.pro | 4 +- app/src/main/assets/plugins.json | 9 + .../tieba/post/BaseApplication.kt | 33 +++- .../post/components/spans/MyURLSpan.java | 3 +- .../tieba/post/plugins/IPlugin.kt | 43 +++++ .../tieba/post/plugins/PluginCommentLookup.kt | 18 ++ .../tieba/post/plugins/PluginManager.kt | 168 ++++++++++++++++++ .../tieba/post/plugins/interfaces/IApp.kt | 9 + .../post/plugins/models/PluginManifest.kt | 12 ++ .../post/utils/SharedPreferencesUtil.java | 3 +- .../huanchengfly/tieba/post/utils/utils.kt | 66 ++++++- app/src/main/res/values/strings.xml | 1 + 12 files changed, 352 insertions(+), 17 deletions(-) create mode 100644 app/src/main/assets/plugins.json create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/plugins/IPlugin.kt create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginCommentLookup.kt create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginManager.kt create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/plugins/interfaces/IApp.kt create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/plugins/models/PluginManifest.kt diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e25f7d56..92278747 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -251,4 +251,6 @@ -keep class com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx { *; } -keep class androidx.recyclerview.widget.RecyclerView$LayoutParams { *; } -keep class androidx.recyclerview.widget.RecyclerView$ViewHolder { *; } --keep class androidx.recyclerview.widget.RecyclerView$LayoutManager { *; } \ No newline at end of file +-keep class androidx.recyclerview.widget.RecyclerView$LayoutManager { *; } + +-keep class com.huanchengfly.tieba.post.plugins.** { *; } \ No newline at end of file diff --git a/app/src/main/assets/plugins.json b/app/src/main/assets/plugins.json new file mode 100644 index 00000000..cc6fc7f1 --- /dev/null +++ b/app/src/main/assets/plugins.json @@ -0,0 +1,9 @@ +[ + { + "id": "CommentLookup", + "name": "发言查询", + "desc": "使用第三方工具箱查询用户过往发言", + "version": "1.0", + "mainClass": "com.huanchengfly.tieba.post.plugins.PluginCommentLookup" + } +] \ No newline at end of file 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 d252c5e3..918bb2cd 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/BaseApplication.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/BaseApplication.kt @@ -19,6 +19,8 @@ import android.widget.TextView import androidx.appcompat.app.AppCompatDelegate import com.flurry.android.FlurryAgent import com.huanchengfly.tieba.post.api.interfaces.CommonCallback +import com.huanchengfly.tieba.post.plugins.PluginManager +import com.huanchengfly.tieba.post.plugins.interfaces.IApp import com.huanchengfly.tieba.post.ui.theme.interfaces.ThemeSwitcher import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils import com.huanchengfly.tieba.post.utils.* @@ -32,13 +34,14 @@ import org.litepal.LitePal import java.util.* import java.util.regex.Pattern -class BaseApplication : Application() { +class BaseApplication : Application(), IApp { private val mActivityList: MutableList = mutableListOf() override fun onCreate() { instance = this super.onCreate() ThemeUtils.init(ThemeDelegate) + PluginManager.init(this) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) LitePal.initialize(this) FlurryAgent.Builder() @@ -441,13 +444,33 @@ class BaseApplication : Application() { R.color.default_color_shadow -> return getColorByAttr(context, R.attr.shadow_color) R.color.default_color_unselected -> return getColorByAttr(context, R.attr.colorUnselected) R.color.default_color_text -> return getColorByAttr(context, R.attr.colorText) - R.color.default_color_text_on_primary -> return getColorByAttr(context, R.attr.colorTextOnPrimary) - R.color.default_color_text_secondary -> return getColorByAttr(context, R.attr.colorTextSecondary) - R.color.default_color_text_disabled -> return getColorByAttr(context, R.attr.color_text_disabled) + R.color.default_color_text_on_primary -> return getColorByAttr( + context, + R.attr.colorTextOnPrimary + ) + R.color.default_color_text_secondary -> return getColorByAttr( + context, + R.attr.colorTextSecondary + ) + R.color.default_color_text_disabled -> return getColorByAttr( + context, + R.attr.color_text_disabled + ) R.color.default_color_divider -> return getColorByAttr(context, R.attr.colorDivider) - R.color.default_color_swipe_refresh_view_background -> return getColorByAttr(context, R.attr.color_swipe_refresh_layout_background) + R.color.default_color_swipe_refresh_view_background -> return getColorByAttr( + context, + R.attr.color_swipe_refresh_layout_background + ) } return context.getColorCompat(colorId) } } + + override fun getAppContext(): Context { + return this + } + + override fun launchUrl(url: String) { + launchUrl(this, url) + } } \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/components/spans/MyURLSpan.java b/app/src/main/java/com/huanchengfly/tieba/post/components/spans/MyURLSpan.java index c9d09c6d..ac62fc44 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/components/spans/MyURLSpan.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/components/spans/MyURLSpan.java @@ -11,6 +11,7 @@ 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.NavigationHelper; +import com.huanchengfly.tieba.post.utils.UtilsKt; public class MyURLSpan extends ClickableSpan { public String url; @@ -34,6 +35,6 @@ public class MyURLSpan extends ClickableSpan { @Override public void onClick(@NonNull View view) { - navigationHelper.navigationByData(NavigationHelper.ACTION_URL, this.url); + UtilsKt.launchUrl(context, url); } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/plugins/IPlugin.kt b/app/src/main/java/com/huanchengfly/tieba/post/plugins/IPlugin.kt new file mode 100644 index 00000000..5fd02184 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/plugins/IPlugin.kt @@ -0,0 +1,43 @@ +package com.huanchengfly.tieba.post.plugins + +import android.content.Context +import com.huanchengfly.tieba.post.plugins.PluginMenuItem.ClickCallback +import com.huanchengfly.tieba.post.plugins.interfaces.IApp +import com.huanchengfly.tieba.post.plugins.models.PluginManifest + +abstract class IPlugin( + val app: IApp, + val manifest: PluginManifest +) { + val context: Context + get() = app.getAppContext() + + open fun onCreate() {} + + open fun onEnable() {} + + open fun onDisable() {} + + open fun onDestroy() {} +} + +inline fun IPlugin.registerMenuItem( + id: String, + title: String, + callback: ClickCallback? = null +) { + val menu = getMenuByData(Data::class) + PluginManager.registerMenuItem(this, PluginMenuItem(id, menu, title, callback)) +} + +inline fun IPlugin.registerMenuItem( + id: String, + title: String, + crossinline callback: (Data) -> Unit +) { + registerMenuItem(id, title, object : ClickCallback { + override fun onClick(data: Data) { + callback.invoke(data) + } + }) +} diff --git a/app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginCommentLookup.kt b/app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginCommentLookup.kt new file mode 100644 index 00000000..9e4d0518 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginCommentLookup.kt @@ -0,0 +1,18 @@ +package com.huanchengfly.tieba.post.plugins + +import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.api.models.ProfileBean +import com.huanchengfly.tieba.post.plugins.interfaces.IApp +import com.huanchengfly.tieba.post.plugins.models.PluginManifest + +class PluginCommentLookup(app: IApp, manifest: PluginManifest) : IPlugin(app, manifest) { + override fun onEnable() { + super.onEnable() + registerMenuItem( + "lookup_comment", + context.getString(R.string.plugin_comment_lookup_menu) + ) { + app.launchUrl("https://www.82cat.com/tieba/reply/${it.user?.name}/1") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginManager.kt b/app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginManager.kt new file mode 100644 index 00000000..f0187cd5 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/plugins/PluginManager.kt @@ -0,0 +1,168 @@ +package com.huanchengfly.tieba.post.plugins + +import android.content.Context +import android.content.SharedPreferences +import com.huanchengfly.tieba.post.api.models.ProfileBean +import com.huanchengfly.tieba.post.fromJson +import com.huanchengfly.tieba.post.plugins.interfaces.IApp +import com.huanchengfly.tieba.post.plugins.models.PluginManifest +import com.huanchengfly.tieba.post.utils.AssetUtil +import com.huanchengfly.tieba.post.utils.SharedPreferencesUtil +import com.huanchengfly.tieba.post.utils.SharedPreferencesUtil.SP_PLUGINS +import kotlin.reflect.KClass + +object PluginManager { + const val MENU_USER_ACTIVITY = "user_activity" + const val MENU_NONE = "none" + + lateinit var appInstance: IApp + val pluginManifests: MutableList = mutableListOf() + val pluginInstances: MutableList = mutableListOf() + + val registeredPluginMenuItems: MutableMap>> = + mutableMapOf() + + val context: Context + get() = appInstance.getAppContext() + val preferences: SharedPreferences + get() = SharedPreferencesUtil.get(SP_PLUGINS) + + init { + listOf( + MENU_USER_ACTIVITY, + MENU_NONE + ).forEach { + registeredPluginMenuItems[it] = mutableMapOf() + } + } + + fun registerMenuItem(pluginInstance: IPlugin, menuItem: PluginMenuItem) { + registeredPluginMenuItems[menuItem.menuId]!!["${pluginInstance.manifest.id}_${menuItem.id}"] = + menuItem + } + + fun init(app: IApp) { + appInstance = app + reloadPlugins() + } + + fun enablePlugin(id: String) { + pluginInstances.filter { it.manifest.id == id }.forEach { enablePlugin(it) } + } + + fun disablePlugin(id: String) { + pluginInstances.filter { it.manifest.id == id }.forEach { disablePlugin(it) } + } + + private fun createPlugin(pluginManifest: PluginManifest): IPlugin? { + try { + val mainClazz = Class.forName(pluginManifest.mainClass) + val constructor = + mainClazz.getDeclaredConstructor(IApp::class.java, PluginManifest::class.java) + constructor.isAccessible = true + return constructor.newInstance(appInstance, pluginManifest) as IPlugin + } catch (e: Exception) { + e.printStackTrace() + return null + } + } + + private fun enablePlugin(pluginInstance: IPlugin) { + try { + pluginInstance.onEnable() + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun disablePlugin(pluginInstance: IPlugin) { + try { + pluginInstance.onDisable() + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun destroyPlugin(pluginInstance: IPlugin) { + try { + pluginInstance.onDestroy() + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun reloadPluginManifests() { + pluginManifests.clear() + pluginManifests.addAll( + AssetUtil.getStringFromAsset(context, "plugins.json").fromJson>() + ) + } + + fun reloadPlugins() { + pluginInstances.forEach { + disablePlugin(it) + destroyPlugin(it) + } + pluginInstances.clear() + reloadPluginManifests() + pluginManifests.forEach { + try { + if (it.pluginCreated && preferences.getBoolean("${it.id}_enabled", true)) { + val pluginInstance = createPlugin(it) + if (pluginInstance != null) { + pluginInstances.add(pluginInstance) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + pluginInstances.forEach { + enablePlugin(it) + } + } + + private val PluginManifest.pluginCreated: Boolean + get() { + return pluginInstances.firstOrNull { it.manifest.id == id } != null + } +} + +class PluginMenuItem( + val id: String, + val menuId: String, + val title: String, + val callback: ClickCallback? = null +) { + interface ClickCallback { + fun onClick(data: Data) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PluginMenuItem<*>) return false + + if (id != other.id) return false + if (menuId != other.menuId) return false + if (title != other.title) return false + if (callback != other.callback) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + menuId.hashCode() + result = 31 * result + title.hashCode() + return result + } +} + +fun getMenuByData(dataClass: KClass<*>): String = getMenuByData(dataClass.java) + +fun getMenuByData(dataClass: Class<*>): String { + return when (dataClass.canonicalName) { + ProfileBean::class.java.canonicalName -> PluginManager.MENU_USER_ACTIVITY + else -> "none" + } +} diff --git a/app/src/main/java/com/huanchengfly/tieba/post/plugins/interfaces/IApp.kt b/app/src/main/java/com/huanchengfly/tieba/post/plugins/interfaces/IApp.kt new file mode 100644 index 00000000..8c67bfeb --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/plugins/interfaces/IApp.kt @@ -0,0 +1,9 @@ +package com.huanchengfly.tieba.post.plugins.interfaces + +import android.content.Context + +interface IApp { + fun getAppContext(): Context + + fun launchUrl(url: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/plugins/models/PluginManifest.kt b/app/src/main/java/com/huanchengfly/tieba/post/plugins/models/PluginManifest.kt new file mode 100644 index 00000000..f5fa41c6 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/plugins/models/PluginManifest.kt @@ -0,0 +1,12 @@ +package com.huanchengfly.tieba.post.plugins.models + +import com.google.gson.annotations.SerializedName + +data class PluginManifest( + val id: String, + val name: String, + val desc: String, + val version: String, + @SerializedName("main_class") + val mainClass: String +) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/SharedPreferencesUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/SharedPreferencesUtil.java index 37d8d31c..f153a781 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/SharedPreferencesUtil.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/SharedPreferencesUtil.java @@ -17,6 +17,7 @@ public class SharedPreferencesUtil { public static final String SP_PERMISSION = "permission"; public static final String SP_IGNORE_VERSIONS = "ignore_version"; public static final String SP_WEBVIEW_INFO = "webview_info"; + public static final String SP_PLUGINS = "plugins"; public static SharedPreferences get(@Preferences String name) { return get(BaseApplication.getInstance(), name); @@ -50,7 +51,7 @@ public class SharedPreferencesUtil { return put(get(context, preference), key, value); } - @StringDef({SP_APP_DATA, SP_IGNORE_VERSIONS, SP_PERMISSION, SP_SETTINGS, SP_WEBVIEW_INFO, SP_DRAFT}) + @StringDef({SP_APP_DATA, SP_IGNORE_VERSIONS, SP_PERMISSION, SP_SETTINGS, SP_WEBVIEW_INFO, SP_DRAFT, SP_PLUGINS}) @Retention(RetentionPolicy.SOURCE) public @interface Preferences { } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/utils.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/utils.kt index 73f31f7f..090d6e7a 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/utils.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/utils.kt @@ -1,15 +1,22 @@ package com.huanchengfly.tieba.post.utils +import android.content.ActivityNotFoundException import android.content.Context +import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.RippleDrawable +import android.net.Uri +import androidx.browser.customtabs.CustomTabColorSchemeParams +import androidx.browser.customtabs.CustomTabsIntent import com.huanchengfly.tieba.post.BaseApplication import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.activities.WebViewActivity import com.huanchengfly.tieba.post.dpToPxFloat import com.huanchengfly.tieba.post.ui.theme.utils.ColorStateListUtils +import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils @JvmOverloads fun getItemBackgroundDrawable( @@ -101,13 +108,54 @@ fun getIntermixedColorBackground( context, position, itemCount, - positionOffset, - radius, - if (context.appPreferences.listItemsBackgroundIntermixed) { - colors - } else { - intArrayOf(colors[0]) - }, - ripple + positionOffset, + radius, + if (context.appPreferences.listItemsBackgroundIntermixed) { + colors + } else { + intArrayOf(colors[0]) + }, + ripple ) -} \ No newline at end of file +} + +fun launchUrl(context: Context, url: String) { + val uri = Uri.parse(url) + val host = uri.host + val path = uri.path + val scheme = uri.scheme + if (host == null || scheme == null || path == null) { + return + } + if (!path.contains("android_asset")) { + val isTiebaLink = + host.contains("tieba.baidu.com") || host.contains("wappass.baidu.com") || host.contains( + "ufosdk.baidu.com" + ) || host.contains("m.help.baidu.com") + if (isTiebaLink || context.appPreferences.useWebView) { + WebViewActivity.launch(context, url) + } else { + if (context.appPreferences.useCustomTabs) { + val intentBuilder = CustomTabsIntent.Builder() + .setShowTitle(true) + .setDefaultColorSchemeParams( + CustomTabColorSchemeParams.Builder() + .setToolbarColor( + ThemeUtils.getColorByAttr( + context, + R.attr.colorToolbar + ) + ) + .build() + ) + try { + intentBuilder.build().launchUrl(context, uri) + } catch (e: ActivityNotFoundException) { + context.startActivity(Intent(Intent.ACTION_VIEW, uri)) + } + } else { + context.startActivity(Intent(Intent.ACTION_VIEW, uri)) + } + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28d923dd..5131f3e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -465,4 +465,5 @@ 功能未完成开发,敬请期待 透明主题 自定义 + 查询发言