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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
Expand All @@ -189,15 +190,14 @@ 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,
binding.typeEnum
)
}

private fun doCreateJavaFile(
private fun doCreateSourceFile(
binding: LayoutCreateFileJavaBinding,
file: File,
context: Context,
Expand All @@ -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)
}
}

Expand All @@ -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)
Expand Down
70 changes: 61 additions & 9 deletions app/src/main/java/com/itsaky/androidide/utils/ClassBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,24 @@ 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
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(
Expand All @@ -49,17 +57,27 @@ 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 {
return TypeSpec.interfaceBuilder(className).addModifiers(PUBLIC).build()
}

@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 {
Expand All @@ -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)
Expand All @@ -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()
}
}
}
17 changes: 9 additions & 8 deletions app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
29 changes: 29 additions & 0 deletions app/src/main/res/layout/layout_create_file_java.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,35 @@
android:paddingTop="16dp"
android:paddingBottom="16dp">

<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/languageGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp"
app:checkedButton="@id/lang_java"
app:selectionRequired="true"
app:singleSelection="true">

<com.google.android.material.button.MaterialButton
android:id="@+id/lang_java"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lang_java"
app:icon="@drawable/ic_language_java" />

<com.google.android.material.button.MaterialButton
android:id="@+id/lang_kotlin"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lang_kotlin"
app:icon="@drawable/ic_language_kotlin" />

</com.google.android.material.button.MaterialButtonToggleGroup>

<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
3 changes: 3 additions & 0 deletions resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@
<string name="classtype_activity">Activity</string>
<string name="title_interface">Interface</string>
<string name="classtype_enum">Enum</string>
<string name="lang_java">Java</string>
<string name="lang_kotlin">Kotlin</string>
<string name="new_kotlin_class">New Kotlin class</string>
Comment thread
Daniel-ADFA marked this conversation as resolved.
<string name="msg_failed_list_files">Failed to list project files</string>

<!-- Code Editor -->
Expand Down