Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
52eccbe
next gen template initial version
jomen-adfa Mar 12, 2026
5a5f8e1
indent issue
jomen-adfa Mar 12, 2026
02f97ba
update minsdk reference
jomen-adfa Mar 13, 2026
14731f2
template archive installation
jomen-adfa Mar 13, 2026
1190045
cleanup of widget references and zip issues
jomen-adfa Mar 13, 2026
5b39329
cleanup imports
jomen-adfa Mar 13, 2026
abae382
take out null checks
jomen-adfa Mar 14, 2026
dafe715
restore leak canary
jomen-adfa Mar 14, 2026
ab0c3e0
set core.cgt.br expected size
jomen-adfa Mar 14, 2026
db56054
prevent zip slip
jomen-adfa Mar 15, 2026
1f10d6c
take out unused import
jomen-adfa Mar 15, 2026
849efc4
initial add widgets
jomen-adfa Mar 15, 2026
ec4417f
user defined parameters
jomen-adfa Mar 16, 2026
63a428d
unused imports
jomen-adfa Mar 16, 2026
3c89bd7
modify log entry
jomen-adfa Mar 18, 2026
02ddbaa
exclude basepath i.e. template root folder in generated project
jomen-adfa Mar 18, 2026
001241e
read tooltipTag from template.json
jomen-adfa Mar 19, 2026
09f3d31
uncomment lib entry for fragment-ktx and take out comments
jomen-adfa Mar 19, 2026
69bd3c1
use File.separator instead of declaring a constant ZIP_SEPARATOR and …
jomen-adfa Mar 20, 2026
862adcf
readable warnings log
jomen-adfa Mar 20, 2026
41812d8
proguard rules to prevent obfuscation of gson used classes
jomen-adfa Mar 20, 2026
80fe0df
clean out extraneous debugging lines
jomen-adfa Mar 20, 2026
9cb3fed
remove unnecessary log object
jomen-adfa Mar 20, 2026
e2725fd
formatting
jomen-adfa Mar 20, 2026
69695e4
formatting
jomen-adfa Mar 20, 2026
60e9aaf
Merge branch 'stage' into feat/ADFA-2737-template-initial
jomen-adfa Mar 20, 2026
c8205d1
Merge branch 'stage' into feat/ADFA-2737-template-initial
jomen-adfa Mar 22, 2026
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
13 changes: 13 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ fun createAssetsZip(arch: String) {
"documentation.db",
bootstrapName,
"plugin-artifacts.zip",
"core.cgt"
).forEach { fileName ->
val filePath = sourceDir.resolve(fileName)
if (!filePath.exists()) {
Expand Down Expand Up @@ -1059,6 +1060,12 @@ val debugAssets =
"localMvnRepository.zip",
"debug",
),
Asset(
"assets/core.cgt",
"https://appdevforall.org/dev-assets/debug/core.cgt",
"core.cgt",
"debug",
),
)

val releaseAssets =
Expand Down Expand Up @@ -1111,6 +1118,12 @@ val releaseAssets =
"v8/bootstrap.zip.br",
"release",
),
Asset(
"assets/release/common/data/common/core.cgt.br",
"https://appdevforall.org/dev-assets/release/core.cgt.br",
"core.cgt.br",
"release",
),
)

