From 6c1882faaeb1a2f6bac8ca81f0ca57b6915aa650 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 13:54:24 -0700 Subject: [PATCH 01/11] Add header and footer to DebugMenuState --- .../src/main/java/com/steamclock/debugmenu/DebugMenuState.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenuState.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenuState.kt index e71ca8b..0a362b0 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenuState.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenuState.kt @@ -6,6 +6,8 @@ import com.steamclock.debugmenu.persistence.DebugMenuPersistence import com.steamclock.debugmenu.persistence.InMemoryPersistence data class DebugMenuState(val title: String, + val header: String? = null, + val footer: String? = null, val options: Map> = mapOf(), val persistence: DebugMenuPersistence = InMemoryPersistence(), val display: DebugMenuDisplay = LogDisplay()) \ No newline at end of file From 9cc68b543ed75d1b80d2d01fb6e16c01f02c0ac1 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 13:55:12 -0700 Subject: [PATCH 02/11] Pass debug menu state to displayMenu --- .../src/main/java/com/steamclock/debugmenu/DebugMenu.kt | 2 +- .../com/steamclock/debugmenu/display/DebugMenuDisplay.kt | 4 ++-- .../java/com/steamclock/debugmenu/display/LogDisplay.kt | 4 ++-- .../com/steamclock/debugmenu_ui/ComposeDebugMenuDisplay.kt | 6 +++--- .../java/com/steamclock/debugmenu_ui/components/Menu.kt | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt index 31b7a14..5d1236a 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt @@ -72,7 +72,7 @@ class DebugMenu private constructor(private val code: String) { state.display.displayCodeEntry() return } - state.display.displayMenu(state.title, state.options[menu]!!) + state.display.displayMenu(state, menu) } fun optionForKey(key: String): DebugOption? { diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/DebugMenuDisplay.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/DebugMenuDisplay.kt index 05c0326..e05ebbb 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/DebugMenuDisplay.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/DebugMenuDisplay.kt @@ -1,12 +1,12 @@ package com.steamclock.debugmenu.display -import com.steamclock.debugmenu.DebugOption +import com.steamclock.debugmenu.DebugMenuState /** * debugmenu * Created by jake on 2021-12-03, 2:32 p.m. */ interface DebugMenuDisplay { - suspend fun displayMenu(title: String, options: List) + suspend fun displayMenu(state: DebugMenuState, menuKey: String) suspend fun displayCodeEntry() } \ No newline at end of file diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt index f6d14c5..1823d0f 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt @@ -7,9 +7,9 @@ import com.steamclock.debugmenu.* * Created by jake on 2021-12-06, 1:56 p.m. */ class LogDisplay: DebugMenuDisplay { - override suspend fun displayMenu(title: String, options: List) { println("Debug Menu: $title") - options.forEach { + override suspend fun displayMenu(state: DebugMenuState, menuKey: String) { + state.options[menuKey]?.forEach { when (it) { is Action -> println(" ${it.title} - Action") diff --git a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/ComposeDebugMenuDisplay.kt b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/ComposeDebugMenuDisplay.kt index f899eb8..eba2882 100644 --- a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/ComposeDebugMenuDisplay.kt +++ b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/ComposeDebugMenuDisplay.kt @@ -12,7 +12,7 @@ import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Composable import com.steamclock.debugmenu.DebugMenu -import com.steamclock.debugmenu.DebugOption +import com.steamclock.debugmenu.DebugMenuState import com.steamclock.debugmenu.display.DebugMenuDisplay import com.steamclock.debugmenu_ui.components.CodeEntry import com.steamclock.debugmenu_ui.components.Menu @@ -58,11 +58,11 @@ class ComposeDebugMenuDisplay(app: Application) : DebugMenuDisplay { dialog.show(activity.supportFragmentManager, null) } - override suspend fun displayMenu(title: String, options: List) { + override suspend fun displayMenu(state: DebugMenuState, menuKey: String) { showDialog { MaterialTheme(colors = if (isSystemInDarkTheme()) darkColors() else lightColors()) { Surface { - Menu(title, options) + Menu(state, menuKey) } } } diff --git a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt index f06e860..0577cd7 100644 --- a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt +++ b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt @@ -17,14 +17,14 @@ import com.steamclock.debugmenu.* * Created by jake on 2021-12-06, 2:15 p.m. */ @Composable -fun Menu(title: String, options: List) { +fun Menu(state: DebugMenuState, menuKey: String) { Column(modifier = Modifier.padding(16.dp)) { Text(text = title, style = MaterialTheme.typography.h6, modifier = Modifier.padding(bottom = 24.dp) ) LazyColumn(verticalArrangement = Arrangement.spacedBy(16.dp)) { - items(options) { option -> + items(state.options[menuKey]!!) { option -> when (option) { is Action -> ActionOption(option) is BooleanValue -> BooleanOption(option) From 0c622146d1d0da30909ce6d50c58deaf5e0580c2 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 13:55:33 -0700 Subject: [PATCH 03/11] Display header and footer --- .../steamclock/debugmenu/display/LogDisplay.kt | 5 +++-- .../steamclock/debugmenu_ui/components/Menu.kt | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt index 1823d0f..c59fcde 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt @@ -7,8 +7,9 @@ import com.steamclock.debugmenu.* * Created by jake on 2021-12-06, 1:56 p.m. */ class LogDisplay: DebugMenuDisplay { - println("Debug Menu: $title") override suspend fun displayMenu(state: DebugMenuState, menuKey: String) { + println("Debug Menu: ${state.title}") + println("Header: ${state.header}") state.options[menuKey]?.forEach { when (it) { is Action -> @@ -27,8 +28,8 @@ class LogDisplay: DebugMenuDisplay { println(" ${it.title} - $value") } } - } + println("Footer: ${state.footer}") } override suspend fun displayCodeEntry() { diff --git a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt index 0577cd7..091608d 100644 --- a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt +++ b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt @@ -19,10 +19,17 @@ import com.steamclock.debugmenu.* @Composable fun Menu(state: DebugMenuState, menuKey: String) { Column(modifier = Modifier.padding(16.dp)) { - Text(text = title, + Text(text = state.title, style = MaterialTheme.typography.h6, - modifier = Modifier.padding(bottom = 24.dp) + modifier = Modifier.padding(bottom = 8.dp) ) + if (state.header != null) { + Text( + text = state.header!!, + style = MaterialTheme.typography.subtitle2, + modifier = Modifier.padding(bottom = 24.dp) + ) + } LazyColumn(verticalArrangement = Arrangement.spacedBy(16.dp)) { items(state.options[menuKey]!!) { option -> when (option) { @@ -35,5 +42,12 @@ fun Menu(state: DebugMenuState, menuKey: String) { } } } + if (state.footer != null) { + Text( + text = state.footer!!, + style = MaterialTheme.typography.subtitle2, + modifier = Modifier.padding(top = 24.dp) + ) + } } } \ No newline at end of file From ba14418689d1fddce3ca45e696c2b4aeac99bec5 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 13:55:48 -0700 Subject: [PATCH 04/11] Allow passing in header and footer into initialization --- app/src/main/java/com/steamclock/debugmenusample/App.kt | 4 +++- .../src/main/java/com/steamclock/debugmenu/DebugMenu.kt | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/steamclock/debugmenusample/App.kt b/app/src/main/java/com/steamclock/debugmenusample/App.kt index c688e4d..fb5c72c 100644 --- a/app/src/main/java/com/steamclock/debugmenusample/App.kt +++ b/app/src/main/java/com/steamclock/debugmenusample/App.kt @@ -16,7 +16,9 @@ class App: Application() { // 123321 DebugMenu.initialize("a320480f534776bddb5cdb54b1e93d210a3c7d199e80a23c1b2178497b184c76", ComposeDebugMenuDisplay(this), - SharedPrefsPersistence(this) + SharedPrefsPersistence(this), + header = "Testing header", + footer = "Testing footer" ) } } \ No newline at end of file diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt index 5d1236a..4b9ccf4 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugMenu.kt @@ -97,11 +97,13 @@ class DebugMenu private constructor(private val code: String) { throw RuntimeException("Call initialize before usage") } - fun initialize(encryptedCode: String, display: DebugMenuDisplay, persistence: DebugMenuPersistence) { + fun initialize(encryptedCode: String, display: DebugMenuDisplay, persistence: DebugMenuPersistence, header: String? = null, footer: String? = null) { val previousState = _instance?.state ?: DebugMenuState(title = "Debug Menu") val newState = previousState.copy( display = display, - persistence = persistence + persistence = persistence, + header = header, + footer = footer ) _instance = DebugMenu(encryptedCode) _instance?.state = newState From d9ed8f3bd2233e96241cd0726abda8adb23ee5cf Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 13:59:51 -0700 Subject: [PATCH 05/11] Add ability to display text --- .../java/com/steamclock/debugmenu/DebugOption.kt | 3 ++- .../steamclock/debugmenu/display/LogDisplay.kt | 3 +++ .../steamclock/debugmenu_ui/components/Menu.kt | 1 + .../debugmenu_ui/components/TextDisplayOption.kt | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/TextDisplayOption.kt diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugOption.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugOption.kt index 32e8486..6fd7581 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugOption.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/DebugOption.kt @@ -10,4 +10,5 @@ data class IntValue(override val title: String, override val key: String, val de data class DoubleValue(override val title: String, override val key: String, val defaultValue: Double = 0.0): DebugOption(title, key) data class LongValue(override val title: String, override val key: String, val defaultValue: Long = 0L): DebugOption(title, key) data class Action(override val title: String, val onClick: suspend () -> Unit): DebugOption(title, title) -data class OptionSelection(override val title: String, override val key: String, val options: List, val defaultIndex: Int? = null): DebugOption(title, key) \ No newline at end of file +data class OptionSelection(override val title: String, override val key: String, val options: List, val defaultIndex: Int? = null): DebugOption(title, key) +data class TextDisplay(val text: String): DebugOption(text, "") \ No newline at end of file diff --git a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt index c59fcde..9c19823 100644 --- a/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt +++ b/debugmenu-core/src/main/java/com/steamclock/debugmenu/display/LogDisplay.kt @@ -27,6 +27,9 @@ class LogDisplay: DebugMenuDisplay { val value = if (index != null) it.options[index] else null println(" ${it.title} - $value") } + is TextDisplay -> { + println(" ${it.text}") + } } } println("Footer: ${state.footer}") diff --git a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt index 091608d..a8e7ce3 100644 --- a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt +++ b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/Menu.kt @@ -39,6 +39,7 @@ fun Menu(state: DebugMenuState, menuKey: String) { is IntValue -> IntOption(option) is LongValue -> LongOption(option) is OptionSelection -> OptionSelection(option) + is TextDisplay -> TextOption(option) } } } diff --git a/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/TextDisplayOption.kt b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/TextDisplayOption.kt new file mode 100644 index 0000000..63695cd --- /dev/null +++ b/debugmenu-ui/src/main/java/com/steamclock/debugmenu_ui/components/TextDisplayOption.kt @@ -0,0 +1,15 @@ +package com.steamclock.debugmenu_ui.components + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import com.steamclock.debugmenu.TextDisplay + +/** + * debugmenu + * Created by jake on 2022-03-23, 1:58 p.m. + */ + +@Composable +fun TextOption(option: TextDisplay) { + Text(option.text) +} \ No newline at end of file From 75780c4fba7f58ca6dba6fbeaed7e08ee654f6ba Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 14:30:10 -0700 Subject: [PATCH 06/11] Add ability to display text elements in debug menu --- .../debugmenusample/MainActivity.kt | 5 +++ .../debugmenu_annotation/Annotations.kt | 5 +++ .../com/steamclock/codegen/ClassBuilder.kt | 44 ++++++++++++++++--- .../com/steamclock/codegen/FileGenerator.kt | 13 +++++- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt b/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt index 4f131a2..a12adde 100644 --- a/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt +++ b/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt @@ -76,6 +76,11 @@ class MainActivity : AppCompatActivity() { TestingMenu.show() } + @DebugTextProvider + fun temp(): String { + return 3.toString() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt b/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt index d11055e..f710d16 100644 --- a/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt +++ b/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt @@ -34,3 +34,8 @@ annotation class DebugDouble(val title: String, val defaultValue: Double = 0.0, @Retention(AnnotationRetention.SOURCE) @MustBeDocumented annotation class DebugSelection(val title: String, val defaultIndex: Int = -1, val menuKey: String = "", val options: Array) + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +annotation class DebugTextProvider(val menuKey: String = "") \ No newline at end of file diff --git a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt index fa92f40..b985709 100644 --- a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt +++ b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt @@ -54,6 +54,9 @@ internal class MenuClassBuilder( "OptionSelection(title = \"${selection.title}\", key = \"${selection.key}\", options = listOf(${options}), defaultIndex = ${selection.defaultIndex})" "DebugMenu.instance.addOptions(key, $text)" } + is TextValueWrapper -> { + "" + } } } @@ -63,6 +66,9 @@ internal class MenuClassBuilder( is ActionWrapper -> { "" } + is TextValueWrapper -> { + "" + } is BooleanWrapper -> { val toggle = it.toggle "val ${toggle.key} = DebugValue(DebugMenu.instance.flow(\"${toggle.key}\"))" @@ -106,12 +112,23 @@ internal class MenuClassBuilder( .groupBy { it.parentClass } } - private val referencedParents: Set + private val textProviderByParents: Map> get() { return options + .filterIsInstance() + .groupBy { it.parentClass } + } + + private val referencedParents: Set + get() { + val actions = options .filterIsInstance() .filter { !it.isGlobal } - .map { it.parentClass }.toSet() + .map { it.parentClass } + val textProviders = options + .filterIsInstance() + .map { it.parentClass } + return (actions + textProviders).toSet() } private val weakReferences: String @@ -128,9 +145,10 @@ internal class MenuClassBuilder( private val initFunctions: String get() { - return actionsByParents.keys.joinToString(separator = "\n ") { parent -> - val actions = actionsByParents[parent] ?: listOf() + return referencedParents.joinToString(separator = "\n ") { parent -> val parentName = parent.split(".").last() + val actions = actionsByParents[parent] ?: listOf() + val textProviders = textProviderByParents[parent] ?: listOf() val actionStrings = actions.joinToString("") { """ @@ -141,12 +159,24 @@ internal class MenuClassBuilder( """ } - """ + val providerStrings = textProviders.joinToString("") { + """ + val ${it.functionName}Text = instance.${parentName}Ref?.get()?.${it.functionName}() as? Any + if (${it.functionName}Text !is String) { + throw Exception("${parent}.${it.functionName} is marked as a StringValueProvider, it must return a String value") + } + val ${it.functionName}TextProvider = TextDisplay(text = instance.${parentName}Ref?.get()?.${it.functionName}() as String) + DebugMenu.instance.addOptions(key, ${it.functionName}TextProvider) + """ + } + + """ fun initialize(parent: $parentName) = runBlocking { instance.${parentName}Ref = WeakReference(parent) $actionStrings - } - """ + $providerStrings + } + """ } } diff --git a/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt b/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt index 79a9492..878d482 100644 --- a/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt +++ b/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt @@ -20,6 +20,7 @@ internal data class DoubleWrapper(val doubleValue: DoubleValue): AnnotationWrapp internal data class LongWrapper(val longValue: LongValue): AnnotationWrapper() internal data class SelectionWrapper(val selectionValue: OptionSelection): AnnotationWrapper() internal data class ActionWrapper(val title: String, val functionName: String, val parentClass: String, val packageName: String, val isGlobal: Boolean): AnnotationWrapper() +internal data class TextValueWrapper(val functionName: String, val parentClass: String, val packageName: String): AnnotationWrapper() @AutoService(Processor::class) // For registering the service @SupportedSourceVersion(SourceVersion.RELEASE_8) // to support Java 8 @@ -53,7 +54,8 @@ class FileGenerator : AbstractProcessor() { DebugDouble::class.java.name, DebugLong::class.java.name, DebugAction::class.java.name, - DebugSelection::class.java.name + DebugSelection::class.java.name, + DebugTextProvider::class.java.name ) } @@ -147,6 +149,15 @@ class FileGenerator : AbstractProcessor() { } if (result == false) return false + result = roundEnvironment?.forEach(DebugTextProvider::class.java, validKind = ElementKind.METHOD) { element, annotation -> + val menuKey = annotation.menuKey + val functionName = element.simpleName.toString() + val parentClass = element.enclosingElement.toString() + val packageName = parentClass.split(".").dropLast(1).joinToString(".") + addOptionToMenu(menuKey, TextValueWrapper(functionName = functionName, parentClass = parentClass, packageName = packageName)) + } + if (result == false) return false + generateMenuClasses(menus) return true From e9efc5822861d284ed227a28518a1b4e80d190fe Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 14:31:43 -0700 Subject: [PATCH 07/11] Update testing text --- .../main/java/com/steamclock/debugmenusample/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt b/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt index a12adde..98cb5e8 100644 --- a/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt +++ b/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt @@ -76,9 +76,9 @@ class MainActivity : AppCompatActivity() { TestingMenu.show() } - @DebugTextProvider + @DebugTextProvider(menuKey = Debug.TestingMenu) fun temp(): String { - return 3.toString() + return "This is testing text" } override fun onCreate(savedInstanceState: Bundle?) { From ae52c68613da9f069e0d5d743e1cfcf9f8da75d9 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 14:37:26 -0700 Subject: [PATCH 08/11] Update README.md --- README.md | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 702903a..440b807 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,9 @@ class App: Application() { super.onCreate() DebugMenu.initialize("5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", ComposeDebugMenuDisplay(this), - SharedPrefsPersistence(this) + SharedPrefsPersistence(this), + header = "", // optional header to display on all debug menus + footer = "" // optional footer to display on all debug menus ) } } @@ -84,6 +86,7 @@ Currently the following options are supported: | `LongValue` | An input field for long values | `0L` | | `Action` | A button that will call the code provided | n/a | | `OptionSelection` | A drop-down menu of string values | `null` | +| `TextDisplay` | A string, useful for showing information | n/a | ## Manual Usage ### Adding Options @@ -119,6 +122,9 @@ class MainActivity : AppCompatActivity() { key = "test-selection", options = listOf("value1", "value2"), defaultIndex = null + ), + TextDisplay( + text = "Test display text" ) ) } @@ -186,13 +192,14 @@ Because the all annotated menu options are added at compile time, it's recommend Adding options can be done using the following Annotations: | Annotation | `BooleanValue` | UI Shown | -|-------------------|-------------------|-------------------------------------------| -| `@DebugBoolean` | `BooleanValue` | A true/false toggle | -| `@DebugInt` | `IntValue` | An input field for integer values | -| `@DebugDouble` | `DoubleValue` | An input field for double values | -| `@DebugLong` | `LongValue` | An input field for long values | -| `@DebugAction` | `Action` | A button that will call the code provided | -| `@DebugSelection` | `OptionSelection` | A drop-down menu of string values | +|----------------------|-------------------|-------------------------------------------| +| `@DebugBoolean` | `BooleanValue` | A true/false toggle | +| `@DebugInt` | `IntValue` | An input field for integer values | +| `@DebugDouble` | `DoubleValue` | An input field for double values | +| `@DebugLong` | `LongValue` | An input field for long values | +| `@DebugAction` | `Action` | A button that will call the code provided | +| `@DebugSelection` | `OptionSelection` | A drop-down menu of string values | +| `@DebugTextProvider` | `TextDisplay` | A string, useful for showing information | All annotations support optional `defaultValues` and `menuKey` parameters. @@ -212,7 +219,6 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } -} ``` It's also possible to define an action scoped to a class using `@DebugAction`. @@ -231,6 +237,22 @@ class MainActivity : AppCompatActivity() { } ``` +You can also provide a text provider, scoped to a class using `@DebugTextProvider`. `@DebugTextProvider` is currently only available when scoped to a class. +```kotlin +class MainActivity : AppCompatActivity() { + @DebugTextProvider + fun textProvider(): String { + return "Simple text provider" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initDebugMenus() // generated extension function that must be called for scoped actions to appear + } +} +``` + ## Reading Debug Values Debug values can be used in a compile-time safe way using the automatically generated classes. Each `menuKey` included in an annotation will generate a class of the same name, which will include a number of wrappers for the options included in that menu. If a `menuKey` is not provided, the debug option will be added to the `GlobalDebugMenu` object instead. From ca0f20ea0e85b0810d2f252bc21e65e0f1008206 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 14:38:28 -0700 Subject: [PATCH 09/11] Fix generatedInitFunctions --- .../src/main/java/com/steamclock/codegen/ClassBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt index b985709..fe9713b 100644 --- a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt +++ b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt @@ -220,6 +220,6 @@ class $menuName private constructor() { fun generatedInitFunctions(): Pair> { // DebugMenu -> MainActivity, SettingsActivity - return menuName to actionsByParents.keys.map { parent -> parent } + return menuName to referencedParents.map { parent -> parent } } } \ No newline at end of file From 3879a9c22e5574e6af0f3cdd055f6597bab34a81 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 15:20:43 -0700 Subject: [PATCH 10/11] Add ability to generate selection options at run time via annotation --- README.md | 26 +++++++++- .../debugmenusample/MainActivity.kt | 9 ++++ .../debugmenu_annotation/Annotations.kt | 8 ++- .../com/steamclock/codegen/ClassBuilder.kt | 49 +++++++++++++++---- .../com/steamclock/codegen/FileGenerator.kt | 22 ++++++++- 5 files changed, 101 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 440b807..fdca445 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ Adding options can be done using the following Annotations: | `@DebugAction` | `Action` | A button that will call the code provided | | `@DebugSelection` | `OptionSelection` | A drop-down menu of string values | | `@DebugTextProvider` | `TextDisplay` | A string, useful for showing information | +| `@DebugSelectionProvider` | `OptionSelection` | A drop-down menu of string values | All annotations support optional `defaultValues` and `menuKey` parameters. @@ -219,6 +220,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } +} ``` It's also possible to define an action scoped to a class using `@DebugAction`. @@ -226,7 +228,7 @@ It's also possible to define an action scoped to a class using `@DebugAction`. class MainActivity : AppCompatActivity() { @DebugAction(title = "Scoped action") fun scopedGlobalAction() { - // can access MainActivity's state + // can access MainActivity's state, is called when the onClick for the debug button is invoked } override fun onCreate(savedInstanceState: Bundle?) { @@ -242,6 +244,7 @@ You can also provide a text provider, scoped to a class using `@DebugTextProvide class MainActivity : AppCompatActivity() { @DebugTextProvider fun textProvider(): String { + // string generated when `initDebugMenus()` is called return "Simple text provider" } @@ -253,6 +256,27 @@ class MainActivity : AppCompatActivity() { } ``` + +You can also define an option selection provider, scoped to a class using `@DebugSelectionProvider`. `@DebugSelectionProvider` is currently only available when scoped to a class. +```kotlin +class MainActivity : AppCompatActivity() { + @DebugSelectionProvider + fun textProvider(): List { + // list of options generated when `initDebugMenus()` is called + return listOf( + "Option 1", + "Option 2", + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initDebugMenus() // generated extension function that must be called for scoped actions to appear + } +} +``` + ## Reading Debug Values Debug values can be used in a compile-time safe way using the automatically generated classes. Each `menuKey` included in an annotation will generate a class of the same name, which will include a number of wrappers for the options included in that menu. If a `menuKey` is not provided, the debug option will be added to the `GlobalDebugMenu` object instead. diff --git a/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt b/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt index 98cb5e8..7505f4f 100644 --- a/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt +++ b/app/src/main/java/com/steamclock/debugmenusample/MainActivity.kt @@ -81,6 +81,15 @@ class MainActivity : AppCompatActivity() { return "This is testing text" } + @DebugSelectionProvider(title = "Annotated Selection") + fun temp2(): List { + return listOf( + "testing 1", + "testing 2", + 2.toString() + ) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt b/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt index f710d16..b13d122 100644 --- a/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt +++ b/debugmenu-annotation/src/main/java/com/steamclock/debugmenu_annotation/Annotations.kt @@ -38,4 +38,10 @@ annotation class DebugSelection(val title: String, val defaultIndex: Int = -1, v @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented -annotation class DebugTextProvider(val menuKey: String = "") \ No newline at end of file +annotation class DebugTextProvider(val menuKey: String = "") + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +annotation class DebugSelectionProvider(val title: String, val defaultIndex: Int = -1, val menuKey: String = "") + diff --git a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt index fe9713b..7429fa4 100644 --- a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt +++ b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt @@ -1,11 +1,5 @@ package com.steamclock.codegen -import com.steamclock.debugmenu.DebugMenu -import com.steamclock.debugmenu.OptionSelection -import com.steamclock.debugmenu.flow -import com.steamclock.debugmenu_annotation.DebugValue -import kotlinx.coroutines.flow.mapNotNull - internal class MenuClassBuilder( private val menuKey: String, private val menuName: String, @@ -57,6 +51,9 @@ internal class MenuClassBuilder( is TextValueWrapper -> { "" } + is SelectionProviderWrapper -> { + "" + } } } @@ -69,6 +66,9 @@ internal class MenuClassBuilder( is TextValueWrapper -> { "" } + is SelectionProviderWrapper -> { + "" + } is BooleanWrapper -> { val toggle = it.toggle "val ${toggle.key} = DebugValue(DebugMenu.instance.flow(\"${toggle.key}\"))" @@ -119,6 +119,13 @@ internal class MenuClassBuilder( .groupBy { it.parentClass } } + private val selectionProviderByParents: Map> + get() { + return options + .filterIsInstance() + .groupBy { it.parentClass } + } + private val referencedParents: Set get() { val actions = options @@ -128,7 +135,10 @@ internal class MenuClassBuilder( val textProviders = options .filterIsInstance() .map { it.parentClass } - return (actions + textProviders).toSet() + val selectionProviders = options + .filterIsInstance() + .map { it.parentClass } + return (actions + textProviders + selectionProviders).toSet() } private val weakReferences: String @@ -149,6 +159,7 @@ internal class MenuClassBuilder( val parentName = parent.split(".").last() val actions = actionsByParents[parent] ?: listOf() val textProviders = textProviderByParents[parent] ?: listOf() + val selectionProviders = selectionProviderByParents[parent] ?: listOf() val actionStrings = actions.joinToString("") { """ @@ -159,22 +170,40 @@ internal class MenuClassBuilder( """ } - val providerStrings = textProviders.joinToString("") { + val textProviderStrings = textProviders.joinToString("") { """ val ${it.functionName}Text = instance.${parentName}Ref?.get()?.${it.functionName}() as? Any if (${it.functionName}Text !is String) { throw Exception("${parent}.${it.functionName} is marked as a StringValueProvider, it must return a String value") } - val ${it.functionName}TextProvider = TextDisplay(text = instance.${parentName}Ref?.get()?.${it.functionName}() as String) + val ${it.functionName}TextProvider = TextDisplay(text = ${it.functionName}Text) DebugMenu.instance.addOptions(key, ${it.functionName}TextProvider) """ } + val selectionProviderStrings = selectionProviders.joinToString("") { + """ + val ${it.functionName}Selections = instance.${parentName}Ref?.get()?.${it.functionName}() as? Any + if (${it.functionName}Selections !is List<*>) { + throw Exception("${parent}.${it.functionName} is marked as a DebugSelectionProvider, it must return a List value") + } + ${it.functionName}Selections.forEach { + if (it !is String) { + throw Exception("${parent}.${it.functionName} is marked as a DebugSelectionProvider, it must return a List value") + } + } + + val ${it.functionName}Selection = OptionSelection(title = "${it.title}", key = "${it.key}", options = ${it.functionName}Selections as List, defaultIndex = ${it.defaultIndex}) + DebugMenu.instance.addOptions(key, ${it.functionName}Selection) + """ + } + """ fun initialize(parent: $parentName) = runBlocking { instance.${parentName}Ref = WeakReference(parent) $actionStrings - $providerStrings + $textProviderStrings + $selectionProviderStrings } """ } diff --git a/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt b/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt index 878d482..5172620 100644 --- a/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt +++ b/debugmenu-codegen/src/main/java/com/steamclock/codegen/FileGenerator.kt @@ -21,6 +21,7 @@ internal data class LongWrapper(val longValue: LongValue): AnnotationWrapper() internal data class SelectionWrapper(val selectionValue: OptionSelection): AnnotationWrapper() internal data class ActionWrapper(val title: String, val functionName: String, val parentClass: String, val packageName: String, val isGlobal: Boolean): AnnotationWrapper() internal data class TextValueWrapper(val functionName: String, val parentClass: String, val packageName: String): AnnotationWrapper() +internal data class SelectionProviderWrapper(val title: String, val key: String, val defaultIndex: Int? = null, val functionName: String, val parentClass: String, val packageName: String): AnnotationWrapper() @AutoService(Processor::class) // For registering the service @SupportedSourceVersion(SourceVersion.RELEASE_8) // to support Java 8 @@ -55,7 +56,8 @@ class FileGenerator : AbstractProcessor() { DebugLong::class.java.name, DebugAction::class.java.name, DebugSelection::class.java.name, - DebugTextProvider::class.java.name + DebugTextProvider::class.java.name, + DebugSelectionProvider::class.java.name ) } @@ -158,6 +160,24 @@ class FileGenerator : AbstractProcessor() { } if (result == false) return false + result = roundEnvironment?.forEach(DebugSelectionProvider::class.java, validKind = ElementKind.METHOD) { element, annotation -> + val menuKey = annotation.menuKey + val functionName = element.simpleName.toString() + val parentClass = element.enclosingElement.toString() + val packageName = parentClass.split(".").dropLast(1).joinToString(".") + val name = element.simpleName.toString() + val title = annotation.title + val defaultValue = annotation.defaultIndex + + // annotations can't include null, so we use -1 instead to represent the same state + val correctedDefaultValue = if (defaultValue == -1) null else defaultValue + + addOptionToMenu(menuKey, SelectionProviderWrapper( + title = title, key = name, defaultIndex = correctedDefaultValue, + functionName = functionName, parentClass = parentClass, packageName = packageName)) + } + if (result == false) return false + generateMenuClasses(menus) return true From 1bc47add74416d0c4c96e606d1e144540b16ae75 Mon Sep 17 00:00:00 2001 From: Jacob Miner Date: Wed, 23 Mar 2022 15:22:01 -0700 Subject: [PATCH 11/11] Suppress casting warning --- .../src/main/java/com/steamclock/codegen/ClassBuilder.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt index 7429fa4..9dcc9dd 100644 --- a/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt +++ b/debugmenu-codegen/src/main/java/com/steamclock/codegen/ClassBuilder.kt @@ -199,6 +199,7 @@ internal class MenuClassBuilder( } """ + @Suppress("UNCHECKED_CAST") fun initialize(parent: $parentName) = runBlocking { instance.${parentName}Ref = WeakReference(parent) $actionStrings