diff --git a/app/src/main/java/com/itsaky/androidide/actions/filetree/NewFileAction.kt b/app/src/main/java/com/itsaky/androidide/actions/filetree/NewFileAction.kt index 8591d00a6c..28435aad93 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/filetree/NewFileAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/filetree/NewFileAction.kt @@ -35,6 +35,7 @@ import com.itsaky.androidide.projects.IProjectManager import com.itsaky.androidide.projects.ProjectManagerImpl import com.itsaky.androidide.resources.R import com.itsaky.androidide.templates.base.models.Dependency +import com.itsaky.androidide.utils.ClassBuilder.SourceLanguage import com.itsaky.androidide.utils.DialogUtils import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.ProjectWriter @@ -174,13 +175,13 @@ class NewFileAction(val context: Context, override val order: Int) : } ) builder.setView(binding.root) - builder.setTitle(R.string.new_java_class) + builder.setTitle(R.string.new_file) builder.setPositiveButton(R.string.text_create) { dialogInterface, _ -> dialogInterface.dismiss() try { - doCreateJavaFile(binding, file, context, node) + doCreateSourceFile(binding, file, context, node) } catch (e: Exception) { - log.error("Failed to create Java file", e) + log.error("Failed to create source file", e) flashError(e.cause?.message ?: e.message) } } @@ -189,7 +190,6 @@ class NewFileAction(val context: Context, override val order: Int) : .showWithLongPressTooltip( context = context, tooltipTag = TooltipTag.PROJECT_FOLDER_NEWTYPE, - // Pass the specific buttons to the helper function binding.typeClass, binding.typeActivity, binding.typeInterface, @@ -197,7 +197,7 @@ class NewFileAction(val context: Context, override val order: Int) : ) } - private fun doCreateJavaFile( + private fun doCreateSourceFile( binding: LayoutCreateFileJavaBinding, file: File, context: Context, @@ -214,78 +214,79 @@ class NewFileAction(val context: Context, override val order: Int) : return } + val isKotlin = binding.languageGroup.checkedButtonId == binding.langKotlin.id + val language = if (isKotlin) SourceLanguage.KOTLIN else SourceLanguage.JAVA + val extension = if (isKotlin) ".kt" else ".java" + val autoLayout = binding.typeGroup.checkedButtonId == binding.typeActivity.id && binding.createLayout.isChecked val pkgName = ProjectWriter.getPackageName(file) val id: Int = binding.typeGroup.checkedButtonId - val javaName = if (name.endsWith(".java")) name else "$name.java" + val fileName = if (name.endsWith(extension)) name else "$name$extension" val className = if (!name.contains(".")) name else name.substring(0, name.lastIndexOf(".")) - // Package Structure Check: When the package is "com", it checks if a com subdirectory exists within the current directory. - // If com/ exists, it means the package structure is organized correctly. - // The Java file should be created within com/ to maintain package consistency. - // If the com subdirectory does not exist, use the original directory (file), avoiding FileNotFoundException errors. - val javaFileDirectory = if (pkgName == "com") { + val sourceFileDirectory = if (pkgName == "com") { val subDir = File(file, "com") if (subDir.exists() && subDir.isDirectory) subDir else file } else { file } - when (id) { - binding.typeClass.id -> - createFile( - node, - javaFileDirectory, - javaName, - ProjectWriter.createJavaClass(pkgName, className), - ) + when (id) { + binding.typeClass.id -> + createFile( + node, + sourceFileDirectory, + fileName, + ProjectWriter.createClass(pkgName, className, language), + ) - binding.typeInterface.id -> - createFile( - node, - javaFileDirectory, - javaName, - ProjectWriter.createJavaInterface(pkgName, className) - ) + binding.typeInterface.id -> + createFile( + node, + sourceFileDirectory, + fileName, + ProjectWriter.createInterface(pkgName, className, language) + ) - binding.typeEnum.id -> - createFile( - node, - javaFileDirectory, - javaName, - ProjectWriter.createJavaEnum(pkgName, className) - ) + binding.typeEnum.id -> + createFile( + node, + sourceFileDirectory, + fileName, + ProjectWriter.createEnum(pkgName, className, language) + ) - binding.typeActivity.id -> { - val appCompat = Dependency.AndroidX.AppCompat - val projectManager = ProjectManagerImpl.getInstance() - val hasAppCompatDependency = projectManager.findModuleForFile(file) - ?.hasExternalDependency(appCompat.group, appCompat.artifact) - createFile( - node, - javaFileDirectory, - javaName, - ProjectWriter.createActivity( - pkgName, - className, - hasAppCompatDependency ?: false - ) - ) - } - - else -> createFile(node, javaFileDirectory, name, "") + binding.typeActivity.id -> { + val appCompat = Dependency.AndroidX.AppCompat + val projectManager = ProjectManagerImpl.getInstance() + val hasAppCompatDependency = projectManager.findModuleForFile(file) + ?.hasExternalDependency(appCompat.group, appCompat.artifact) + createFile( + node, + sourceFileDirectory, + fileName, + ProjectWriter.createActivity( + pkgName, + className, + hasAppCompatDependency ?: false, + language + ) + ) } + else -> createFile(node, sourceFileDirectory, name, "") + } + node?.let { requestCollapseNode(it, true) } if (autoLayout) { val packagePath = pkgName.toString().replace(".", "/") - createAutoLayout(context, javaFileDirectory, name, packagePath) + createAutoLayout(context, sourceFileDirectory, name, packagePath, isKotlin) } } @@ -306,10 +307,12 @@ class NewFileAction(val context: Context, override val order: Int) : context: Context, directory: File, fileName: String, - packagePath: String + packagePath: String, + isKotlin: Boolean = false ) { val dir = directory.toString().replace("java/$packagePath", "res/layout/") - val layoutName = ProjectWriter.createLayoutName(fileName.replace(".java", ".xml")) + val sourceExtension = if (isKotlin) ".kt" else ".java" + val layoutName = ProjectWriter.createLayoutName(fileName.replace(sourceExtension, ".xml")) val newFileLayout = File(dir, layoutName) if (newFileLayout.exists()) { flashError(R.string.msg_layout_file_exists) diff --git a/app/src/main/java/com/itsaky/androidide/utils/ClassBuilder.kt b/app/src/main/java/com/itsaky/androidide/utils/ClassBuilder.kt index 8895eda1ca..bf569dc5a5 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/ClassBuilder.kt +++ b/app/src/main/java/com/itsaky/androidide/utils/ClassBuilder.kt @@ -21,6 +21,8 @@ import android.app.Activity import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.itsaky.androidide.preferences.utils.indentationString +import com.itsaky.androidide.utils.ClassBuilder.SourceLanguage.JAVA +import com.itsaky.androidide.utils.ClassBuilder.SourceLanguage.KOTLIN import com.squareup.javapoet.JavaFile import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec @@ -28,9 +30,15 @@ import jdkx.lang.model.element.Modifier.PROTECTED import jdkx.lang.model.element.Modifier.PUBLIC object ClassBuilder { + + enum class SourceLanguage { JAVA, KOTLIN } + @JvmStatic - fun createClass(packageName: String, className: String): String { - return toJavaFile(packageName, newClassSpec(className)).toString() + fun createClass(packageName: String, className: String, language: SourceLanguage = JAVA): String { + return when (language) { + JAVA -> toJavaFile(packageName, newClassSpec(className)).toString() + KOTLIN -> buildKotlinFile(packageName) { appendLine("class $className {"); appendLine("}") } + } } private fun toJavaFile( @@ -49,8 +57,11 @@ object ClassBuilder { } @JvmStatic - fun createInterface(packageName: String, className: String): String { - return toJavaFile(packageName, newInterfaceSpec(className)).toString() + fun createInterface(packageName: String, className: String, language: SourceLanguage = JAVA): String { + return when (language) { + JAVA -> toJavaFile(packageName, newInterfaceSpec(className)).toString() + KOTLIN -> buildKotlinFile(packageName) { appendLine("interface $className {"); appendLine("}") } + } } private fun newInterfaceSpec(className: String): TypeSpec { @@ -58,8 +69,15 @@ object ClassBuilder { } @JvmStatic - fun createEnum(packageName: String, className: String): String { - return toJavaFile(packageName, newEnumSpec(className)).toString() + fun createEnum(packageName: String, className: String, language: SourceLanguage = JAVA): String { + return when (language) { + JAVA -> toJavaFile(packageName, newEnumSpec(className)).toString() + KOTLIN -> buildKotlinFile(packageName) { + appendLine("enum class $className {") + appendLine("${indentationString}ENUM_DECLARED") + appendLine("}") + } + } } private fun newEnumSpec(className: String): TypeSpec { @@ -71,10 +89,18 @@ object ClassBuilder { @JvmStatic fun createActivity( - packageName: String, - className: String, - appCompatActivity: Boolean = true, + packageName: String, + className: String, + appCompatActivity: Boolean = true, + language: SourceLanguage = JAVA ): String { + return when (language) { + JAVA -> createJavaActivity(packageName, className, appCompatActivity) + KOTLIN -> createKotlinActivity(packageName, className, appCompatActivity) + } + } + + private fun createJavaActivity(packageName: String, className: String, appCompatActivity: Boolean): String { val onCreate = MethodSpec.methodBuilder("onCreate") .addAnnotation(Override::class.java) @@ -89,4 +115,30 @@ object ClassBuilder { .addMethod(onCreate) return toJavaFile(packageName, activity.build()) { skipJavaLangImports(true) }.toString() } + + private fun createKotlinActivity(packageName: String, className: String, appCompatActivity: Boolean): String { + val superClass = if (appCompatActivity) "AppCompatActivity" else "Activity" + val import = if (appCompatActivity) "androidx.appcompat.app.AppCompatActivity" else "android.app.Activity" + return buildString { + if (packageName.isNotEmpty()) appendLine("package $packageName") + appendLine() + appendLine("import android.os.Bundle") + appendLine("import $import") + appendLine() + appendLine("class $className : $superClass() {") + appendLine() + appendLine("${indentationString}override fun onCreate(savedInstanceState: Bundle?) {") + appendLine("${indentationString}${indentationString}super.onCreate(savedInstanceState)") + appendLine("$indentationString}") + appendLine("}") + } + } + + private fun buildKotlinFile(packageName: String, block: StringBuilder.() -> Unit): String { + return buildString { + if (packageName.isNotEmpty()) appendLine("package $packageName") + appendLine() + block() + } + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java index 5306848a04..df1357d2fc 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java +++ b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java @@ -22,6 +22,7 @@ import androidx.annotation.NonNull; import com.blankj.utilcode.util.ResourceUtils; +import com.itsaky.androidide.utils.ClassBuilder.SourceLanguage; import java.io.File; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -121,19 +122,19 @@ private static boolean isValidPackageName(String name) { return name.matches("^[a-zA-Z_][a-zA-Z0-9_]*$"); } - public static String createJavaClass(String packageName, String className) { - return ClassBuilder.createClass(packageName, className); + public static String createClass(String packageName, String className, SourceLanguage language) { + return ClassBuilder.createClass(packageName, className, language); } - public static String createJavaInterface(String packageName, String className) { - return ClassBuilder.createInterface(packageName, className); + public static String createInterface(String packageName, String className, SourceLanguage language) { + return ClassBuilder.createInterface(packageName, className, language); } - public static String createJavaEnum(String packageName, String className) { - return ClassBuilder.createEnum(packageName, className); + public static String createEnum(String packageName, String className, SourceLanguage language) { + return ClassBuilder.createEnum(packageName, className, language); } - public static String createActivity(String packageName, String className, boolean appCompatActivity) { - return ClassBuilder.createActivity(packageName, className, appCompatActivity); + public static String createActivity(String packageName, String className, boolean appCompatActivity, SourceLanguage language) { + return ClassBuilder.createActivity(packageName, className, appCompatActivity, language); } } diff --git a/app/src/main/res/layout/layout_create_file_java.xml b/app/src/main/res/layout/layout_create_file_java.xml index 3ecd0c0c6e..15b4cd9c18 100755 --- a/app/src/main/res/layout/layout_create_file_java.xml +++ b/app/src/main/res/layout/layout_create_file_java.xml @@ -8,6 +8,35 @@ android:paddingTop="16dp" android:paddingBottom="16dp"> + + + + + + + + Activity Interface Enum + Java + Kotlin + New Kotlin class Failed to list project files