mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(templates): project name is not properly updated if project with same name already exists
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
+13
-4
@@ -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()
|
||||
}
|
||||
|
||||
+9
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user