feat: 插件管理界面

This commit is contained in:
HuanChengFly 2021-08-15 19:22:28 +08:00
parent c2af149d1d
commit bfcdf1b553
15 changed files with 233 additions and 22 deletions

View File

@ -39,17 +39,27 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:largeHeap="true" android:largeHeap="true"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<activity android:name=".activities.HotMessageListActivity" /> <activity
android:name=".activities.PluginManageActivity"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden"
android:windowSoftInputMode="adjustResize"
android:label="@string/title_plugin_manage" />
<activity
android:name=".activities.HotMessageListActivity"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden"
android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".activities.PhotoViewActivity" android:name=".activities.PhotoViewActivity"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden" android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden"
android:theme="@style/AppTheme.PhotoView" android:windowSoftInputMode="adjustResize"
android:windowSoftInputMode="adjustResize" /> android:theme="@style/AppTheme.PhotoView" />
<receiver <receiver
android:name=".receivers.BootCompleteSignReceiver" android:name=".receivers.BootCompleteSignReceiver"
@ -122,8 +132,8 @@
<activity <activity
android:name=".activities.HistoryActivity" android:name=".activities.HistoryActivity"
android:label="@string/title_history"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden" android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden"
android:label="@string/title_history"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity android:name=".activities.OKSignActivity"> <activity android:name=".activities.OKSignActivity">
<intent-filter> <intent-filter>
@ -199,8 +209,8 @@
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".activities.UserCollectActivity" android:name=".activities.UserCollectActivity"
android:label="@string/title_my_collect"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden" android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden"
android:label="@string/title_my_collect"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".activities.MessageActivity" android:name=".activities.MessageActivity"
@ -236,8 +246,8 @@
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".activities.ForumInfoActivity" android:name=".activities.ForumInfoActivity"
android:label="@string/title_forum_info"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden" android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden"
android:label="@string/title_forum_info"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name="com.yalantis.ucrop.UCropActivity" android:name="com.yalantis.ucrop.UCropActivity"
@ -250,8 +260,8 @@
<activity <activity
android:name=".activities.AppThemeActivity" android:name=".activities.AppThemeActivity"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden" android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize|keyboardHidden"
android:windowSoftInputMode="adjustResize" android:label="@string/title_theme_settings"
android:label="@string/title_theme_settings" /> android:windowSoftInputMode="adjustResize" />
<receiver android:name=".receivers.AutoSignAlarm" /> <receiver android:name=".receivers.AutoSignAlarm" />

View File

@ -4,6 +4,7 @@
"id": "CommentLookup", "id": "CommentLookup",
"name": "发言查询", "name": "发言查询",
"desc": "使用第三方工具箱查询用户过往发言", "desc": "使用第三方工具箱查询用户过往发言",
"author": "huanchengfly",
"version": "1.0", "version": "1.0",
"main_class": "com.huanchengfly.tieba.post.plugins.PluginCommentLookup" "main_class": "com.huanchengfly.tieba.post.plugins.PluginCommentLookup"
} }

View File

@ -173,7 +173,7 @@ class BaseApplication : Application(), IApp {
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {} override fun onActivityDestroyed(activity: Activity) {}
}) })
CrashUtil.CrashHandler.getInstance().init(this) if (BuildConfig.DEBUG) CrashUtil.CrashHandler.getInstance().init(this)
PluginManager.init(this) PluginManager.init(this)
} }
@ -472,6 +472,6 @@ class BaseApplication : Application(), IApp {
} }
override fun launchUrl(url: String) { override fun launchUrl(url: String) {
launchUrl(this, url) launchUrl(mActivityList.lastOrNull() ?: this, url)
} }
} }

View File

@ -0,0 +1,44 @@
package com.huanchengfly.tieba.post.activities
import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.adapters.PluginManageAdapter
import com.huanchengfly.tieba.post.components.dividers.SpacesItemDecoration
import com.huanchengfly.tieba.post.dpToPx
import com.huanchengfly.tieba.post.plugins.PluginManager
import com.huanchengfly.tieba.post.utils.ThemeUtil
class PluginManageActivity : BaseActivity() {
@BindView(R.id.toolbar)
lateinit var toolbar: Toolbar
@BindView(R.id.collapsing_toolbar)
lateinit var collapsingToolbar: CollapsingToolbarLayout
@BindView(R.id.recycler_view)
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ThemeUtil.setTranslucentThemeBackground(findViewById(R.id.background))
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = title
collapsingToolbar.title = title
PluginManager.reloadPluginManifests()
recyclerView.apply {
adapter = PluginManageAdapter(this@PluginManageActivity)
layoutManager = LinearLayoutManager(this@PluginManageActivity)
addItemDecoration(SpacesItemDecoration(0, 0, 0, 8.dpToPx()))
}
}
override fun getLayoutId(): Int {
return R.layout.activity_plugin_manage
}
}

