From 90812edcf96c314ea0419217f1c006f3dc1ed3d4 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Sun, 27 Aug 2023 13:50:04 +0530 Subject: [PATCH] fix(templates): project name is not properly updated if project with same name already exists --- .../androidide/activities/MainActivity.kt | 2 + .../fragments/TemplateDetailsFragment.kt | 6 +- .../fragments/TemplateListFragment.kt | 42 +++++--- .../androidide/templates/ITemplateProvider.kt | 14 +-- .../itsaky/androidide/templates/base/base.kt | 4 +- .../itsaky/androidide/templates/parameters.kt | 100 ++++++++++++++---- .../templates/impl/TemplateProviderImpl.kt | 17 ++- .../impl/TemplateWidgetViewProviderImpl.kt | 10 +- 8 files changed, 146 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt index 258107ee6..8cf570a59 100755 --- a/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt @@ -44,6 +44,7 @@ import com.itsaky.androidide.preferences.internal.statConsentDialogShown import com.itsaky.androidide.preferences.internal.statOptIn import com.itsaky.androidide.projects.ProjectManager.projectPath import com.itsaky.androidide.resources.R.string +import com.itsaky.androidide.templates.ITemplateProvider import com.itsaky.androidide.utils.DialogUtils import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.flashError @@ -292,6 +293,7 @@ class MainActivity : LimitlessIDEActivity() { } override fun onDestroy() { + ITemplateProvider.getInstance().release() super.onDestroy() _binding = null } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/TemplateDetailsFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/TemplateDetailsFragment.kt index 04de9f3e5..01971098a 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/TemplateDetailsFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/TemplateDetailsFragment.kt @@ -73,8 +73,10 @@ class TemplateDetailsFragment : binding.finish.setOnClickListener { viewModel.creatingProject.value = true - val template = checkNotNull( - viewModel.template.value) { "Cannot create project. Template not found." } + val template = viewModel.template.value ?: run { + viewModel.setScreen(MainViewModel.SCREEN_MAIN) + return@setOnClickListener + } val isValid = template.parameters.fold(true) { isValid, param -> if (param is StringParameter) { 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 6715723d1..42194343b 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt @@ -29,6 +29,7 @@ import com.itsaky.androidide.adapters.TemplateListAdapter import com.itsaky.androidide.databinding.FragmentTemplateListBinding import com.itsaky.androidide.templates.ITemplateProvider import com.itsaky.androidide.templates.ProjectTemplate +import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.viewmodel.MainViewModel import kotlin.math.ceil @@ -51,21 +52,10 @@ class TemplateListFragment : FragmentWithBinding( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // Show only project templates - val templates = ITemplateProvider.getInstance(true) - .getTemplates() - .filterIsInstance() - layoutManager = FlexboxLayoutManager(requireContext(), FlexDirection.ROW) layoutManager!!.justifyContent = JustifyContent.SPACE_EVENLY - adapter = TemplateListAdapter(templates) { template, _ -> - viewModel.template.value = template - viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_DETAILS) - } - binding.list.layoutManager = layoutManager - binding.list.adapter = adapter // This makes sure that the items are evenly distributed in the list // and the last row is always aligned to the start @@ -98,12 +88,40 @@ class TemplateListFragment : FragmentWithBinding( binding.exitButton.setOnClickListener { viewModel.setScreen(MainViewModel.SCREEN_MAIN) } + + viewModel.currentScreen.observe(viewLifecycleOwner) { current -> + if (current == MainViewModel.SCREEN_TEMPLATE_DETAILS) { + return@observe + } + + reloadTemplates() + } } override fun onDestroyView() { binding.list.viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener) - ITemplateProvider.getInstance().clear() super.onDestroyView() } + private val log = ILogger.newInstance("TemplateListFragment") + + private fun reloadTemplates() { + _binding ?: return + + log.debug("Reloading templates...") + + // Show only project templates + // reloading the tempaltes also makes sure that the resources are + // released from template parameter widgets + val templates = ITemplateProvider.getInstance(reload = true) + .getTemplates() + .filterIsInstance() + + adapter = TemplateListAdapter(templates) { template, _ -> + viewModel.template.value = template + viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_DETAILS) + } + + binding.list.adapter = adapter + } } diff --git a/templates-api/src/main/java/com/itsaky/androidide/templates/ITemplateProvider.kt b/templates-api/src/main/java/com/itsaky/androidide/templates/ITemplateProvider.kt index 6ed159c58..56c1f4faf 100644 --- a/templates-api/src/main/java/com/itsaky/androidide/templates/ITemplateProvider.kt +++ b/templates-api/src/main/java/com/itsaky/androidide/templates/ITemplateProvider.kt @@ -39,12 +39,9 @@ interface ITemplateProvider { @JvmStatic @JvmOverloads fun getInstance(reload: Boolean = false): ITemplateProvider { - if (reload) { - provider?.clear() - provider = null - } - return provider ?: ServiceLoader.load(ITemplateProvider::class.java) + return provider?.also { if (reload) it.reload() } ?: ServiceLoader.load( + ITemplateProvider::class.java) .findFirstOrThrow() .also { provider = it } } @@ -71,8 +68,13 @@ interface ITemplateProvider { */ fun getTemplate(templateId: String): Template<*>? + /** + * Reloads the templates. + */ + fun reload() + /** * Clear all the templates stored in the provider. */ - fun clear() + fun release() } \ No newline at end of file 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 88f257cb1..fff3beb10 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 @@ -86,7 +86,9 @@ fun baseProject(projectName: StringParameter = projectNameParameter(), constraints = listOf(NONEMPTY, DIRECTORY, EXISTS) } - projectName.setValue(getNewProjectName(saveLocation.value, projectName.value)) + projectName.doBeforeCreateView { + it.setValue(getNewProjectName(saveLocation.value, projectName.value)) + } widgets(TextFieldWidget(projectName), TextFieldWidget(packageName), TextFieldWidget(saveLocation), SpinnerWidget(language), 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 196628b57..96ab245dd 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 @@ -81,14 +81,17 @@ enum class ParameterConstraint { } abstract class Parameter(@StringRes val name: Int, - @StringRes val description: Int?, val default: T, - var constraints: List + @StringRes val description: Int?, val default: T, + var constraints: List ) { private val observers = hashSetOf>() private val lock = ReentrantLock() private var _value: T? = null + private var actionBeforeCreateView: ((Parameter) -> Unit)? = null + private var actionAfterCreateView: ((Parameter) -> Unit)? = null + /** * The value of this parameter. */ @@ -109,6 +112,16 @@ abstract class Parameter(@StringRes val name: Int, } } + /** + * Resets the parameter value to the default value and removes any external value observers. + * + * @param notify Whether the observers should be notified about this change or not. + */ + fun reset(notify: Boolean = true) { + setValue(default, notify) + clearObservers() + } + /** * Adds the [Observer] instance to the list of observers. * @@ -134,11 +147,52 @@ abstract class Parameter(@StringRes val name: Int, } fun release() { + clearObservers() + + this.actionBeforeCreateView = null + this.actionAfterCreateView = null + } + + private fun clearObservers() { lock.withLock { observers.clear() } } + /** + * Perform the given action before the view is created. + * + * @param action The action to execute. + * @see beforeCreateView + */ + fun doBeforeCreateView(action: (Parameter) -> Unit) { + this.actionBeforeCreateView = action + } + + /** + * Perform the given action after the view is created. + * + * @param action The action to execute. + * @see afterCreateView + */ + fun doAfterCreateView(action: (Parameter) -> Unit) { + this.actionBeforeCreateView = action + } + + /** + * Called before the layout for this widget is created. + */ + open fun beforeCreateView() { + this.actionBeforeCreateView?.invoke(this) + } + + /** + * Called after the layout for this widget is created. + */ + open fun afterCreateView() { + this.actionAfterCreateView?.invoke(this) + } + private fun notifyObservers() { lock.withLock { observers.forEach { @@ -202,7 +256,7 @@ abstract class ParameterBuilder { } class BooleanParameter(@StringRes name: Int, @StringRes description: Int?, - default: Boolean, constraints: List + default: Boolean, constraints: List ) : Parameter(name, description, default, constraints) class BooleanParameterBuilder : ParameterBuilder() { @@ -224,12 +278,12 @@ class BooleanParameterBuilder : ParameterBuilder() { * clicked. Click listener to the icon will be set on if this is non-null. */ abstract class TextFieldParameter(@StringRes name: Int, - @StringRes description: Int?, default: T, - val startIcon: ((TextFieldParameter) -> Int)?, - val endIcon: ((TextFieldParameter) -> Int)?, - val onStartIconClick: View.OnClickListener?, - val onEndIconClick: View.OnClickListener?, - constraints: List + @StringRes description: Int?, default: T, + val startIcon: ((TextFieldParameter) -> Int)?, + val endIcon: ((TextFieldParameter) -> Int)?, + val onStartIconClick: View.OnClickListener?, + val onEndIconClick: View.OnClickListener?, + constraints: List ) : Parameter(name, description, default, constraints) abstract class TextFieldParameterBuilder( @@ -240,12 +294,12 @@ abstract class TextFieldParameterBuilder( ) : ParameterBuilder() class StringParameter(@StringRes name: Int, @StringRes description: Int?, - default: String, - startIcon: ((TextFieldParameter) -> Int)?, - endIcon: ((TextFieldParameter) -> Int)?, - onStartIconClick: View.OnClickListener?, - onEndIconClick: View.OnClickListener?, - constraints: List + default: String, + startIcon: ((TextFieldParameter) -> Int)?, + endIcon: ((TextFieldParameter) -> Int)?, + onStartIconClick: View.OnClickListener?, + onEndIconClick: View.OnClickListener?, + constraints: List ) : TextFieldParameter(name, description, default, startIcon, endIcon, onStartIconClick, onEndIconClick, constraints) @@ -260,14 +314,14 @@ class StringParameterBuilder : TextFieldParameterBuilder() { } class EnumParameter>(@StringRes name: Int, - @StringRes description: Int?, default: T, - startIcon: ((TextFieldParameter) -> Int)?, - endIcon: ((TextFieldParameter) -> Int)?, - onStartIconClick: View.OnClickListener?, - onEndIconClick: View.OnClickListener?, - constraints: List, - val displayName: ((T) -> String)? = null, - val filter: ((T) -> Boolean)? = null + @StringRes description: Int?, default: T, + startIcon: ((TextFieldParameter) -> Int)?, + endIcon: ((TextFieldParameter) -> Int)?, + onStartIconClick: View.OnClickListener?, + onEndIconClick: View.OnClickListener?, + constraints: List, + val displayName: ((T) -> String)? = null, + val filter: ((T) -> Boolean)? = null ) : TextFieldParameter(name, description, default, startIcon, endIcon, onStartIconClick, onEndIconClick, constraints) 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 96e681d0b..7f54fdaac 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 @@ -42,9 +42,7 @@ class TemplateProviderImpl : ITemplateProvider { private val templates = mutableMapOf>() init { - templates().forEach { template -> - templates[template.templateId] = template - } + initializeTemplates() } private fun templates() = @@ -59,6 +57,12 @@ class TemplateProviderImpl : ITemplateProvider { noAndroidXActivityProject(), composeActivityProject() ) + + private fun initializeTemplates() { + templates().forEach { template -> + templates[template.templateId] = template + } + } //@formatter:on override fun getTemplates(): List> { @@ -69,7 +73,12 @@ class TemplateProviderImpl : ITemplateProvider { return templates[templateId] } - override fun clear() { + override fun reload() { + release() + initializeTemplates() + } + + override fun release() { templates.forEach { it.value.release() } templates.clear() } 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 8cd42fab0..b51dc146f 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 @@ -69,13 +69,21 @@ class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider { override fun createView(context: Context, widget: Widget): View { if (widget is ParameterWidget) { - widget.parameter.setValue(widget.parameter.value, false) + widget.parameter.apply { + beforeCreateView() + setValue(value, false) + } } + return when (widget) { is TextFieldWidget -> createTextField(context, widget) is CheckBoxWidget -> createCheckBox(context, widget) is SpinnerWidget -> createSpinner(context, widget) else -> throw IllegalArgumentException("Unknown widget type : $widget") + }.also { + if (widget is ParameterWidget) { + widget.parameter.afterCreateView() + } } }