Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
[versions]

testCore = "1.6.1"
coreTesting = "2.2.0"
java = "1.8"

minSdk = "21"
mockitoCore = "5.10.0"
robolectric = "4.11.1"
runner = "1.6.2"
targetSdk = "32"
compileSdk = "34"
buildTools = "34.0.0"
Expand Down Expand Up @@ -43,6 +48,8 @@ androidx-browser = { module = "androidx.browser:browser", version = "1.4.0" }
androidx-cardview = { module = "androidx.cardview:cardview", version = "1.0.0" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.2" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidXCore" }
androidx-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "coreTesting" }
androidx-test-core = { module = "androidx.test:core", version.ref = "testCore" }
androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidXLifecycle" }
androidx-lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidXLifecycle" }
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidXLifecycle" }
Expand All @@ -51,6 +58,7 @@ androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragme
androidx-navigation-ui = { module = "androidx.navigation:navigation-ui", version.ref = "navigation" }
androidx-preference = { module = "androidx.preference:preference-ktx", version = "1.2.0" }
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.2.1" }
androidx-runner = { module = "androidx.test:runner", version.ref = "runner" }
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }

datastore-preferences = { module = "androidx.datastore:datastore-preferences", version = "1.0.0" }
Expand All @@ -69,6 +77,7 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx

leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version = "2.7" }

mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoCore" }
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }

Expand All @@ -78,6 +87,7 @@ okio = { module = "com.squareup.okio:okio", version = "2.10.0" }

retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
room = { module = "androidx.room:room-ktx", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }

Expand Down
29 changes: 29 additions & 0 deletions pluto-plugins/base/lib/src/main/java/com/pluto/plugin/DataModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,47 @@ package com.pluto.plugin

import androidx.annotation.DrawableRes

/**
* Data class containing details about a plugin's developer.
*
* This class is used to provide information about the developer of a plugin,
* which can be displayed in the plugin's details screen.
*
* @property vcsLink Optional link to the version control system repository
* @property website Optional link to the developer's website
* @property twitter Optional link to the developer's Twitter profile
*/
data class DeveloperDetails(
val vcsLink: String? = null,
val website: String? = null,
val twitter: String? = null
)

/**
* Data class containing configuration for a plugin.
*
* This class defines the visual and metadata properties of a plugin,
* such as its name, icon, and version.
*
* @property name The display name of the plugin
* @property icon The resource ID of the plugin's icon
* @property version The version string of the plugin
*/
data class PluginConfiguration(
val name: String,
@DrawableRes val icon: Int = R.drawable.pluto___ic_plugin_placeholder_icon,
val version: String
)

