feat(templates): autofill 'packageName' when 'projectName' parameter is changed

This commit is contained in:
Akash Yadav
2023-05-24 16:17:06 +05:30
parent 84eff5df80
commit 5e189b5ef7
3 changed files with 92 additions and 57 deletions
@@ -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<Sdk> {
name = R.string.minimum_sdk
default = Sdk.Lollipop
startIcon = R.drawable.ic_min_sdk
displayName = Sdk::displayName
}
val type = enumParameter<ModuleType> {
name = R.string.wizard_module_type
default = AndroidLibrary
@@ -185,13 +161,6 @@ fun baseAndroidModule(isLibrary: Boolean = false,
displayName = ModuleType::typeName
}
val language = enumParameter<Language> {
name = R.string.wizard_language
default = Java
startIcon = R.drawable.ic_language_java
displayName = Language::lang
}
widgets(TextFieldWidget(moduleName))
appName?.let {
@@ -133,7 +133,9 @@ abstract class Parameter<T>(@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<T>(@StringRes val name: Int,
*/
fun onChanged(parameter: Parameter<T>)
}
/**
* Default implementation of [Observer] which can enabled or disabled.
*/
abstract class DefaultObserver<T>(var isEnabled: Boolean = true) : Observer<T> {
/**
* 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<T>() {
abstract class ParameterBuilder<T> {
@StringRes
var name: Int? = null
@@ -221,7 +241,7 @@ class StringParameter(@StringRes name: Int, @StringRes description: Int?,
) : TextFieldParameter<String>(name, description, default, startIcon, endIcon,
onStartIconClick, onEndIconClick, constraints, suggest)
class StringParameterBuilder() : TextFieldParameterBuilder<String>() {
class StringParameterBuilder : TextFieldParameterBuilder<String>() {
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"
@@ -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<Boolean>() {
override fun onChanged(parameter: Parameter<Boolean>) {
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<String>() {
override fun onChanged(parameter: Parameter<String>) {
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<Enum<*>>
val items = mutableMapOf<String, Enum<*>>()
val nameToEnum = mutableMapOf<String, Enum<*>>()
val enumToName = mutableMapOf<Enum<*>, 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<Enum<*>>() {
override fun onChanged(parameter: Parameter<Enum<*>>) {
(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
}