Skip to content

Commit

Permalink
Feat/sealed interface actions (#27)
Browse files Browse the repository at this point in the history
* feat: add new type of the actions handlers for the template

* feat: add AI based generation for the Sealed actions

* feat: ad UI for the actions type selection
  • Loading branch information
levinzonr authored Sep 7, 2024
1 parent 348311e commit f1826d3
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@ class TemplateGenerator(private val project: Project) {
directory: PsiDirectory,
properties: MutableMap<String,Any>
) : PsiFile {
try {

val existing = directory.findFile("${fileName}.kt")
if (existing != null) return existing
val existing = directory.findFile("${fileName}.kt")
if (existing != null) return existing


val manager = FileTemplateManager.getInstance(project)
val template = manager.getInternalTemplate(templateName)
properties[PropertyKeys.PackageName] = requireNotNull(directory.getPackageName())
return FileTemplateUtil.createFromTemplate(
val manager = FileTemplateManager.getInstance(project)
val template = manager.getInternalTemplate(templateName)
properties[PropertyKeys.PackageName] = requireNotNull(directory.getPackageName())
return FileTemplateUtil.createFromTemplate(
template,
"${fileName}.kt",
properties.toProperties(),
directory
) as PsiFile
) as PsiFile
} catch (e: Exception) {
throw IllegalStateException("Failed to generate file $fileName", e)
}
}

private fun PsiDirectory.getPackageName(): String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,53 @@ data class FeatureBreakdown(
}


fun actionsHandlers(baseName: String): String {
return """
${
actions.joinToString("\n") {
if (it.params.isEmpty()) {
"data object ${it.name.drop(2)} : ${baseName}Action"
} else {
"data class ${it.name.drop(2)}(${
it.params.joinToString(",\n") {
"val ${it.name}: ${it.type}"
}
}) : ${baseName}Action"
}
}
}
""".trimIndent()
}

fun coordinatorHandlers(baseName: String): String {
return """
fun handle(action: ${baseName}Action) {
when(action) {
${
actions.joinToString("\n") {
val imperativeHandler = if (it.type != Action.Type.Other) {
"viewModel.${it.imperativeName}(${it.params.joinToString(","){
"action.${it.name}"
}})"
} else {
"// handle ${it.imperativeName} action"
}
if (it.params.isEmpty()) {
"${baseName}Action.${it.name.drop(2)} -> {\n $imperativeHandler \n}"
} else {
"is ${baseName}Action.${it.name.drop(2)} -> {\n $imperativeHandler \n}"
}
}
}
}
}
""".trimIndent()
}


val viewModelActions = actions.filter { it.type != Action.Type.Other }.joinToString("\n") {
val params = it.namedParams
val param = it.params.firstOrNull()?.name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.models

enum class ActionsType {
Data, Sealed
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ data class FeatureConfiguration(
val useCollectFlowWithLifecycle: Boolean,
val usePreviewParameterProvider: Boolean,
val injection: InjectionConfiguration,
val navigationType: NavigationType
val navigationType: NavigationType,
val actionsType: ActionsType
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ data class FeatureProperties(
PropertyKeys.NAVIGATION_CLASS_SUFFIX to "Destination",
"VM_ACTIONS" to breakdown?.viewModelActions.orEmpty(),
"NAV_TYPE" to config.navigationType.name,
"ACTIONS_TYPE" to config.actionsType.name,
"COORDINATOR_HANDLERS" to breakdown?.coordinatorHandlers(name).orEmpty(),
"SEALED_ACTIONS" to breakdown?.actionsHandlers(name).orEmpty()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.repo

import com.levinzonr.arch.jetpackcompose.plugin.core.persistence.PreferencesDataSource
import com.levinzonr.arch.jetpackcompose.plugin.features.navigation.NavigationType
import com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.models.ActionsType
import com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.models.FeatureConfiguration
import com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.models.InjectionConfiguration

Expand All @@ -15,17 +16,20 @@ class FeatureConfigurationRepository(
put(KEY_FLOW_LIFECYCLE, features.useCollectFlowWithLifecycle)
put(KEY_PREVIEW_PARAMETER_PROVIDER, features.usePreviewParameterProvider)
put(KEY_NAVIGATION_TYPE, features.navigationType.name)
put(KEY_ACTIONS_TYPE, features.actionsType.name)
}
}

fun get() : FeatureConfiguration {
val injection = dataSource.get(KEY_INJECTION, InjectionConfiguration.Hilt.name)
val navigationType = dataSource.get(KEY_NAVIGATION_TYPE, NavigationType.Kiwi.name)
val actionsType = dataSource.get(KEY_ACTIONS_TYPE, ActionsType.Data.name)
return FeatureConfiguration(
useCollectFlowWithLifecycle = dataSource.get(KEY_FLOW_LIFECYCLE, true),
usePreviewParameterProvider = dataSource.get(KEY_PREVIEW_PARAMETER_PROVIDER, true),
injection = InjectionConfiguration.valueOf(injection),
navigationType = NavigationType.valueOf(navigationType)
navigationType = NavigationType.valueOf(navigationType),
actionsType = ActionsType.valueOf(actionsType)
)
}

Expand All @@ -38,5 +42,6 @@ class FeatureConfigurationRepository(
private const val KEY_PREVIEW_PARAMETER_PROVIDER = "use_preview_parameter_provider"
private const val KEY_INJECTION = "view_model_injection"
private const val KEY_NAVIGATION_TYPE = "navigation_type"
private const val KEY_ACTIONS_TYPE = "actions_type"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class AdvancedDialog(

override fun createPanel(): DialogPanel {
return panel {

row {
rowComment("Configure advanced settings for the feature" +
"settings will be persisted for the current project.")
}

group("State Collection") {
row {
checkBox("Use collectAsStateWithLifecycle")
Expand Down Expand Up @@ -59,6 +65,31 @@ class AdvancedDialog(
}
}

group("Actions Provider") {
buttonsGroup {

row {
radioButton("Data Class")
.bindSelected(viewModel::dataClassActionsSetter)
}

row {
comment("All actions will be generated as data classes (default)")
}

row {
radioButton("Sealed Interface")
.bindSelected(viewModel::sealedActionsSetter)
}

row {
comment("Actions will be generated as a sealed interface i.e LoginAction.UsernameChange")
}


}
}

group {
row {
text(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.ui.advanced

import com.levinzonr.arch.jetpackcompose.plugin.features.navigation.NavigationType
import com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.models.ActionsType
import com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.repository.FeatureConfigurationRepository
import com.levinzonr.arch.jetpackcompose.plugin.features.newfeature.domain.models.InjectionConfiguration

Expand Down Expand Up @@ -35,6 +36,12 @@ class AdvancedViewModel(
featureConfigurationRepository.put(featureConfigurationRepository.get().copy(navigationType = value))
}

private var actionsType: ActionsType
get() = featureConfigurationRepository.get().actionsType
set(value) {
featureConfigurationRepository.put(featureConfigurationRepository.get().copy(actionsType = value))
}


var kiwiSetter: Boolean
get() = navigationType == NavigationType.Kiwi
Expand Down Expand Up @@ -63,5 +70,15 @@ class AdvancedViewModel(
}


var sealedActionsSetter: Boolean
get() = actionsType == ActionsType.Sealed
set(value) {
if (value) actionsType = ActionsType.Sealed
}

var dataClassActionsSetter: Boolean
get() = actionsType == ActionsType.Data
set(value) {
if (value) actionsType = ActionsType.Data
}
}
11 changes: 11 additions & 0 deletions src/main/resources/fileTemplates/internal/ComposeContract.kt.ft
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,25 @@ object ${NAME}${NAVIGATION_CLASS_SUFFIX} : Destination
* ${NAME} Actions emitted from the UI Layer
* passed to the coordinator to handle
**/
#if (${ACTIONS_TYPE} == "Data")
data class ${NAME}Actions(
#if (${AI_USED} == true)
${ACTIONS}
#else
val onClick: () -> Unit = {}
#end
)
#end

#if (${ACTIONS_TYPE} == "Sealed")
sealed interface ${NAME}Action {
#if (${AI_USED} == true)
${SEALED_ACTIONS}
#else
data object OnClick : ${NAME}Action
#end
}
#end

#if (${USE_PREVIEW_PARAMETER_PROVIDER} == "true")
/**
Expand Down
21 changes: 15 additions & 6 deletions src/main/resources/fileTemplates/internal/ComposeCoordinator.kt.ft
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,23 @@ class ${NAME}Coordinator(
val viewModel: ${NAME}ViewModel
) {
val screenStateFlow = viewModel.stateFlow
#if (${ACTIONS_TYPE} == "Sealed" && ${AI_USED} == "false")
fun handle(action: ${NAME}Action) {
when (action) {
${NAME}Action.OnClick -> { /* Handle action */ }
}
}
#end

#if (${AI_USED} == "true")
${COORDINATOR_ACTIONS}
#else
fun doStuff() {
// TODO Handle UI Action
}
#if (${ACTIONS_TYPE} == "Sealed" && ${AI_USED} == "true")
${COORDINATOR_HANDLERS}
#end

#if (${AI_USED} == "true" && ${ACTIONS_TYPE} == "Data")
${COORDINATOR_ACTIONS}
#end


}

@Composable
Expand Down
20 changes: 19 additions & 1 deletion src/main/resources/fileTemplates/internal/ComposeRoute.kt.ft
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,30 @@ fun ${NAME}Route(
#end

// UI Actions
#if (${ACTIONS_TYPE} == "Data")
val actions = remember${NAME}Actions(coordinator)
#end
#if (${ACTIONS_TYPE} == "Sealed")
val actionsHandler: (${NAME}Action) -> Unit = { action ->
coordinator.handle(action)
}
#end

#if (${ACTIONS_TYPE} == "Data")
// UI Rendering
${NAME}Screen(uiState, actions)
#end
#if (${ACTIONS_TYPE} == "Sealed")
// UI Rendering
${NAME}Screen(
state = uiState,
onAction = actionsHandler
)
#end
}


#if (${ACTIONS_TYPE} == "Data")
@Composable
fun remember${NAME}Actions(coordinator: ${NAME}Coordinator): ${NAME}Actions {
return remember(coordinator) {
Expand All @@ -50,4 +67,5 @@ fun remember${NAME}Actions(coordinator: ${NAME}Coordinator): ${NAME}Actions {
)
#end
}
}
}
#end
14 changes: 13 additions & 1 deletion src/main/resources/fileTemplates/internal/ComposeScreen.kt.ft
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import androidx.compose.ui.tooling.preview.Preview
@Composable
fun ${NAME}Screen(
state: ${NAME}State,
actions: ${NAME}Actions,
#if (${ACTIONS_TYPE} == "Data")
actions: ${NAME}Actions
#else
onAction: (${NAME}Action) -> Unit
#end
) {
// TODO UI Rendering
}
Expand All @@ -22,7 +26,11 @@ private fun ${NAME}ScreenPreview(
) {
${NAME}Screen(
state = state,
#if (${ACTIONS_TYPE} == "Data")
actions = ${NAME}Actions()
#else
onAction = {}
#end
)
}
#else
Expand All @@ -31,7 +39,11 @@ private fun ${NAME}ScreenPreview(
private fun ${NAME}ScreenPreview() {
${NAME}Screen(
state = ${NAME}State(),
#if (${ACTIONS_TYPE} == "Data")
actions = ${NAME}Actions()
#else
onAction = {}
#end
)
}
#end

0 comments on commit f1826d3

Please sign in to comment.