/**
* Data class containing configuration for a plugin group.
*
* This class defines the visual properties of a plugin group,
* such as its name and icon.
*
* @property name The display name of the plugin group
* @property icon The resource ID of the plugin group's icon
*/
data class PluginGroupConfiguration(
val name: String,
@DrawableRes val icon: Int = R.drawable.pluto___ic_plugin_group_placeholder_icon,
Expand Down
116 changes: 115 additions & 1 deletion pluto-plugins/base/lib/src/main/java/com/pluto/plugin/Plugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,161 @@ import android.widget.Toast.LENGTH_SHORT
import androidx.annotation.Keep
import androidx.fragment.app.Fragment

/**
* Base class for all Pluto plugins.
*
* This abstract class provides the foundation for creating Pluto debugging plugins.
* It handles plugin lifecycle, configuration, and UI presentation.
*
* To create a new plugin, extend this class and implement the required abstract methods.
*
* Example:
* ```
* class NetworkPlugin : Plugin("network") {
* override fun getConfig() = PluginConfiguration(
* name = "Network",
* icon = R.drawable.ic_network,
* version = "1.0.0"
* )
*
* override fun getView() = NetworkFragment()
*
* override fun onPluginInstalled() {
* // Initialize plugin resources
* }
*
* override fun onPluginDataCleared() {
* // Clear plugin data
* }
* }
* ```
*
* @property identifier A unique string identifier for the plugin
*/
@Keep
abstract class Plugin(val identifier: String) : PluginEntity(identifier) {

/**
* The application context.
*
* This property provides access to the application context for the plugin.
* It throws an IllegalStateException if accessed before the plugin is installed.
*/
val context: Context
get() = returnContext()

/**
* The application instance.
*
* This property provides access to the application instance for the plugin.
* It throws an IllegalStateException if accessed before the plugin is installed.
*/
val application: Application
get() = returnApplication()

/** The internal application instance, set during installation */
private var _application: Application? = null

/**
* Returns the application context.
*
* @throws IllegalStateException if the plugin is not installed
* @return The application context
*/
private fun returnContext(): Context {
_application?.let {
return it.applicationContext
}
throw IllegalStateException("${this.javaClass.name} plugin is not installed yet.")
}

/**
* Returns the application instance.
*
* @throws IllegalStateException if the plugin is not installed
* @return The application instance
*/
private fun returnApplication(): Application {
_application?.let {
return it
}
throw IllegalStateException("${this.javaClass.name} plugin is not installed yet.")
}

/**
* Bundle for saving instance state.
*
* This bundle can be used to save and restore the plugin's state.
*/
var savedInstance: Bundle = Bundle()
private set

/**
* Installs the plugin with the provided application instance.
*
* This method is final and cannot be overridden. It sets the application
* instance and calls onPluginInstalled().
*
* @param application The application instance to use for installation
*/
final override fun install(application: Application) {
this._application = application
onPluginInstalled()
}

/**
* Returns the plugin configuration.
*
* This method should provide a PluginConfiguration object that defines
* the plugin's name, icon, and version.
*
* @return The plugin configuration
*/
abstract fun getConfig(): PluginConfiguration

/**
* Returns the plugin's UI view.
*
* This method should provide a Fragment that implements the plugin's UI.
*
* @return The plugin's UI fragment
*/
abstract fun getView(): Fragment

/**
* Returns the plugin developer's details.
*
* This method can be overridden to provide information about the plugin's
* developer, such as VCS link, website, and Twitter handle.
*
* @return The developer details, or null if not provided
*/
open fun getDeveloperDetails(): DeveloperDetails? = null

/**
* plugin lifecycle methods
* Called when the plugin is installed.
*
* This method is called during the plugin installation process.
* It should be used to initialize any resources needed by the plugin.
*/
abstract fun onPluginInstalled()

/**
* Called when the plugin's data should be cleared.
*
* This method is called when the user requests to clear the plugin's data.
* It should be used to clear any cached data or logs.
*/
abstract fun onPluginDataCleared()

/**
* Called when the plugin's view is created.
*
* This method is called when the plugin's UI is created.
* It shows a toast message indicating that the view has switched to this plugin.
*
* @param savedInstanceState The saved instance state bundle
*/
@SuppressWarnings("UnusedPrivateMember")
fun onPluginViewCreated(savedInstanceState: Bundle?) {
Toast.makeText(context, "View switched to ${getConfig().name}", LENGTH_SHORT).show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,41 @@ package com.pluto.plugin

import android.app.Application

/**
* Base class for all Pluto plugin entities.
*
* This abstract class serves as the foundation for both individual plugins and plugin groups.
* It provides a common interface for installation and identity management.
*
* @property identifier A unique string identifier for the plugin entity
*/
abstract class PluginEntity(private val identifier: String) {

/**
* Installs the plugin entity with the provided application instance.
*
* This method is called during Pluto initialization to set up the plugin.
*
* @param application The application instance to use for installation
*/
abstract fun install(application: Application)

/**
* Compares this plugin entity with another object for equality.
*
* Plugin entities are considered equal if they have the same identifier.
*
* @param other The object to compare with
* @return True if the objects are equal, false otherwise
*/
override fun equals(other: Any?): Boolean = other is PluginEntity && identifier == other.identifier

/**
* Returns a hash code value for this plugin entity.
*
* The hash code is based on the identifier to ensure consistency with equals.
*
* @return The hash code value
*/
override fun hashCode(): Int = identifier.hashCode()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,77 @@ package com.pluto.plugin

import android.app.Application

/**
* Base class for grouping related Pluto plugins.
*
* This abstract class allows multiple plugins to be grouped together under a
* common identifier. Plugin groups are useful for organizing related plugins,
* such as database inspection tools or network monitoring utilities.
*
* To create a new plugin group, extend this class and implement the required
* abstract methods.
*
* Example:
* ```
* class DatabasePluginGroup : PluginGroup("database") {
* override fun getConfig() = PluginGroupConfiguration(
* name = "Database Tools"
* )
*
* override fun getPlugins() = listOf(
* RoomDatabasePlugin(),
* SharedPreferencesPlugin()
* )
* }
* ```
*
* @param identifier A unique string identifier for the plugin group
*/
abstract class PluginGroup(identifier: String) : PluginEntity(identifier) {

/** Set of installed plugins in this group */
private var plugins: LinkedHashSet<Plugin> = linkedSetOf()

/**
* List of all installed plugins in this group.
*
* This property returns a copy of the internal plugins set as a list,
* ensuring that the original set cannot be modified externally.
*/
val installedPlugins: List<Plugin>
get() {
val list = arrayListOf<Plugin>()
list.addAll(plugins)
return list
}

/**
* Returns the plugin group configuration.
*
* This method should provide a PluginGroupConfiguration object that defines
* the group's name and icon.
*
* @return The plugin group configuration
*/
abstract fun getConfig(): PluginGroupConfiguration

/**
* Returns the list of plugins in this group.
*
* This method should provide a list of all plugins that belong to this group.
*
* @return The list of plugins in this group
*/
protected abstract fun getPlugins(): List<Plugin>

/**
* Installs all plugins in this group with the provided application instance.
*
* This method is final and cannot be overridden. It installs each plugin
* in the group and adds it to the internal registry of plugins.
*
* @param application The application instance to use for installation
*/
final override fun install(application: Application) {
getPlugins().forEach {
it.install(application)
Expand Down
8 changes: 8 additions & 0 deletions pluto/lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,12 @@ dependencies {

implementation(libs.moshi)
ksp(libs.moshi.codegen)

// Test dependencies
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.mockito.core)
testImplementation(libs.androidx.core.testing)
testImplementation(libs.androidx.test.core)
testImplementation(libs.androidx.runner)
}
Loading