Skip to content
Open
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,32 @@
## [Unreleased]

### Added

- Added support for Android Studio Otter.

## [2025.2.1] - 2025-11-15

### Added

- Support for clean_framework v4.
- Added support for Android Studio Koala.

## [2024.1.1] - 2023-12-28

### Added

- Added support for Android Studio Iguana.
- Added default `copyWith` override to generated Entity class.
- Added test file generators at feature generation.

## [2023.2.23] - 2023-02-23

### Added

- Added clean_framework package at project generation

## [2023.2.22] - 2023-02-22

### Added

- Initial release
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ Support for developing Flutter project with Clean Framework package. The plugin
- Gateway Generator
- Feature Directory Generator; also generates corresponding use case provider for the feature<

#### Compatibility
- Built against IntelliJ Platform build 252.* (IntelliJ IDEA / Android Studio 2025.2.1).
- Depends on the bundled Dart plugin and resolves the most recent Flutter plugin compatible with that build.

The Clean Framework package can be found at [https://pub.dev/packages/clean_framework](https://pub.dev/packages/clean_framework).
<!-- Plugin description end -->
<!-- Plugin description end -->
10 changes: 9 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.markdownToHTML
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

fun properties(key: String) = project.findProperty(key).toString()
fun environment(key: String) = providers.environmentVariable(key)
Expand Down Expand Up @@ -28,7 +29,8 @@ kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(17)
vendor = JvmVendorSpec.JETBRAINS
}}
}
}

intellij {
pluginName = properties("pluginName")
Expand Down Expand Up @@ -86,4 +88,10 @@ tasks {
dependsOn("patchChangelog")
token = environment("PUBLISH_TOKEN")
}

withType<KotlinCompile>().configureEach {
compilerOptions {
freeCompilerArgs.add("-Xskip-metadata-version-check")
}
}
}
10 changes: 5 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
pluginName = Clean Framework
pluginVersion = 2024.5.1
pluginVersion = 2025.2.1
pluginGroup = com.acmesoftware
pluginRepositoryUrl = https://github.com/AcmeSoftwareLLC/clean-framework-intellij

pluginSinceBuild = 223
pluginUntilBuild = 241.*
pluginSinceBuild = 252
pluginUntilBuild = 252.*

platformType = IC
platformVersion = 2022.3.3
platformVersion = 2025.2.1

platformPlugins = java, Kotlin, io.flutter:77.1.1, Dart:223.8977
platformPlugins = com.intellij.java, Dart:253.28294.51, io.flutter:88.1.0

gradleVersion = 8.6

Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
annotations = "24.1.0"

# plugins
kotlin = "1.9.23"
kotlin = "2.0.20"
changelog = "2.2.0"
gradleIntelliJPlugin = "1.17.2"
gradleIntelliJPlugin = "1.17.3"

[libraries]
annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,39 @@ import com.intellij.util.IncorrectOperationException
import com.jetbrains.lang.dart.DartLanguage
import com.jetbrains.lang.dart.util.PubspecYamlUtil