View File

@ -219,7 +219,6 @@ class UserActivity : BaseActivity() {
} }
} }
return if (PluginManager.performPluginMenuClick( return if (PluginManager.performPluginMenuClick(
this,
PluginManager.MENU_USER_ACTIVITY, PluginManager.MENU_USER_ACTIVITY,
item.itemId, item.itemId,
profileBean profileBean

View File

@ -0,0 +1,45 @@
package com.huanchengfly.tieba.post.adapters
import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.edit
import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.adapters.base.BaseSingleTypeAdapter
import com.huanchengfly.tieba.post.components.MyViewHolder
import com.huanchengfly.tieba.post.plugins.PluginManager
import com.huanchengfly.tieba.post.plugins.models.PluginManifest
import com.huanchengfly.tieba.post.utils.SharedPreferencesUtil
class PluginManageAdapter(context: Context) :
BaseSingleTypeAdapter<PluginManifest>(context, PluginManager.pluginManifests) {
val preferences: SharedPreferences
get() = SharedPreferencesUtil.get(SharedPreferencesUtil.SP_PLUGINS)
override fun getItemLayoutId(): Int {
return R.layout.item_plugin_list
}
override fun convert(viewHolder: MyViewHolder, item: PluginManifest, position: Int) {
viewHolder.setText(R.id.plugin_name, item.name)
viewHolder.setText(
R.id.plugin_info,
context.getString(R.string.template_plugin_info, item.version, item.author)
)
viewHolder.setText(R.id.plugin_desc, item.desc)
viewHolder.getView<SwitchCompat>(R.id.plugin_status).apply {
isChecked = preferences.getBoolean("${item.id}_enabled", false)
setOnCheckedChangeListener { _, isChecked ->
preferences.edit(commit = true) {
putBoolean("${item.id}_enabled", isChecked)
}
PluginManager.reloadPlugins()
}
}
}
fun refresh() {
PluginManager.reloadPluginManifests()
setData(PluginManager.pluginManifests)
}
}

View File

@ -33,11 +33,11 @@ inline fun <reified Data> IPlugin.registerMenuItem(
inline fun <reified Data> IPlugin.registerMenuItem( inline fun <reified Data> IPlugin.registerMenuItem(
id: String, id: String,
title: String, title: String,
crossinline callback: (Context, Data) -> Unit crossinline callback: (Data) -> Unit
) { ) {
registerMenuItem(id, title, object : ClickCallback<Data> { registerMenuItem(id, title, object : ClickCallback<Data> {
override fun onClick(context: Context, data: Data) { override fun onClick(data: Data) {
callback.invoke(context, data) callback.invoke(data)
} }
}) })
} }

View File

@ -4,7 +4,6 @@ import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.api.models.ProfileBean import com.huanchengfly.tieba.post.api.models.ProfileBean
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 com.huanchengfly.tieba.post.utils.launchUrl
class PluginCommentLookup(app: IApp, manifest: PluginManifest) : IPlugin(app, manifest) { class PluginCommentLookup(app: IApp, manifest: PluginManifest) : IPlugin(app, manifest) {
override fun onEnable() { override fun onEnable() {
@ -12,8 +11,8 @@ class PluginCommentLookup(app: IApp, manifest: PluginManifest) : IPlugin(app, ma
registerMenuItem<ProfileBean>( registerMenuItem<ProfileBean>(
"lookup_comment", "lookup_comment",
context.getString(R.string.plugin_comment_lookup_menu) context.getString(R.string.plugin_comment_lookup_menu)
) { context, data -> ) { data ->
launchUrl(context, "https://www.82cat.com/tieba/reply/${data.user?.name}/1") app.launchUrl("https://www.82cat.com/tieba/reply/${data.user?.name}/1")
} }
} }
} }

View File

@ -30,6 +30,11 @@ object PluginManager {
get() = SharedPreferencesUtil.get(SP_PLUGINS) get() = SharedPreferencesUtil.get(SP_PLUGINS)
init { init {
reloadPluginMenu()
}
private fun reloadPluginMenu() {
registeredPluginMenuItems.clear()
listOf( listOf(
MENU_USER_ACTIVITY, MENU_USER_ACTIVITY,
MENU_NONE MENU_NONE
@ -56,7 +61,6 @@ object PluginManager {
} }
fun <Data> performPluginMenuClick( fun <Data> performPluginMenuClick(
context: Context,
menuId: String, menuId: String,
itemId: Int, itemId: Int,
data: Data data: Data
@ -67,7 +71,7 @@ object PluginManager {
return false return false
} }
return try { return try {
(item as PluginMenuItem<Data>).callback!!.onClick(context, data) (item as PluginMenuItem<Data>).callback!!.onClick(data)
true true
} catch (e: Exception) { } catch (e: Exception) {
false false
@ -132,10 +136,11 @@ object PluginManager {
destroyPlugin(it) destroyPlugin(it)
} }
pluginInstances.clear() pluginInstances.clear()
reloadPluginMenu()
reloadPluginManifests() reloadPluginManifests()
pluginManifests.forEach { pluginManifests.forEach {
try { try {
if (!it.pluginCreated && preferences.getBoolean("${it.id}_enabled", true)) { if (!it.pluginCreated && preferences.getBoolean("${it.id}_enabled", false)) {
val pluginInstance = createPlugin(it) val pluginInstance = createPlugin(it)
if (pluginInstance != null) { if (pluginInstance != null) {
pluginInstances.add(pluginInstance) pluginInstances.add(pluginInstance)
@ -163,7 +168,7 @@ class PluginMenuItem<Data>(
val callback: ClickCallback<Data>? = null val callback: ClickCallback<Data>? = null
) { ) {
interface ClickCallback<Data> { interface ClickCallback<Data> {
fun onClick(context: Context, data: Data) fun onClick(data: Data)
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -7,6 +7,7 @@ data class PluginManifest(
val name: String, val name: String,
val desc: String, val desc: String,
val version: String, val version: String,
val author: String,
@SerializedName("main_class") @SerializedName("main_class")
val mainClass: String val mainClass: String
) )

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7s2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z" />
</vector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<com.huanchengfly.tieba.post.widgets.theme.TintCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:backgroundTint="@color/default_color_window_background"
tools:context=".activities.PluginManageActivity">
<include layout="@layout/layout_new_appbar" />
<androidx.recyclerview.widget.RecyclerView
android:paddingStart="@dimen/card_margin"
android:paddingEnd="@dimen/card_margin"
android:paddingTop="@dimen/card_margin"
android:clipToPadding="false"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</com.huanchengfly.tieba.post.widgets.theme.TintCoordinatorLayout>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<com.huanchengfly.tieba.post.widgets.theme.TintLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="@drawable/bg_radius_10dp"
app:backgroundTint="@color/default_color_card">
<LinearLayout
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.huanchengfly.tieba.post.widgets.theme.TintTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/plugin_name"
app:tint="@color/default_color_text"
android:textStyle="bold"
android:textSize="16sp"
android:singleLine="true"
tools:text="插件名称" />
<com.huanchengfly.tieba.post.widgets.theme.TintTextView
android:layout_marginTop="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/plugin_info"
app:tint="@color/default_color_text_secondary"
android:textSize="14sp"
android:singleLine="true"
tools:text="v1.0 · 作者 huanchengfly" />
</LinearLayout>
<com.huanchengfly.tieba.post.widgets.theme.TintSwitch
style="@style/Widget.Switch"
android:minHeight="0dp"
android:id="@+id/plugin_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<com.huanchengfly.tieba.post.widgets.theme.TintTextView
android:layout_marginTop="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/plugin_desc"
app:tint="@color/default_color_text_secondary"
android:textSize="14sp"
tools:text="这是一个示例插件" />
</com.huanchengfly.tieba.post.widgets.theme.TintLinearLayout>

View File

@ -466,4 +466,6 @@
<string name="title_theme_translucent">透明主题</string> <string name="title_theme_translucent">透明主题</string>
<string name="title_theme_custom">自定义</string> <string name="title_theme_custom">自定义</string>
<string name="plugin_comment_lookup_menu">查询发言</string> <string name="plugin_comment_lookup_menu">查询发言</string>
<string name="title_plugin_manage">附加功能管理</string>
<string name="template_plugin_info">v%1$s · 作者 %2$s</string>
</resources> </resources>

View File

@ -120,6 +120,16 @@
android:key="forumFabFunction" android:key="forumFabFunction"
android:title="@string/settings_forum_fab_function" /> android:title="@string/settings_forum_fab_function" />
<Preference
android:icon="@drawable/ic_round_extension"
android:key="plugin_manage"
android:title="@string/title_plugin_manage">
<intent
android:targetPackage="com.huanchengfly.tieba.post"
android:targetClass="com.huanchengfly.tieba.post.activities.PluginManageActivity"
android:action="android.intent.action.VIEW" />
</Preference>
<Preference <Preference
android:enabled="false" android:enabled="false"
android:layout="@layout/layout_preference_bottom" /> android:layout="@layout/layout_preference_bottom" />