From 83e0ddce18c85ca78389767efa9f4257cafc9d5a Mon Sep 17 00:00:00 2001 From: Daniel Alome Date: Thu, 26 Mar 2026 01:13:30 +0100 Subject: [PATCH] Add plugin template contribution API --- .../fragments/TemplateListFragment.kt | 3 - .../plugins/services/IdeTemplateService.kt | 19 ++ .../plugins/templates/CgtTemplateBuilder.kt | 230 ++++++++++++++++++ .../plugins/manager/core/PluginManager.kt | 50 +++- .../manager/project/PluginProjectManager.kt | 85 +++++++ .../services/IdeTemplateServiceImpl.kt | 96 ++++++++ .../templates/base/ProjectTemplateBuilder.kt | 2 +- .../itsaky/androidide/templates/base/base.kt | 17 +- .../templates/impl/TemplateProviderImpl.kt | 7 +- .../androidide/templates/impl/zip/ZipJson.kt | 2 +- .../templates/impl/zip/ZipTemplateReader.kt | 14 +- 11 files changed, 491 insertions(+), 34 deletions(-) create mode 100644 plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt create mode 100644 plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt create mode 100644 plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/project/PluginProjectManager.kt create mode 100644 plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt diff --git a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt index 0c03127156..b2bfd12a12 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt @@ -118,9 +118,6 @@ class TemplateListFragment : log.debug("Reloading templates...") - // Show only project templates - // reloading the templates also makes sure that the resources are - // released from template parameter widgets val templates = ITemplateProvider .getInstance(reload = true) diff --git a/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt b/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt new file mode 100644 index 0000000000..82bb621fb0 --- /dev/null +++ b/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeTemplateService.kt @@ -0,0 +1,19 @@ +package com.itsaky.androidide.plugins.services + +import com.itsaky.androidide.plugins.templates.CgtTemplateBuilder +import java.io.File + +interface IdeTemplateService { + + fun createTemplateBuilder(name: String): CgtTemplateBuilder + + fun registerTemplate(cgtFile: File): Boolean + + fun unregisterTemplate(templateFileName: String): Boolean + + fun isTemplateRegistered(templateFileName: String): Boolean + + fun getRegisteredTemplates(): List + + fun reloadTemplates() +} diff --git a/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt b/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt new file mode 100644 index 0000000000..6490e7c46d --- /dev/null +++ b/plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/templates/CgtTemplateBuilder.kt @@ -0,0 +1,230 @@ +package com.itsaky.androidide.plugins.templates + +import com.itsaky.androidide.plugins.PluginContext +import java.io.File +import java.io.InputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +class CgtTemplateBuilder(private val templateName: String) { + + private var description: String = "" + private var tooltipTag: String = "" + private var version: String = "1.0" + private var thumbnail: ByteArray? = null + private var showLanguage: Boolean = false + private var showMinSdk: Boolean = false + private var showPackageName: Boolean = false + + private val textParameters = mutableListOf() + private val checkboxParameters = mutableListOf() + private val templateFiles = mutableListOf() + private val staticFiles = mutableListOf() + private val binaryFiles = mutableListOf() + + fun description(desc: String) = apply { this.description = desc } + + fun tooltipTag(tag: String) = apply { this.tooltipTag = tag } + + fun version(ver: String) = apply { this.version = ver } + + fun thumbnail(bytes: ByteArray) = apply { this.thumbnail = bytes } + + fun showLanguageOption() = apply { this.showLanguage = true } + + fun showMinSdkOption() = apply { this.showMinSdk = true } + + fun showPackageNameOption() = apply { this.showPackageName = true } + + fun thumbnailFromAssets(assetPath: String, context: PluginContext) = apply { + this.thumbnail = context.androidContext.assets.open(assetPath).use { it.readBytes() } + } + + fun addTextParameter(label: String, identifier: String, default: String? = null) = apply { + textParameters.add(TextParam(label, identifier, default)) + } + + fun addCheckboxParameter(label: String, identifier: String, default: Boolean = false) = apply { + checkboxParameters.add(CheckboxParam(label, identifier, default)) + } + + fun addTemplateFile(path: String, content: String) = apply { + templateFiles.add(FileEntry(path, toPebbleSyntax(content))) + } + + fun addTemplateFromAssets(path: String, assetPath: String, context: PluginContext) = apply { + val content = context.androidContext.assets.open(assetPath) + .bufferedReader() + .use { it.readText() } + templateFiles.add(FileEntry(path, content)) + } + + fun addStaticFile(path: String, content: String) = apply { + staticFiles.add(FileEntry(path, content)) + } + + fun addStaticFile(path: String, bytes: ByteArray) = apply { + binaryFiles.add(BinaryFileEntry(path, bytes)) + } + + fun addStaticFile(path: String, inputStream: InputStream) = apply { + binaryFiles.add(BinaryFileEntry(path, inputStream.use { it.readBytes() })) + } + + fun addStaticFromAssets(path: String, assetPath: String, context: PluginContext) = apply { + val bytes = context.androidContext.assets.open(assetPath) + .use { it.readBytes() } + binaryFiles.add(BinaryFileEntry(path, bytes)) + } + + fun build(outputDir: File): File { + outputDir.mkdirs() + val dirName = templateName.replace(Regex("[^a-zA-Z0-9]"), "") + val outputFile = File(outputDir, "$dirName.cgt") + + ZipOutputStream(outputFile.outputStream()).use { zip -> + writeIndex(zip, dirName) + writeMetadata(zip, dirName) + writeThumbnail(zip, dirName) + writeTemplateFiles(zip, dirName) + writeStaticFiles(zip, dirName) + writeBinaryFiles(zip, dirName) + } + + return outputFile + } + + private fun toPebbleSyntax(content: String): String { + return content + .replace("{{", "\${{") + .replace("{%", "\${%") + .replace("{#", "\${#") + .replace("\$\${{", "\${{") + .replace("\$\${%", "\${%") + .replace("\$\${#", "\${#") + } + + private fun writeIndex(zip: ZipOutputStream, dirName: String) { + val json = """ + { + "templates": [ + { "path": "$dirName" } + ] + } + """.trimIndent() + addEntry(zip, "templates.json", json) + } + + private fun writeMetadata(zip: ZipOutputStream, dirName: String) { + val optionalBlock = buildString { + val parts = mutableListOf() + if (showLanguage) parts.add(""""language": {"identifier": "LANGUAGE"}""") + if (showMinSdk) parts.add(""""minsdk": {"identifier": "MIN_SDK"}""") + if (parts.isNotEmpty()) { + append(""" + "optional": { + ${parts.joinToString(",\n ")} + }, + """.trimIndent()) + } + } + + val userBlock = buildString { + if (textParameters.isEmpty() && checkboxParameters.isEmpty()) return@buildString + + val textJson = textParameters.joinToString(",\n ") { p -> + val defaultPart = if (p.default != null) """, "default": "${p.default}"""" else "" + """{"label": "${p.label}", "identifier": "${p.identifier}"$defaultPart}""" + } + + val checkboxJson = checkboxParameters.joinToString(",\n ") { p -> + """{"label": "${p.label}", "identifier": "${p.identifier}", "default": ${p.default}}""" + } + + append(""""user": {""") + if (textParameters.isNotEmpty()) { + append(""" + "text": [$textJson]""") + } + if (checkboxParameters.isNotEmpty()) { + if (textParameters.isNotEmpty()) append(",") + append(""" + "checkbox": [$checkboxJson]""") + } + append(""" + },""") + } + + val json = """ + { + "name": "$templateName", + "description": "$description", + "tooltipTag": "$tooltipTag", + "version": "$version", + "parameters": { + "required": { + "appName": {"identifier": "APP_NAME"}, + ${if (showPackageName) """"packageName": {"identifier": "PACKAGE_NAME"},""" else ""} + "saveLocation": {"identifier": "SAVE_LOCATION"} + }, + $optionalBlock + $userBlock + "placeholder": null + }, + "system": { + "agpVersion": {"identifier": "AGP_VERSION"}, + "kotlinVersion": {"identifier": "KOTLIN_VERSION"}, + "gradleVersion": {"identifier": "GRADLE_VERSION"}, + "compileSdk": {"identifier": "COMPILE_SDK"}, + "targetSdk": {"identifier": "TARGET_SDK"}, + "javaSourceCompat": {"identifier": "JAVA_SOURCE_COMPAT"}, + "javaTargetCompat": {"identifier": "JAVA_TARGET_COMPAT"}, + "javaTarget": {"identifier": "JAVA_TARGET"} + } + } + """.trimIndent() + + addEntry(zip, "$dirName/template/template.json", json) + } + + private fun writeThumbnail(zip: ZipOutputStream, dirName: String) { + val thumbBytes = thumbnail ?: return + zip.putNextEntry(ZipEntry("$dirName/template/thumb.png")) + zip.write(thumbBytes) + zip.closeEntry() + } + + private fun writeTemplateFiles(zip: ZipOutputStream, dirName: String) { + for (file in templateFiles) { + addEntry(zip, "$dirName/${file.path}.peb", file.content) + } + } + + private fun writeStaticFiles(zip: ZipOutputStream, dirName: String) { + for (file in staticFiles) { + addEntry(zip, "$dirName/${file.path}", file.content) + } + } + + private fun writeBinaryFiles(zip: ZipOutputStream, dirName: String) { + for (file in binaryFiles) { + zip.putNextEntry(ZipEntry("$dirName/${file.path}")) + zip.write(file.bytes) + zip.closeEntry() + } + } + + private fun addEntry(zip: ZipOutputStream, path: String, content: String) { + zip.putNextEntry(ZipEntry(path)) + zip.write(content.toByteArray(Charsets.UTF_8)) + zip.closeEntry() + } + + private data class TextParam(val label: String, val identifier: String, val default: String?) + private data class CheckboxParam(val label: String, val identifier: String, val default: Boolean) + private data class FileEntry(val path: String, val content: String) + private data class BinaryFileEntry(val path: String, val bytes: ByteArray) + + companion object + +} diff --git a/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt b/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt index 7abc436c8b..5b07219c6e 100644 --- a/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt +++ b/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt @@ -30,6 +30,9 @@ import com.itsaky.androidide.plugins.manager.context.PluginRegistry import com.itsaky.androidide.plugins.manager.context.ResourceManagerImpl import com.itsaky.androidide.plugins.manager.context.ServiceRegistryImpl import com.itsaky.androidide.plugins.manager.documentation.PluginDocumentationManager +import com.itsaky.androidide.plugins.manager.project.PluginProjectManager +import com.itsaky.androidide.plugins.manager.services.IdeTemplateServiceImpl +import com.itsaky.androidide.plugins.services.IdeTemplateService import com.itsaky.androidide.plugins.services.IdeTooltipService import com.itsaky.androidide.plugins.services.IdeEditorTabService import com.itsaky.androidide.plugins.services.IdeFileService @@ -102,6 +105,12 @@ class PluginManager private constructor( private val pluginsDir = File(context.filesDir, "plugins") private val documentationManager = PluginDocumentationManager(context) + private var templateReloadListener: (() -> Unit)? = null + + fun setTemplateReloadListener(listener: (() -> Unit)?) { + this.templateReloadListener = listener + PluginProjectManager.getInstance().setTemplateReloadListener(listener) + } // Helper methods for cleaner error handling private fun executeWithErrorHandling( @@ -459,6 +468,13 @@ class PluginManager private constructor( } } + PluginProjectManager.getInstance().cleanupPluginTemplates(pluginId) + + val templateService = loadedPlugin.context.services.get(IdeTemplateService::class.java) + if (templateService is IdeTemplateServiceImpl) { + templateService.cleanupAllTemplates() + } + loadedPlugin.plugin.deactivate() loadedPlugin.plugin.dispose() @@ -662,6 +678,7 @@ class PluginManager private constructor( loadedPlugin.plugin.activate() loadedPlugin.isEnabled = true savePluginState(pluginId, true) + logger.info("Enabled plugin: $pluginId") true } catch (e: Exception) { @@ -669,19 +686,20 @@ class PluginManager private constructor( false } } - + fun disablePlugin(pluginId: String): Boolean { val loadedPlugin = loadedPlugins[pluginId] ?: return false - + if (!loadedPlugin.isEnabled) { logger.info("Plugin $pluginId is already disabled") return true } - + return try { loadedPlugin.plugin.deactivate() loadedPlugin.isEnabled = false savePluginState(pluginId, false) + logger.info("Disabled plugin: $pluginId") true } catch (e: Exception) { @@ -900,6 +918,19 @@ class PluginManager private constructor( IdeThemeServiceImpl(context) } + registerServiceWithErrorHandling( + pluginServiceRegistry, + IdeTemplateService::class.java, + pluginId, + "template" + ) { + IdeTemplateServiceImpl( + pluginId = pluginId, + permissions = permissions, + onTemplatesChanged = { templateReloadListener?.invoke() } + ) + } + // Create PluginContext with resource context return PluginContextImpl( androidContext = resourceContext, // Use the resource context instead of app context @@ -1023,6 +1054,19 @@ class PluginManager private constructor( IdeThemeServiceImpl(context) } + registerServiceWithErrorHandling( + pluginServiceRegistry, + IdeTemplateService::class.java, + pluginId, + "template" + ) { + IdeTemplateServiceImpl( + pluginId = pluginId, + permissions = permissions, + onTemplatesChanged = { templateReloadListener?.invoke() } + ) + } + return PluginContextImpl( androidContext = context, services = pluginServiceRegistry, diff --git a/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/project/PluginProjectManager.kt b/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/project/PluginProjectManager.kt new file mode 100644 index 0000000000..3176ac508c --- /dev/null +++ b/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/project/PluginProjectManager.kt @@ -0,0 +1,85 @@ +package com.itsaky.androidide.plugins.manager.project + +import com.itsaky.androidide.utils.Environment +import org.slf4j.LoggerFactory +import java.io.File + +class PluginProjectManager private constructor() { + + companion object { + private val log = LoggerFactory.getLogger(PluginProjectManager::class.java) + + @Volatile + private var instance: PluginProjectManager? = null + + fun getInstance(): PluginProjectManager { + return instance ?: synchronized(this) { + instance ?: PluginProjectManager().also { instance = it } + } + } + + private const val PLUGIN_CGT_PREFIX = "plugin_" + private const val CGT_EXTENSION = "cgt" + } + + private var templateReloadListener: (() -> Unit)? = null + + fun setTemplateReloadListener(listener: (() -> Unit)?) { + this.templateReloadListener = listener + } + + fun extractBundledCgtTemplates(pluginId: String, classLoader: ClassLoader) { + val searchPaths = listOf("templates/", "assets/templates/") + + val extracted = searchPaths.flatMap { basePath -> + extractCgtFromPath(pluginId, classLoader, basePath) + } + + if (extracted.isNotEmpty()) { + templateReloadListener?.invoke() + } + } + + private fun extractCgtFromPath( + pluginId: String, + classLoader: ClassLoader, + basePath: String + ): List { + val entries = classLoader.getResourceAsStream(basePath) + ?.bufferedReader() + ?.use { it.readLines() } + ?: return emptyList() + + return entries + .filter { it.endsWith(".$CGT_EXTENSION") } + .mapNotNull { entry -> + val inputStream = classLoader.getResourceAsStream("$basePath$entry") ?: return@mapNotNull null + val destFile = File(Environment.TEMPLATES_DIR, "${PLUGIN_CGT_PREFIX}${pluginId}_$entry") + + runCatching { + inputStream.use { input -> + destFile.outputStream().use { output -> input.copyTo(output) } + } + log.info("Extracted bundled template: {} from plugin {}", entry, pluginId) + destFile + }.onFailure { + log.error("Failed to extract bundled template $entry from plugin $pluginId", it) + }.getOrNull() + } + } + + fun cleanupPluginTemplates(pluginId: String) { + val prefix = "${PLUGIN_CGT_PREFIX}${pluginId}_" + val deleted = Environment.TEMPLATES_DIR + .listFiles { file -> file.name.startsWith(prefix) && file.extension == CGT_EXTENSION } + ?.count { file -> + file.delete().also { if (it) log.debug("Cleaned up plugin template: {}", file.name) } + } + ?: 0 + + if (deleted > 0) { + log.info("Cleaned up {} template files for plugin {}", deleted, pluginId) + templateReloadListener?.invoke() + } + } +} diff --git a/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt b/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt new file mode 100644 index 0000000000..5e2dc6d08a --- /dev/null +++ b/plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTemplateServiceImpl.kt @@ -0,0 +1,96 @@ +package com.itsaky.androidide.plugins.manager.services + +import com.itsaky.androidide.plugins.PluginPermission +import com.itsaky.androidide.plugins.services.IdeTemplateService +import com.itsaky.androidide.plugins.templates.CgtTemplateBuilder +import com.itsaky.androidide.utils.Environment +import org.slf4j.LoggerFactory +import java.io.File + +class IdeTemplateServiceImpl( + private val pluginId: String, + private val permissions: Set, + private val onTemplatesChanged: () -> Unit +) : IdeTemplateService { + + companion object { + private val log = LoggerFactory.getLogger(IdeTemplateServiceImpl::class.java) + private const val PLUGIN_PREFIX = "plugin_" + private const val CGT_EXTENSION = "cgt" + } + + private fun prefixedName(fileName: String): String { + return "${PLUGIN_PREFIX}${pluginId}_$fileName" + } + + override fun createTemplateBuilder(name: String): CgtTemplateBuilder { + return CgtTemplateBuilder(name) + } + + override fun registerTemplate(cgtFile: File): Boolean { + if (!permissions.contains(PluginPermission.FILESYSTEM_WRITE)) { + log.warn("Plugin $pluginId lacks FILESYSTEM_WRITE permission for template registration") + return false + } + + if (!cgtFile.exists() || cgtFile.extension != CGT_EXTENSION) { + log.warn("Invalid template file for plugin $pluginId: ${cgtFile.absolutePath}") + return false + } + + val destFile = File(Environment.TEMPLATES_DIR, prefixedName(cgtFile.name)) + return try { + cgtFile.copyTo(destFile, overwrite = true) + log.info("Plugin $pluginId registered template: ${destFile.name}") + onTemplatesChanged() + true + } catch (e: Exception) { + log.error("Failed to register template for plugin $pluginId", e) + false + } + } + + override fun unregisterTemplate(templateFileName: String): Boolean { + val destFile = File(Environment.TEMPLATES_DIR, prefixedName(templateFileName)) + if (!destFile.exists()) return false + + return try { + destFile.delete() + log.info("Plugin $pluginId unregistered template: ${destFile.name}") + onTemplatesChanged() + true + } catch (e: Exception) { + log.error("Failed to unregister template for plugin $pluginId", e) + false + } + } + + override fun isTemplateRegistered(templateFileName: String): Boolean { + return File(Environment.TEMPLATES_DIR, prefixedName(templateFileName)).exists() + } + + override fun getRegisteredTemplates(): List { + val prefix = prefixedName("") + return Environment.TEMPLATES_DIR + .listFiles { file -> file.name.startsWith(prefix) && file.extension == CGT_EXTENSION } + ?.map { it.name.removePrefix(prefix) } + ?: emptyList() + } + + override fun reloadTemplates() { + onTemplatesChanged() + } + + fun cleanupAllTemplates() { + val prefix = prefixedName("") + val files = Environment.TEMPLATES_DIR + .listFiles { file -> file.name.startsWith(prefix) && file.extension == CGT_EXTENSION } + ?: return + + files.forEach { it.delete() } + + if (files.isNotEmpty()) { + onTemplatesChanged() + } + } +} diff --git a/templates-api/src/main/java/com/itsaky/androidide/templates/base/ProjectTemplateBuilder.kt b/templates-api/src/main/java/com/itsaky/androidide/templates/base/ProjectTemplateBuilder.kt index bed627c668..18823347bb 100644 --- a/templates-api/src/main/java/com/itsaky/androidide/templates/base/ProjectTemplateBuilder.kt +++ b/templates-api/src/main/java/com/itsaky/androidide/templates/base/ProjectTemplateBuilder.kt @@ -200,6 +200,6 @@ class ProjectTemplateBuilder : ExecutorDataTemplateBuilder 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) + if (showPackageName) { + projectName.observe { name -> + val newPackage = AndroidUtils.appNameToPackageName(name.value, packageName.value) + packageName.setValue(newPackage) + } } Environment.mkdirIfNotExists(Environment.PROJECTS_DIR) @@ -366,10 +368,9 @@ inline fun baseZipProject( it.setValue(getNewProjectName(saveLocation.value, projectName.value)) } - widgets( - TextFieldWidget(projectName), TextFieldWidget(packageName), - TextFieldWidget(saveLocation) - ) + widgets(TextFieldWidget(projectName)) + if (showPackageName) widgets(TextFieldWidget(packageName)) + widgets(TextFieldWidget(saveLocation)) if (showLanguage) { widgets(SpinnerWidget(language)) diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt index e3edf59a25..cb51989fdf 100644 --- a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt @@ -51,22 +51,17 @@ class TemplateProviderImpl : ITemplateProvider { private fun initializeTemplates() { val folder = TEMPLATES_DIR - log.debug("Template listing archives in: ${folder.toString()} with extension: ${TEMPLATE_ARCHIVE_EXTENSION}") val list = folder.listFiles { file -> file.extension == TEMPLATE_ARCHIVE_EXTENSION } ?: return for (zipFile in list) { - log.debug("Template archive: $zipFile") try { val zipTemplates = ZipTemplateReader.read(zipFile) { json, params, path, data, defModule -> ZipRecipeExecutor({ ZipFile(zipFile) }, json, params, path, data, defModule) } for (t in zipTemplates) { - log.debug("template: $t") templates[t.templateId] = t } - - log.debug("templates: $templates") } catch (e: Exception) { log.error("Failed to load template from archive: $zipFile", e) } @@ -90,4 +85,4 @@ class TemplateProviderImpl : ITemplateProvider { templates.forEach { it.value.release() } templates.clear() } -} \ No newline at end of file +} diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipJson.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipJson.kt index f7b1bd25a5..38fa60d895 100644 --- a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipJson.kt +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipJson.kt @@ -21,7 +21,7 @@ data class ParametersJson( data class RequiredParametersJson( val appName: IdentifierJson, - val packageName: IdentifierJson, + val packageName: IdentifierJson? = null, val saveLocation: IdentifierJson ) diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipTemplateReader.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipTemplateReader.kt index 8cfab63bdb..073e9ea8ee 100644 --- a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipTemplateReader.kt +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/zip/ZipTemplateReader.kt @@ -33,18 +33,14 @@ object ZipTemplateReader { try { ZipFile(zipFile).use { zip -> - log.debug("zipFile: $zipFile") - val indexEntry = zip.getEntry(ARCHIVE_JSON) ?: return emptyList() val indexJson = zip.getInputStream(indexEntry).bufferedReader().use { gson.fromJson(it, TemplatesIndex::class.java) } - log.debug("indexJson: $indexJson") for (templateRef in indexJson.templates) { try { val basePath = templateRef.path - log.debug("basePath: $basePath") val metaEntry = zip.getEntry("$basePath/$META_FOLDER/$META_JSON") ?: continue val metaJsonString = zip.getInputStream(metaEntry).bufferedReader().use { reader -> @@ -53,14 +49,9 @@ object ZipTemplateReader { val metaJson = gson.fromJson(metaJsonString, TemplateJson::class.java) - log.debug("metaJson: $metaJson") - val thumbEntry = zip.getEntry("$basePath/$META_FOLDER/$META_THUMBNAIL") val thumbData = thumbEntry?.let { zip.getInputStream(it).use { s -> s.readBytes() } } - if (thumbData == null) log.error("template $basePath/$META_FOLDER/$META_THUMBNAIL not found or is invalid") - log.debug("thumbData: $thumbData") - val userWidgets = mutableListOf>() val params = mutableMapOf>() @@ -86,7 +77,8 @@ object ZipTemplateReader { val project = baseZipProject( showLanguage = (metaJson.parameters?.optional?.language != null), - showMinSdk = (metaJson.parameters?.optional?.minsdk != null) + showMinSdk = (metaJson.parameters?.optional?.minsdk != null), + showPackageName = (metaJson.parameters?.required?.packageName != null) ) { this.templateNameStr = metaJson.name @@ -100,14 +92,12 @@ object ZipTemplateReader { widgets(widget) } - log.debug("this.name: ${this.templateNameStr}") this.recipe = TemplateRecipe { executor -> val innerRecipe = recipeFactory(metaJson, params, basePath, data, defModule) innerRecipe.execute(executor) } } - log.debug("adding project ${metaJson.name}") templates.add(project) } catch (e: Exception) { log.error("Failed to load template at ${templateRef.path}", e)