diff --git a/templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt b/templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt index 9261d4777..553273dc7 100644 --- a/templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt +++ b/templates-api/src/main/java/com/itsaky/androidide/templates/base/base.kt @@ -68,14 +68,12 @@ fun baseProject(block: ProjectTemplateBuilder.() -> Unit val projectName = projectNameParameter() val language = projectLanguageParameter() val minSdk = minSdkParameter() + val packageName = packageNameParameter() - val packageName = packageNameParameter { - - suggest = { - AndroidUtils.appNameToPackageName(/* appName = */ - projectName.value, /* packageName = */ - this.value ?: "") - } + // When project name is changed, change the package name accordingly + projectName.observe { name -> + val newPackage = AndroidUtils.appNameToPackageName(name.value!!, packageName.value!!) + packageName.setValue(newPackage) } val saveLocation = stringParameter { @@ -83,12 +81,6 @@ fun baseProject(block: ProjectTemplateBuilder.() -> Unit default = Environment.PROJECTS_DIR.absolutePath endIcon = R.drawable.ic_folder constraints = listOf(NONEMPTY, DIRECTORY, EXISTS) - - suggest = { - projectName.value!!.replace(" ", "").let { - File(Environment.PROJECTS_DIR, it).absolutePath - } - } } widgets(TextFieldWidget(projectName), TextFieldWidget(packageName), @@ -151,33 +143,17 @@ fun baseAndroidModule(isLibrary: Boolean = false, ): ModuleTemplate { return AndroidModuleTemplateBuilder().apply { + val appName = if (isLibrary) null else projectNameParameter() + val language = projectLanguageParameter() + val minSdk = minSdkParameter() + val packageName = packageNameParameter() + val moduleName = stringParameter { name = R.string.wizard_module_name default = ":app" constraints = listOf(NONEMPTY, MODULE_NAME) } - val appName = if (isLibrary) null else stringParameter { - name = R.string.project_app_name - default = "My Application" - startIcon = R.drawable.ic_android - constraints = listOf(NONEMPTY) - } - - val packageName = stringParameter { - name = R.string.package_name - default = "com.example.myapplication" - startIcon = R.drawable.ic_package - constraints = listOf(NONEMPTY, PACKAGE) - } - - val minSdk = enumParameter { - name = R.string.minimum_sdk - default = Sdk.Lollipop - startIcon = R.drawable.ic_min_sdk - displayName = Sdk::displayName - } - val type = enumParameter { name = R.string.wizard_module_type default = AndroidLibrary @@ -185,13 +161,6 @@ fun baseAndroidModule(isLibrary: Boolean = false, displayName = ModuleType::typeName } - val language = enumParameter { - name = R.string.wizard_language - default = Java - startIcon = R.drawable.ic_language_java - displayName = Language::lang - } - widgets(TextFieldWidget(moduleName)) appName?.let { diff --git a/templates-api/src/main/java/com/itsaky/androidide/templates/parameters.kt b/templates-api/src/main/java/com/itsaky/androidide/templates/parameters.kt index fbe5b9e9a..5a3fe8725 100644 --- a/templates-api/src/main/java/com/itsaky/androidide/templates/parameters.kt +++ b/templates-api/src/main/java/com/itsaky/androidide/templates/parameters.kt @@ -133,7 +133,9 @@ abstract class Parameter(@StringRes val name: Int, private fun notifyObservers() { lock.withLock { observers.forEach { - it.onChanged(this) + if (it !is DefaultObserver || it.isEnabled) { + it.onChanged(this) + } } } } @@ -150,9 +152,27 @@ abstract class Parameter(@StringRes val name: Int, */ fun onChanged(parameter: Parameter) } + + /** + * Default implementation of [Observer] which can enabled or disabled. + */ + abstract class DefaultObserver(var isEnabled: Boolean = true) : Observer { + + /** + * Executes the given [action] with this observer disabled. + * + * @param action The action to perform. + */ + fun disableAndRun(action: () -> Unit) { + val enabled = isEnabled + isEnabled = false + action() + isEnabled = enabled + } + } } -abstract class ParameterBuilder() { +abstract class ParameterBuilder { @StringRes var name: Int? = null @@ -221,7 +241,7 @@ class StringParameter(@StringRes name: Int, @StringRes description: Int?, ) : TextFieldParameter(name, description, default, startIcon, endIcon, onStartIconClick, onEndIconClick, constraints, suggest) -class StringParameterBuilder() : TextFieldParameterBuilder() { +class StringParameterBuilder : TextFieldParameterBuilder() { override fun build(): StringParameter { return StringParameter(name = name!!, description = description, @@ -279,7 +299,7 @@ fun projectNameParameter(configure: StringParameterBuilder.() -> Unit = {}) = configure() } -fun packageNameParameter(configure: StringParameterBuilder.() -> Unit) = +fun packageNameParameter(configure: StringParameterBuilder.() -> Unit = {}) = stringParameter { name = R.string.package_name default = "com.example.myapplication" diff --git a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateWidgetViewProviderImpl.kt b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateWidgetViewProviderImpl.kt index 9d7e5be36..387b267a9 100644 --- a/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateWidgetViewProviderImpl.kt +++ b/templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateWidgetViewProviderImpl.kt @@ -28,6 +28,8 @@ import com.itsaky.androidide.templates.BooleanParameter import com.itsaky.androidide.templates.CheckBoxWidget import com.itsaky.androidide.templates.EnumParameter import com.itsaky.androidide.templates.ITemplateWidgetViewProvider +import com.itsaky.androidide.templates.Parameter +import com.itsaky.androidide.templates.Parameter.DefaultObserver import com.itsaky.androidide.templates.SpinnerWidget import com.itsaky.androidide.templates.StringParameter import com.itsaky.androidide.templates.TextFieldParameter @@ -79,17 +81,40 @@ class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider { root.setText(param.name) root.isChecked = param.default - root.addOnCheckedStateChangedListener { checkbox, _ -> - param.setValue(checkbox.isChecked) + val observer = object : DefaultObserver() { + override fun onChanged(parameter: Parameter) { + disableAndRun { + root.isChecked = param.value ?: false + } + } } + + root.addOnCheckedStateChangedListener { checkbox, _ -> + observer.disableAndRun { + param.setValue(checkbox.isChecked) + } + } + + param.observe(observer) }.root } private fun createTextField(context: Context, widget: TextFieldWidget): View { return LayoutTextfieldBinding.inflate(LayoutInflater.from(context)).apply { val param = widget.parameter as StringParameter + val observer = object : DefaultObserver() { + override fun onChanged(parameter: Parameter) { + disableAndRun { + input.setText(param.value ?: "") + } + } + } + param.configureTextField(context, root) { value -> - param.setValue(value) + observer.disableAndRun { + param.setValue(value) + } + val err = ConstraintVerifier.verify(value, constraints = param.constraints) @@ -97,8 +122,12 @@ class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider { if (err != null) { root.error = err } + } + input.setText(param.default) + param.observe(observer) + }.root } @@ -106,31 +135,48 @@ class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider { return LayoutSpinnerBinding.inflate(LayoutInflater.from(context)).apply { val param = widget.parameter as EnumParameter> - val items = mutableMapOf>() + val nameToEnum = mutableMapOf>() + val enumToName = mutableMapOf, String>() param.default.javaClass.enumConstants?.forEach { val displayName = param.displayName?.invoke(it) ?: it.name - items[displayName] = it + nameToEnum[displayName] = it + enumToName[it] = displayName } check( - items.isNotEmpty()) { "Cannot retrive values for enum parameter $param with default value ${param.default}" } + nameToEnum.isNotEmpty()) { "Cannot retrive values for enum parameter $param with default value ${param.default}" } - val array = items.keys.toTypedArray() + val array = nameToEnum.keys.toTypedArray() input.setAdapter(ArrayAdapter(context, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, array)) - val defaultName = - param.displayName?.invoke(param.default) ?: param.default.name - root.isEnabled = items.size > 1 + val defaultName = + enumToName[param.default] ?: param.default.name + + root.isEnabled = nameToEnum.size > 1 input.listSelection = array.indexOf(defaultName) input.setText(defaultName, false) - param.configureTextField(context, root) { - param.setValue(items[it] ?: param.default) + val observer = object : DefaultObserver>() { + override fun onChanged(parameter: Parameter>) { + (parameter as EnumParameter<*>).apply { + disableAndRun { + input.setText(enumToName[value ?: default]) + } + } + } } + + param.configureTextField(context, root) { + observer.disableAndRun { + param.setValue(nameToEnum[it] ?: param.default) + } + } + + param.observe(observer) }.root }