fun assetsBatch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.blankj.utilcode.util.ConvertUtils
import com.bumptech.glide.Glide
import com.google.android.material.shape.CornerFamily
import com.itsaky.androidide.adapters.TemplateListAdapter.ViewHolder
import com.itsaky.androidide.databinding.LayoutTemplateListItemBinding
Expand All @@ -37,6 +38,7 @@ class TemplateListAdapter(
private val onClick: ((Template<*>, ViewHolder) -> Unit)? = null,
private val onLongClick: ((Template<*>, View) -> Unit)? = null,
) : RecyclerView.Adapter<ViewHolder>() {

private val templates = templates.toMutableList()

class ViewHolder(
Expand All @@ -61,14 +63,24 @@ class TemplateListAdapter(
holder: ViewHolder,
position: Int,
) {
holder.binding.apply {
val template = templates[position]
if (template == Template.EMPTY) {

holder.binding.apply {
val template = templates[position]
if (template == Template.EMPTY) {
root.visibility = View.INVISIBLE
return@apply
}
templateName.text = templateName.context.getString(template.templateName)
templateIcon.setImageResource(template.thumb)

templateName.text = template.templateNameStr
if (template.thumbData != null) {
Glide.with(templateIcon.context)
.asBitmap()
.load(template.thumbData)
.into(templateIcon)
} else {
templateIcon.setImageResource(template.thumb)
}

templateIcon.shapeAppearanceModel =
templateIcon.shapeAppearanceModel
.toBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.adfa.constants.DOCUMENTATION_DB
import org.adfa.constants.GRADLE_API_NAME_JAR_ZIP
import org.adfa.constants.GRADLE_DISTRIBUTION_ARCHIVE_NAME
import org.adfa.constants.LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME
import org.adfa.constants.TEMPLATE_CORE_ARCHIVE
import org.slf4j.LoggerFactory
import com.itsaky.androidide.resources.R
import java.io.File
Expand Down Expand Up @@ -113,7 +114,8 @@ object AssetsInstallationHelper {
BOOTSTRAP_ENTRY_NAME,
GRADLE_API_NAME_JAR_ZIP,
LLAMA_AAR,
PLUGIN_ARTIFACTS_ZIP
PLUGIN_ARTIFACTS_ZIP,
TEMPLATE_CORE_ARCHIVE,
)

val stagingDir = Files.createTempDirectory(UUID.randomUUID().toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import org.adfa.constants.GRADLE_API_NAME_JAR_BR
import org.adfa.constants.GRADLE_API_NAME_JAR_ZIP
import org.adfa.constants.GRADLE_DISTRIBUTION_ARCHIVE_NAME
import org.adfa.constants.LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME
import org.adfa.constants.TEMPLATE_CORE_ARCHIVE
import org.adfa.constants.TEMPLATE_CORE_ARCHIVE_BR
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileNotFoundException
Expand Down Expand Up @@ -78,26 +80,36 @@ data object BundledAssetsInstaller : BaseAssetsInstaller() {
}
}

AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> {
TEMPLATE_CORE_ARCHIVE -> {
val assetPath = ToolsManager.getCommonAsset(TEMPLATE_CORE_ARCHIVE_BR)
BrotliInputStream(assets.open(assetPath)).use { input ->
val destFile = Environment.TEMPLATES_DIR.resolve(TEMPLATE_CORE_ARCHIVE)
destFile.outputStream().use { output ->
input.copyTo(output)
}
}
}

AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> {
val assetPath =
ToolsManager.getCommonAsset("${AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME}.br")

val result = retryOnceOnNoSuchFile (
onFirstFailure = { Files.createDirectories(stagingDir) },
onSecondFailure = { e2 ->
throw IOException(
context.getString(R.string.terminal_installation_failed_low_storage),
e2
)
}
) {
withTempZipChannel(
stagingDir = stagingDir,
prefix = "bootstrap",
writeTo = { path -> writeBrotliAssetToPath(context, assetPath, path) },
useChannel = { ch -> TerminalInstaller.installIfNeeded(context, ch) }
)
}
throw IOException(
context.getString(R.string.terminal_installation_failed_low_storage),
e2
)
}
) {
withTempZipChannel(
stagingDir = stagingDir,
prefix = "bootstrap",
writeTo = { path -> writeBrotliAssetToPath(context, assetPath, path) },
useChannel = { ch -> TerminalInstaller.installIfNeeded(context, ch) }
)
}

when (result) {
is TerminalInstaller.InstallResult.Success -> {}
Expand Down Expand Up @@ -217,6 +229,7 @@ data object BundledAssetsInstaller : BaseAssetsInstaller() {
AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> 124120151L
GRADLE_API_NAME_JAR_ZIP -> 29447748L
AssetsInstallationHelper.PLUGIN_ARTIFACTS_ZIP -> 86442L
TEMPLATE_CORE_ARCHIVE -> 133120L
else -> 0L
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.adfa.constants.DOCUMENTATION_DB
import org.adfa.constants.GRADLE_API_NAME_JAR_ZIP
import org.adfa.constants.GRADLE_DISTRIBUTION_ARCHIVE_NAME
import org.adfa.constants.LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME
import org.adfa.constants.TEMPLATE_CORE_ARCHIVE
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileNotFoundException
Expand Down Expand Up @@ -72,6 +73,13 @@ data object SplitAssetsInstaller : BaseAssetsInstaller() {
logger.debug("Completed extracting '{}' to dir: {}", entry.name, destDir)
}

TEMPLATE_CORE_ARCHIVE -> {
val coreCgt = Environment.TEMPLATES_DIR.resolve(TEMPLATE_CORE_ARCHIVE)
coreCgt.outputStream().use { output ->
zipInput.copyTo(output)
}
}

AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> {
logger.debug("Extracting 'bootstrap.zip' to dir: {}", stagingDir)

Expand Down Expand Up @@ -194,6 +202,7 @@ data object SplitAssetsInstaller : BaseAssetsInstaller() {
LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME -> 215389106L
AssetsInstallationHelper.BOOTSTRAP_ENTRY_NAME -> 456462823L
GRADLE_API_NAME_JAR_ZIP -> 46758608L
TEMPLATE_CORE_ARCHIVE -> 702001L
else -> 0L
}

Expand All @@ -203,6 +212,7 @@ data object SplitAssetsInstaller : BaseAssetsInstaller() {
ANDROID_SDK_ZIP -> Environment.ANDROID_HOME
LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME -> Environment.LOCAL_MAVEN_DIR
GRADLE_API_NAME_JAR_ZIP -> Environment.GRADLE_GEN_JARS
TEMPLATE_CORE_ARCHIVE -> Environment.TEMPLATES_DIR
else -> throw IllegalStateException("Entry '$entryName' is not expected to be an archive")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ class TemplateDetailsFragment :
name = result.data.name,
createdAt = now,
lastModified = now,
templateName = getString(template.templateName),
language = result.data.language.name
templateName = template.templateNameStr,
language = result.data.language?.name ?: "unknown"
)
)

Expand All @@ -163,6 +163,6 @@ class TemplateDetailsFragment :
template ?: return

binding.widgets.adapter = TemplateWidgetsListAdapter(template.widgets)
binding.title.setText(template.templateName)
binding.title.text = template.templateNameStr
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public final class Environment {
public static final String NDK_TAR_XZ = "ndk-cmake.tar.xz";
public static File NDK_DIR;

public static File TEMPLATES_DIR;

public static String getArchitecture() {
return IDEBuildConfigProvider.getInstance().getCpuAbiName();
}
Expand Down Expand Up @@ -188,6 +190,8 @@ public static void init(Context context) {

NDK_DIR = new File(ANDROID_HOME, "ndk");

TEMPLATES_DIR = mkdirIfNotExists(new File(ANDROIDIDE_HOME, "templates"));

isInitialized.set(true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,8 @@ const val LOGSENDER_AAR_NAME = "logsender.aar"
const val GRADLE_API_NAME_JAR = "gradle-api-$GRADLE_DISTRIBUTION_VERSION.jar"
const val GRADLE_API_NAME_JAR_ZIP = "${GRADLE_API_NAME_JAR}.zip"
const val GRADLE_API_NAME_JAR_BR = "${GRADLE_API_NAME_JAR}.br"

// Templates archive
const val TEMPLATE_ARCHIVE_EXTENSION = "cgt"
const val TEMPLATE_CORE_ARCHIVE = "core.$TEMPLATE_ARCHIVE_EXTENSION"
const val TEMPLATE_CORE_ARCHIVE_BR = "${TEMPLATE_CORE_ARCHIVE}.br"
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ compose-compiler = "2.1.21"

leakcanary = "2.14"

pebble = "4.1.1"


[libraries]

# Dependencies in composite build
Expand Down Expand Up @@ -307,6 +310,8 @@ androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "m
monitor = { group = "androidx.test", name = "monitor", version.ref = "monitorVersion" }
org-json = { module = "org.json:json", version = "20210307"}

pebble = { module = "io.pebbletemplates:pebble", version.ref = "pebble" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
Expand Down
2 changes: 2 additions & 0 deletions templates-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ dependencies {
api(libs.androidx.annotation)
api(libs.androidx.appcompat)
api(libs.google.material)

implementation(libs.google.gson)
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,7 @@ class ProjectTemplateBuilder : ExecutorDataTemplateBuilder<ProjectTemplateRecipe
tooltipTag,
widgets!!,
recipe!!,
templateNameStr!!,
thumbData!!
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,103 @@ inline fun <R : FileTemplateRecipeResult> baseFile(
return FileTemplateBuilder<R>(dir).apply(configurator)
.build() as FileTemplate<R>
}

/**
* Setup base files for zip project templates.
*
* @param block Function to configure the template.
*/
inline fun baseZipProject(
projectName: StringParameter = projectNameParameter(),
packageName: StringParameter = packageNameParameter(),
useKts: BooleanParameter = useKtsParameter(),
minSdk: EnumParameter<Sdk> = minSdkParameter(),
language: EnumParameter<Language> = projectLanguageParameter(),
projectVersionData: ProjectVersionData = ProjectVersionData(),
isToml: Boolean = false,
showUseKts: Boolean = false,
showMinSdk: Boolean = true,
showLanguage: Boolean = true,
crossinline block: ProjectTemplateBuilder.() -> Unit
): ProjectTemplate {
return ProjectTemplateBuilder().apply {

// When project name is changed, change the package name accordingly
projectName.observe { name ->
val newPackage = AndroidUtils.appNameToPackageName(name.value, packageName.value)
packageName.setValue(newPackage)
}

Environment.mkdirIfNotExists(Environment.PROJECTS_DIR)

val saveLocation = stringParameter {
name = R.string.wizard_save_location
default = Environment.PROJECTS_DIR.absolutePath
endIcon = { R.drawable.ic_folder }
constraints = listOf(NONEMPTY, DIRECTORY, EXISTS)
inputType =
android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE
maxLines = 1
tooltipTag = "setup.save.location"
}

projectName.doBeforeCreateView {
it.setValue(getNewProjectName(saveLocation.value, projectName.value))
}

widgets(
TextFieldWidget(projectName), TextFieldWidget(packageName),
TextFieldWidget(saveLocation)
)

if (showLanguage) {
widgets(SpinnerWidget(language))
}

if (showMinSdk) {
widgets(SpinnerWidget(minSdk))
}

if (showUseKts) {
widgets(CheckBoxWidget(useKts))
}

// Setup the required properties before executing the recipe
preRecipe = {
this@apply._executor = this

if (!showUseKts) {
useKts.setValue(true, notify = false)
}

this@apply._data = ProjectTemplateData(
projectName.value,
File(saveLocation.value, projectName.value),
projectVersionData,
language = if (showLanguage) language.value else null,
useKts = useKts.value,
useToml = isToml
)

if (data.projectDir.exists() && data.projectDir.listFiles()
?.isNotEmpty() == true
) {
throw IllegalArgumentException("Project directory already exists")
}

setDefaultModuleData(
ModuleTemplateData(
":app", appName = data.name, packageName.value,
data.moduleNameToDir(":app"), type = AndroidApp,
language = if (showLanguage) language.value else null,
minSdk = if (showMinSdk) minSdk.value else null,
useKts = data.useKts, useToml = isToml
)
)
}

block()

}.build() as ProjectTemplate
}
Loading
Loading