fix(templates): project name is not properly updated if project with same name already exists

This commit is contained in:
Akash Yadav
2023-08-27 13:50:04 +05:30
parent c513f44e8a
commit 90812edcf9
8 changed files with 146 additions and 49 deletions
@@ -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
}
@@ -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) {
@@ -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<FragmentTemplateListBinding>(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Show only project templates
val templates = ITemplateProvider.getInstance(true)
.getTemplates()
.filterIsInstance<ProjectTemplate>()
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<FragmentTemplateListBinding>(
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<ProjectTemplate>()
adapter = TemplateListAdapter(templates) { template, _ ->
viewModel.template.value = template
viewModel.setScreen(MainViewModel.SCREEN_TEMPLATE_DETAILS)
}
binding.list.adapter = adapter
}
}
@@ -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()
}
@@ -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),
@@ -81,14 +81,17 @@ enum class ParameterConstraint {
}
abstract class Parameter<T>(@StringRes val name: Int,
@StringRes val description: Int?, val default: T,
var constraints: List<ParameterConstraint>
@StringRes val description: Int?, val default: T,
var constraints: List<ParameterConstraint>
) {
private val observers = hashSetOf<Observer<T>>()
private val lock = ReentrantLock()
private var _value: T? = null
private var actionBeforeCreateView: ((Parameter<T>) -> Unit)? = null
private var actionAfterCreateView: ((Parameter<T>) -> Unit)? = null
/**
* The value of this parameter.
*/
@@ -109,6 +112,16 @@ abstract class Parameter<T>(@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<T>(@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<T>) -> 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<T>) -> 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<T> {
}
class BooleanParameter(@StringRes name: Int, @StringRes description: Int?,
default: Boolean, constraints: List<ParameterConstraint>
default: Boolean, constraints: List<ParameterConstraint>
) : Parameter<Boolean>(name, description, default, constraints)
class BooleanParameterBuilder : ParameterBuilder<Boolean>() {
@@ -224,12 +278,12 @@ class BooleanParameterBuilder : ParameterBuilder<Boolean>() {
* clicked. Click listener to the icon will be set on if this is non-null.
*/
abstract class TextFieldParameter<T>(@StringRes name: Int,
@StringRes description: Int?, default: T,
val startIcon: ((TextFieldParameter<T>) -> Int)?,
val endIcon: ((TextFieldParameter<T>) -> Int)?,
val onStartIconClick: View.OnClickListener?,
val onEndIconClick: View.OnClickListener?,
constraints: List<ParameterConstraint>
@StringRes description: Int?, default: T,
val startIcon: ((TextFieldParameter<T>) -> Int)?,
val endIcon: ((TextFieldParameter<T>) -> Int)?,
val onStartIconClick: View.OnClickListener?,
val onEndIconClick: View.OnClickListener?,
constraints: List<ParameterConstraint>
) : Parameter<T>(name, description, default, constraints)
abstract class TextFieldParameterBuilder<T>(
@@ -240,12 +294,12 @@ abstract class TextFieldParameterBuilder<T>(
) : ParameterBuilder<T>()
class StringParameter(@StringRes name: Int, @StringRes description: Int?,
default: String,
startIcon: ((TextFieldParameter<String>) -> Int)?,
endIcon: ((TextFieldParameter<String>) -> Int)?,
onStartIconClick: View.OnClickListener?,
onEndIconClick: View.OnClickListener?,
constraints: List<ParameterConstraint>
default: String,
startIcon: ((TextFieldParameter<String>) -> Int)?,
endIcon: ((TextFieldParameter<String>) -> Int)?,
onStartIconClick: View.OnClickListener?,
onEndIconClick: View.OnClickListener?,
constraints: List<ParameterConstraint>
) : TextFieldParameter<String>(name, description, default, startIcon, endIcon,
onStartIconClick, onEndIconClick, constraints)
@@ -260,14 +314,14 @@ class StringParameterBuilder : TextFieldParameterBuilder<String>() {
}
class EnumParameter<T : Enum<*>>(@StringRes name: Int,
@StringRes description: Int?, default: T,
startIcon: ((TextFieldParameter<T>) -> Int)?,
endIcon: ((TextFieldParameter<T>) -> Int)?,
onStartIconClick: View.OnClickListener?,
onEndIconClick: View.OnClickListener?,
constraints: List<ParameterConstraint>,
val displayName: ((T) -> String)? = null,
val filter: ((T) -> Boolean)? = null
@StringRes description: Int?, default: T,
startIcon: ((TextFieldParameter<T>) -> Int)?,
endIcon: ((TextFieldParameter<T>) -> Int)?,
onStartIconClick: View.OnClickListener?,
onEndIconClick: View.OnClickListener?,
constraints: List<ParameterConstraint>,
val displayName: ((T) -> String)? = null,
val filter: ((T) -> Boolean)? = null
) : TextFieldParameter<T>(name, description, default, startIcon, endIcon,
onStartIconClick, onEndIconClick, constraints)
@@ -42,9 +42,7 @@ class TemplateProviderImpl : ITemplateProvider {
private val templates = mutableMapOf<String, Template<*>>()
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<Template<*>> {
@@ -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()
}
@@ -69,13 +69,21 @@ class TemplateWidgetViewProviderImpl : ITemplateWidgetViewProvider {
override fun <T> createView(context: Context, widget: Widget<T>): View {
if (widget is ParameterWidget<T>) {
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<T>) {
widget.parameter.afterCreateView()
}
}
}