class NewFeatureAction : AnAction(), GenerateFeatureDialog.Callback {
private lateinit var dataContext: DataContext
class NewFeatureAction : AnAction() {

override fun actionPerformed(e: AnActionEvent) {
val dialog = GenerateFeatureDialog(this)
val context = e.dataContext
val dialog = GenerateFeatureDialog { featureName ->
generateFeature(featureName, context)
}
dialog.show()
}

override fun update(e: AnActionEvent) {
e.dataContext.let {
this.dataContext = it
val presentation = e.presentation
presentation.isEnabled = true
}
e.presentation.isEnabled = true
}

override fun onGenerateFeature(featureName: String) {
private fun generateFeature(featureName: String, dataContext: DataContext) {
val application = ApplicationManager.getApplication()
val project = CommonDataKeys.PROJECT.getData(dataContext)
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val view = LangDataKeys.IDE_VIEW.getData(dataContext)

val focusedVirtualFile = if (view == null) {
val editor = LangDataKeys.EDITOR.getData(dataContext)!!
FileDocumentManager.getInstance().getFile(editor.document)!!.parent
val editor = LangDataKeys.EDITOR.getData(dataContext) ?: return
FileDocumentManager.getInstance().getFile(editor.document)?.parent ?: return
} else {
view.orChooseDirectory!!.virtualFile
view.orChooseDirectory?.virtualFile ?: return
}

val pubspecFile = PubspecYamlUtil.findPubspecYamlFile(project!!, focusedVirtualFile)
val libDir = pubspecFile!!.parent.findChild(PubspecYamlUtil.LIB_DIR_NAME)!!
val packageName = PubspecYamlUtil.getDartProjectName(pubspecFile)!!
val pubspecFile = PubspecYamlUtil.findPubspecYamlFile(project, focusedVirtualFile) ?: return
val libDir = pubspecFile.parent.findChild(PubspecYamlUtil.LIB_DIR_NAME) ?: return
val packageName = PubspecYamlUtil.getDartProjectName(pubspecFile) ?: return

val libDirectory = PsiDirectoryFactory.getInstance(project).createDirectory(libDir)
val featuresDirectory = libDirectory.findSubdirectory("features")!!
val featuresDirectory = libDirectory.findSubdirectory("features")
?: libDirectory.createSubdirectory("features")

application.runWriteAction {
val runnable = Runnable {
Expand All @@ -69,7 +68,7 @@ class NewFeatureAction : AnAction(), GenerateFeatureDialog.Callback {
FeatureGeneratorFactory.getGenerators(packageName, featureName)
)

val root = libDirectory.parent!!
val root = libDirectory.parent ?: libDirectory
val testDirectory = findOrCreateDirectory(root, "test")
val featuresTestDirectory = findOrCreateDirectory(testDirectory, "features")

Expand Down Expand Up @@ -180,4 +179,4 @@ class NewFeatureAction : AnAction(), GenerateFeatureDialog.Callback {
private fun findOrCreateDirectory(parent: PsiDirectory, directoryName: String): PsiDirectory {
return parent.findSubdirectory(directoryName) ?: return parent.createSubdirectory(directoryName)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,30 @@ import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.fileTypes.PlainTextLanguage
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.*
import com.intellij.psi.impl.file.PsiDirectoryFactory
import com.jetbrains.lang.dart.DartLanguage
import com.jetbrains.lang.dart.util.PubspecYamlUtil
import org.jetbrains.kotlin.idea.core.util.toPsiFile
import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.yaml.snakeyaml.Yaml


class SetupProjectAction : AnAction() {
private lateinit var dataContext: DataContext

@OptIn(UnsafeCastFunction::class)
override fun actionPerformed(e: AnActionEvent) {
val application = ApplicationManager.getApplication()
val project = CommonDataKeys.PROJECT.getData(dataContext)
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val view = LangDataKeys.IDE_VIEW.getData(dataContext)

val focusedVirtualFile = if (view == null) {
val editor = LangDataKeys.EDITOR.getData(dataContext)!!
FileDocumentManager.getInstance().getFile(editor.document)!!.parent
val editor = LangDataKeys.EDITOR.getData(dataContext) ?: return
FileDocumentManager.getInstance().getFile(editor.document)?.parent ?: return
} else {
view.orChooseDirectory!!.virtualFile
view.orChooseDirectory?.virtualFile ?: return
}

val pubspecFile = PubspecYamlUtil.findPubspecYamlFile(project!!, focusedVirtualFile)
val libDir = pubspecFile!!.parent.findChild(PubspecYamlUtil.LIB_DIR_NAME)!!
val pubspecFile = PubspecYamlUtil.findPubspecYamlFile(project, focusedVirtualFile) ?: return
val libDir = pubspecFile.parent.findChild(PubspecYamlUtil.LIB_DIR_NAME)!!
val packageName = PubspecYamlUtil.getDartProjectName(pubspecFile)!!
val packageNamePascal = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, packageName)

Expand Down Expand Up @@ -105,12 +99,17 @@ class SetupProjectAction : AnAction() {

findOrCreateDirectory(libDirectory, "features")

pubspecFile.toPsiFile(project)?.also {
PsiManager.getInstance(project).findFile(pubspecFile)?.also {
val pubspecYamlBuilder = StringBuilder(it.text)

val yamlMap = Yaml().load<HashMap<String, Any>>(pubspecFile.inputStream)
val dependencies = yamlMap[PubspecYamlUtil.DEPENDENCIES].cast<HashMap<String, Any>>().keys
val devDependencies = yamlMap[PubspecYamlUtil.DEV_DEPENDENCIES].cast<HashMap<String, Any>>().keys
val yamlMap = pubspecFile.inputStream.use { stream ->
@Suppress("UNCHECKED_CAST")
(Yaml().load<Map<String, Any?>>(stream) ?: emptyMap())
}
val dependencies = (yamlMap[PubspecYamlUtil.DEPENDENCIES] as? Map<*, *>)?.keys
?.filterIsInstance<String>()?.toSet() ?: emptySet()
val devDependencies = (yamlMap[PubspecYamlUtil.DEV_DEPENDENCIES] as? Map<*, *>)?.keys
?.filterIsInstance<String>()?.toSet() ?: emptySet()

listOf("clean_framework_router", "clean_framework").forEach { dep ->
if (!dependencies.contains(dep)) insertDependency(pubspecYamlBuilder, dep)
Expand Down Expand Up @@ -191,4 +190,4 @@ class SetupProjectAction : AnAction() {
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.JTextField

class GenerateFeatureDialog(private val callback: Callback) : DialogWrapper(true) {
class GenerateFeatureDialog(private val onGenerate: (String) -> Unit) : DialogWrapper(true) {
private lateinit var featureNameTextField: JTextField
private lateinit var errorLabel: JLabel

Expand Down Expand Up @@ -53,14 +53,10 @@ class GenerateFeatureDialog(private val callback: Callback) : DialogWrapper(true
if (featureNameRegex.matches(featureName)) {
super.doOKAction()

callback.onGenerateFeature(featureName)
onGenerate(featureName)
} else {
errorLabel.text = "Feature name should be in PascalCase"
errorLabel.foreground = JBColor(DarculaColors.RED, DarculaColors.RED)
}
}

interface Callback {
fun onGenerateFeature(featureName: String)
}
}
}