Skip to content

Commit 9396fc9

Browse files
committed
Add feature template management with creation, editing, and deletion functionality
1 parent 38e974e commit 9396fc9

8 files changed

Lines changed: 847 additions & 104 deletions

File tree

src/main/kotlin/com/github/cnrture/quickprojectwizard/common/Utils.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.github.cnrture.quickprojectwizard.common.file.FileWriter
44
import com.github.cnrture.quickprojectwizard.common.file.ImportAnalyzer
55
import com.github.cnrture.quickprojectwizard.common.file.LibraryDependencyFinder
66
import com.github.cnrture.quickprojectwizard.data.ModuleTemplate
7+
import com.github.cnrture.quickprojectwizard.data.FeatureTemplate
78
import com.github.cnrture.quickprojectwizard.dialog.MessageDialog
89
import com.intellij.ide.BrowserUtil
910
import com.intellij.ide.starters.local.GeneratorTemplateFile
@@ -30,7 +31,13 @@ object Utils {
3031
fun validateFeatureInput(featureName: String, selectedSrc: String): Boolean =
3132
featureName.isNotEmpty() && selectedSrc != Constants.DEFAULT_SRC_VALUE
3233

33-
fun createFeature(project: Project, selectedSrc: String, featureName: String, fileWriter: FileWriter) {
34+
fun createFeature(
35+
project: Project,
36+
selectedSrc: String,
37+
featureName: String,
38+
fileWriter: FileWriter,
39+
selectedTemplate: FeatureTemplate? = null,
40+
) {
3441
try {
3542
val projectRoot = project.rootDirectoryString()
3643

@@ -56,7 +63,8 @@ object Utils {
5663
MessageDialog("Success").show()
5764
val currentlySelectedFile = project.getCurrentlySelectedFile(selectedSrc)
5865
listOf(currentlySelectedFile).refreshFileSystem()
59-
}
66+
},
67+
selectedTemplate = selectedTemplate
6068
)
6169
} catch (e: Exception) {
6270
MessageDialog("Error: ${e.message}").show()

src/main/kotlin/com/github/cnrture/quickprojectwizard/common/file/FileWriter.kt

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package com.github.cnrture.quickprojectwizard.common.file
22

33
import com.github.cnrture.quickprojectwizard.common.Constants
44
import com.github.cnrture.quickprojectwizard.data.ModuleTemplate
5+
import com.github.cnrture.quickprojectwizard.data.FeatureTemplate
6+
import com.github.cnrture.quickprojectwizard.data.FileTemplate
57
import com.github.cnrture.quickprojectwizard.data.SettingsService
68
import com.github.cnrture.quickprojectwizard.projectwizard.xmlarch.ui.emptyFragmentLayout
79
import com.github.cnrture.quickprojectwizard.projectwizard.xmlarch.ui.emptyMainFragment
810
import com.github.cnrture.quickprojectwizard.projectwizard.xmlarch.ui.emptyMainUIState
911
import com.github.cnrture.quickprojectwizard.projectwizard.xmlarch.ui.emptyMainViewModelXML
10-
import com.github.cnrture.quickprojectwizard.toolwindow.template.FeatureTemplate
12+
import com.github.cnrture.quickprojectwizard.toolwindow.template.FeatureTemplate as FeatureTemplateOld
1113
import com.github.cnrture.quickprojectwizard.toolwindow.template.GitIgnoreTemplate
1214
import com.github.cnrture.quickprojectwizard.toolwindow.template.ManifestTemplate
1315
import com.github.cnrture.quickprojectwizard.toolwindow.template.TemplateWriter
@@ -168,7 +170,19 @@ class FileWriter() {
168170
packageName: String,
169171
showErrorDialog: (String) -> Unit,
170172
showSuccessDialog: () -> Unit,
173+
selectedTemplate: FeatureTemplate? = null,
171174
): List<File> {
175+
if (selectedTemplate != null) {
176+
return createFeatureFilesFromTemplate(
177+
file = file,
178+
featureName = featureName,
179+
packageName = packageName,
180+
template = selectedTemplate,
181+
showErrorDialog = showErrorDialog,
182+
showSuccessDialog = showSuccessDialog
183+
)
184+
}
185+
172186
val featureFile = Paths.get(file.absolutePath, featureName.lowercase()).toFile()
173187
featureFile.mkdirs()
174188

@@ -203,7 +217,7 @@ class FileWriter() {
203217
val writer: Writer = FileWriter(file)
204218
val dataToWrite = when (file.name) {
205219
"${capitalizedModuleName}Screen.kt" -> {
206-
FeatureTemplate.getScreen(packageName, capitalizedModuleName)
220+
FeatureTemplateOld.getScreen(packageName, capitalizedModuleName)
207221
}
208222

209223
"${capitalizedModuleName}Fragment.kt" -> {
@@ -212,7 +226,7 @@ class FileWriter() {
212226

213227
"${capitalizedModuleName}ViewModel.kt" -> {
214228
if (settings.state.isCompose) {
215-
FeatureTemplate.getViewModel(
229+
FeatureTemplateOld.getViewModel(
216230
packageName,
217231
capitalizedModuleName,
218232
settings.state.isHiltEnable,
@@ -227,15 +241,15 @@ class FileWriter() {
227241
}
228242

229243
"${capitalizedModuleName}Contract.kt" -> {
230-
FeatureTemplate.getContract(packageName, capitalizedModuleName)
244+
FeatureTemplateOld.getContract(packageName, capitalizedModuleName)
231245
}
232246

233247
"${capitalizedModuleName}UiState.kt" -> {
234248
emptyMainUIState(packageName, capitalizedModuleName)
235249
}
236250

237251
"${capitalizedModuleName}ScreenPreviewProvider.kt" -> {
238-
FeatureTemplate.getPreviewProvider(packageName, capitalizedModuleName)
252+
FeatureTemplateOld.getPreviewProvider(packageName, capitalizedModuleName)
239253
}
240254

241255
"app/src/main/res/layout/fragment$xmlName.xml" -> {
@@ -267,6 +281,63 @@ class FileWriter() {
267281
return successfullyCreatedFiles
268282
}
269283

284+
private fun createFeatureFilesFromTemplate(
285+
file: File,
286+
featureName: String,
287+
packageName: String,
288+
template: FeatureTemplate,
289+
showErrorDialog: (String) -> Unit,
290+
showSuccessDialog: () -> Unit,
291+
): List<File> {
292+
val featureFile = Paths.get(file.absolutePath, featureName.lowercase()).toFile()
293+
featureFile.mkdirs()
294+
295+
val filesCreated = mutableListOf<File>()
296+
297+
template.fileTemplates.forEach { fileTemplate: FileTemplate ->
298+
val fileName = fileTemplate.fileName
299+
.replace("{FEATURE_NAME}", featureName.replaceFirstChar { it.uppercase() })
300+
.replace("{FEATURE_NAME_LOWERCASE}", featureName.lowercase())
301+
302+
val fileDir = if (fileTemplate.filePath.isNotEmpty()) {
303+
val subDirPath = fileTemplate.filePath.replace(".", File.separator)
304+
Paths.get(featureFile.absolutePath, subDirPath).toFile().apply { mkdirs() }
305+
} else {
306+
featureFile
307+
}
308+
309+
val targetFile = File(fileDir, fileName)
310+
311+
val content = fileTemplate.fileContent
312+
.replace("{FEATURE_NAME}", featureName.replaceFirstChar { it.uppercase() })
313+
.replace("{FEATURE_NAME_LOWERCASE}", featureName.lowercase())
314+
.replace("{PACKAGE}", packageName)
315+
.replace(
316+
"{FILE_PACKAGE}",
317+
if (fileTemplate.filePath.isNotEmpty()) {
318+
"$packageName.${fileTemplate.filePath}"
319+
} else {
320+
packageName
321+
}
322+
)
323+
324+
try {
325+
val writer: Writer = FileWriter(targetFile)
326+
writer.write(content)
327+
writer.flush()
328+
writer.close()
329+
filesCreated.add(targetFile)
330+
} catch (e: IOException) {
331+
showErrorDialog("Error creating file ${fileName}: ${e.message}")
332+
} catch (e: Exception) {
333+
showErrorDialog("Unexpected error: ${e.message}")
334+
}
335+
}
336+
337+
showSuccessDialog()
338+
return filesCreated
339+
}
340+
270341
private fun createDefaultPackages(moduleFile: File, packageName: String): List<File> {
271342
fun makePath(srcPath: File, packagePath: String): File {
272343
val packagePathFile = Paths.get(

src/main/kotlin/com/github/cnrture/quickprojectwizard/data/SettingsService.kt

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,24 @@ class SettingsService : PersistentStateComponent<SettingsState> {
2727
fun removeTemplate(template: ModuleTemplate) {
2828
myState.moduleTemplates.removeAll { it.id == template.id }
2929
}
30+
31+
fun saveFeatureTemplate(template: FeatureTemplate) {
32+
val existingIndex = myState.featureTemplates.indexOfFirst { it.id == template.id }
33+
if (existingIndex != -1) {
34+
myState.featureTemplates[existingIndex] = template
35+
} else {
36+
myState.featureTemplates.add(template)
37+
}
38+
}
39+
40+
fun removeFeatureTemplate(template: FeatureTemplate) {
41+
myState.featureTemplates.removeAll { it.id == template.id }
42+
}
3043
}
3144

3245
data class SettingsState(
3346
var moduleTemplates: MutableList<ModuleTemplate> = mutableListOf(),
47+
var featureTemplates: MutableList<FeatureTemplate> = mutableListOf(),
3448
var defaultPackageName: String = Constants.DEFAULT_BASE_PACKAGE_NAME,
3549
var preferredModuleType: String = Constants.ANDROID,
3650
var featureScreenTemplate: String = Constants.EMPTY,
@@ -61,3 +75,166 @@ data class FileTemplate(
6175
val fileContent: String,
6276
val fileType: String,
6377
)
78+
79+
data class FeatureTemplate(
80+
val id: String,
81+
val name: String,
82+
val fileTemplates: List<FileTemplate>,
83+
val isDefault: Boolean = false,
84+
)
85+
86+
fun getDefaultModuleTemplates(): List<ModuleTemplate> {
87+
return listOf(
88+
ModuleTemplate(
89+
id = "candroid_template",
90+
name = "Candroid's Template",
91+
fileTemplates = listOf(
92+
FileTemplate(
93+
fileName = "Repository.kt",
94+
filePath = "domain/repository",
95+
fileContent = "interface {{MODULE_NAME}}Repository {\n // Define methods here\n}",
96+
fileType = "kt"
97+
),
98+
),
99+
isDefault = true,
100+
),
101+
)
102+
}
103+
104+
fun getDefaultFeatureTemplates(): List<FeatureTemplate> {
105+
return listOf(
106+
FeatureTemplate(
107+
id = "candroid_template",
108+
name = "Compose MVVM Template",
109+
fileTemplates = listOf(
110+
FileTemplate(
111+
fileName = "{FEATURE_NAME}Screen.kt",
112+
filePath = "",
113+
fileContent = """package {PACKAGE}
114+
115+
import androidx.compose.foundation.layout.*
116+
import androidx.compose.material3.*
117+
import androidx.compose.runtime.*
118+
import androidx.compose.ui.Alignment
119+
import androidx.compose.ui.Modifier
120+
import androidx.compose.ui.unit.dp
121+
import androidx.hilt.navigation.compose.hiltViewModel
122+
123+
@Composable
124+
fun {FEATURE_NAME}Screen(
125+
viewModel: {FEATURE_NAME}ViewModel = hiltViewModel()
126+
) {
127+
val uiState by viewModel.uiState.collectAsState()
128+
129+
{FEATURE_NAME}Content(
130+
uiState = uiState,
131+
onEvent = viewModel::onEvent
132+
)
133+
}
134+
135+
@Composable
136+
private fun {FEATURE_NAME}Content(
137+
uiState: {FEATURE_NAME}UiState,
138+
onEvent: ({FEATURE_NAME}Event) -> Unit
139+
) {
140+
Column(
141+
modifier = Modifier
142+
.fillMaxSize()
143+
.padding(16.dp),
144+
horizontalAlignment = Alignment.CenterHorizontally,
145+
verticalArrangement = Arrangement.Center
146+
) {
147+
Text(
148+
text = "{FEATURE_NAME} Screen",
149+
style = MaterialTheme.typography.headlineMedium
150+
)
151+
}
152+
}
153+
""",
154+
fileType = "kt"
155+
),
156+
FileTemplate(
157+
fileName = "{FEATURE_NAME}ViewModel.kt",
158+
filePath = "",
159+
fileContent = """package {PACKAGE}
160+
161+
import androidx.lifecycle.ViewModel
162+
import dagger.hilt.android.lifecycle.HiltViewModel
163+
import kotlinx.coroutines.flow.MutableStateFlow
164+
import kotlinx.coroutines.flow.StateFlow
165+
import kotlinx.coroutines.flow.asStateFlow
166+
import javax.inject.Inject
167+
168+
@HiltViewModel
169+
class {FEATURE_NAME}ViewModel @Inject constructor() : ViewModel() {
170+
171+
private val _uiState = MutableStateFlow({FEATURE_NAME}UiState())
172+
val uiState: StateFlow<{FEATURE_NAME}UiState> = _uiState.asStateFlow()
173+
174+
fun onEvent(event: {FEATURE_NAME}Event) {
175+
when (event) {
176+
// Handle events
177+
}
178+
}
179+
}
180+
""",
181+
fileType = "kt"
182+
),
183+
FileTemplate(
184+
fileName = "{FEATURE_NAME}Contract.kt",
185+
filePath = "",
186+
fileContent = """package {PACKAGE}
187+
188+
data class {FEATURE_NAME}UiState(
189+
val isLoading: Boolean = false,
190+
val error: String? = null
191+
)
192+
193+
sealed class {FEATURE_NAME}Event {
194+
// Define events here
195+
}
196+
""",
197+
fileType = "kt"
198+
),
199+
),
200+
isDefault = true,
201+
),
202+
FeatureTemplate(
203+
id = "simple_screen_template",
204+
name = "Simple Screen Template",
205+
fileTemplates = listOf(
206+
FileTemplate(
207+
fileName = "{FEATURE_NAME}Screen.kt",
208+
filePath = "",
209+
fileContent = """package {PACKAGE}
210+
211+
import androidx.compose.foundation.layout.*
212+
import androidx.compose.material3.*
213+
import androidx.compose.runtime.*
214+
import androidx.compose.ui.Alignment
215+
import androidx.compose.ui.Modifier
216+
import androidx.compose.ui.unit.dp
217+
218+
@Composable
219+
fun {FEATURE_NAME}Screen() {
220+
Column(
221+
modifier = Modifier
222+
.fillMaxSize()
223+
.padding(16.dp),
224+
horizontalAlignment = Alignment.CenterHorizontally,
225+
verticalArrangement = Arrangement.Center
226+
) {
227+
Text(
228+
text = "{FEATURE_NAME} Screen",
229+
style = MaterialTheme.typography.headlineMedium
230+
)
231+
}
232+
}
233+
""",
234+
fileType = "kt"
235+
),
236+
),
237+
isDefault = false,
238+
),
239+
)
240+
}

0 commit comments

Comments
 (0)