From d02ffb5e009626a2d77585aca6e30c51e523d16d Mon Sep 17 00:00:00 2001 From: Thomas Breil Date: Thu, 21 Dec 2023 09:08:18 +0100 Subject: [PATCH] Add AutoCompletion, Projects and correction --- build.gradle.kts | 1 - src/main/kotlin/main/Compare.kt | 1 + src/main/kotlin/main/Form.kt | 95 +++++++++++++-- src/main/kotlin/main/Glossary.kt | 70 +++++++---- src/main/kotlin/main/Home.kt | 114 ++++++++++++++++-- src/main/kotlin/main/Main.kt | 39 ++++-- src/main/kotlin/main/projects.kt | 198 +++++++++++++++++++++++++++++++ 7 files changed, 471 insertions(+), 47 deletions(-) create mode 100644 src/main/kotlin/main/projects.kt diff --git a/build.gradle.kts b/build.gradle.kts index 12de536..6dddd18 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,6 @@ dependencies { testImplementation("junit:junit:4.13.1") implementation("org.apache.poi:poi:5.0.0") implementation("org.apache.poi:poi-ooxml:5.0.0") - } diff --git a/src/main/kotlin/main/Compare.kt b/src/main/kotlin/main/Compare.kt index c1671eb..c54be86 100644 --- a/src/main/kotlin/main/Compare.kt +++ b/src/main/kotlin/main/Compare.kt @@ -21,6 +21,7 @@ fun compareResults( codeWords: List, onBackClick: () -> Unit ) { + println(glossaryWords) val commonWords = findCommonWords(glossaryWords, codeWords) Column( diff --git a/src/main/kotlin/main/Form.kt b/src/main/kotlin/main/Form.kt index 22725ca..a674763 100644 --- a/src/main/kotlin/main/Form.kt +++ b/src/main/kotlin/main/Form.kt @@ -1,16 +1,18 @@ package main +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Check -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import kotlinx.serialization.encodeToString @@ -18,10 +20,12 @@ import kotlinx.serialization.json.Json import java.io.File import java.io.IOException +var appState = AppState @OptIn(ExperimentalMaterialApi::class) @Composable fun formPage(glossary: Glossary, onCancelClick: () -> Unit) { // State to track whether to show the snackbar + var appState = AppState val requiredFieldsSnackbarVisibleState = remember { mutableStateOf(false) } val alreadyExistSnackbarVisibleState = remember { mutableStateOf(false) } @@ -83,6 +87,7 @@ fun formPage(glossary: Glossary, onCancelClick: () -> Unit) { ) ) + TextField( value = description.value, onValueChange = { description.value = it }, @@ -94,16 +99,84 @@ fun formPage(glossary: Glossary, onCancelClick: () -> Unit) { ) ) + + var expanded by remember { mutableStateOf(false) } + + val glossaryWords = loadDatasFromFile(glossary.jsonFilePath) + var contextList: List = emptyList() + glossaryWords.forEach { word: Word -> + contextList = contextList + word.mainContext + } + + var listToShow by remember(mainContext.value) { + mutableStateOf(contextList.filter { it.startsWith(mainContext.value, ignoreCase = true) }) + } + + TextField( value = mainContext.value, - onValueChange = { mainContext.value = it }, + onValueChange = { + mainContext.value = it + expanded = true + listToShow = contextList.filter { word -> word.startsWith(mainContext.value, ignoreCase = true) } + + }, label = { Text("Contexte principal *") }, colors = TextFieldDefaults.textFieldColors( focusedIndicatorColor = customRedColor, unfocusedIndicatorColor = Color.Gray, focusedLabelColor = customRedColor ) + + ) + var dropHeight = (listToShow.count() * 60 + 30) + + if (dropHeight > 300){ + dropHeight = 300 + } + + if (expanded && mainContext.value.isNotBlank() && listToShow.isNotEmpty()) { + Box( + modifier = Modifier + .height(dropHeight.dp) + .padding(10.dp) + .border(1.dp, Color.Black) + ) { + val scrollState = rememberLazyListState() + LazyColumn( + state = scrollState, + modifier = Modifier.padding(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + + listToShow.forEach { word -> + + item { + Box( + modifier = Modifier + + .width(300.dp) + .clickable { + mainContext.value = word + expanded = false + } + .border(1.dp, Color.Black) + .height(50.dp) + .padding(10.dp) + .clip(MaterialTheme.shapes.medium) + + + ) { + Text(text = word) + } + } + } + } + } + } + + TextField( value = secondaryContext.value, @@ -219,7 +292,7 @@ fun formPage(glossary: Glossary, onCancelClick: () -> Unit) { backgroundColor = customRedColor, contentColor = Color.White ) - ) { + ) { Text("OK") } } @@ -237,7 +310,7 @@ fun formPage(glossary: Glossary, onCancelClick: () -> Unit) { backgroundColor = customRedColor, contentColor = Color.White ) - ) { + ) { Text("OK") } } @@ -249,6 +322,7 @@ fun formPage(glossary: Glossary, onCancelClick: () -> Unit) { } } + fun resetFields( name: MutableState, description: MutableState, @@ -287,7 +361,7 @@ fun saveDatasInFile(filePath: String, listeWords: List) { val json = Json { prettyPrint = true } try { val content = json.encodeToString(listeWords) - File(filePath).writeText(content) + File(glossaryPath + (appState.selectedProject?.name ) + "/" + filePath).writeText(content) } catch (e: IOException) { e.printStackTrace() } @@ -295,11 +369,12 @@ fun saveDatasInFile(filePath: String, listeWords: List) { fun loadDatasFromFile(filePath: String): List { // If file is empty, return empty list - if (!File(filePath).exists() || File(filePath).length() == 0L) { + + if (!File(glossaryPath + (appState.selectedProject?.name ) + "/" + filePath).exists() || File(glossaryPath + (appState.selectedProject?.name ) + "/" + filePath).length() == 0L) { return emptyList() } return try { - val content = File(filePath).readText() + val content = File(glossaryPath + (appState.selectedProject?.name ) + "/" + filePath).readText() Json.decodeFromString>(content) } catch (e: IOException) { e.printStackTrace() diff --git a/src/main/kotlin/main/Glossary.kt b/src/main/kotlin/main/Glossary.kt index b82b01b..54882c4 100644 --- a/src/main/kotlin/main/Glossary.kt +++ b/src/main/kotlin/main/Glossary.kt @@ -85,7 +85,7 @@ fun glossaryPage( onClick = { importedSuccessfully.value = false frame.dispose() - }, + }, colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, contentColor = Color.White @@ -95,9 +95,33 @@ fun glossaryPage( } } ) + } - Button( + if (errorImportation.value) { + AlertDialog( + onDismissRequest = { errorImportation.value = false }, + title = { Text("Importation Echouée") }, + text = { Text("Le fichier n'a pas pu être importé") }, + confirmButton = { + Button( + onClick = { + errorImportation.value = false + frame.dispose() + }, + colors = ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text("OK") + } + } + ) + } + + + Button( onClick = onExportClick, modifier = Modifier .width(300.dp), @@ -159,6 +183,7 @@ fun glossaryPage( } var importedSuccessfully = mutableStateOf(false) +var errorImportation = mutableStateOf(false) var exportedSuccessfully = mutableStateOf(false) fun selectFile(extensions: Set, onFileSelected: (String) -> Unit) { @@ -243,26 +268,31 @@ fun importCSVFile(glossary : Glossary, filePath: String) { println(value) } val mot = header.zip(values).toMap().toMutableMap() - val nouveauWord = Word( - name = mot["Mot"]!!, - description = mot["Description"]!!, - mainContext = mot["Contexte principal"]!!, - secondaryContext = mot["Contexte 2"]!!, - relatedTo = mot["Lié à"]!!, - synonymous = mot["Synonyme"]!!, - antonym = mot["Antonyme"]!! - ) + try { + val nouveauWord = Word( + name = mot["Mot"]!!, + description = mot["Description"]!!, + mainContext = mot["Contexte principal"]!!, + secondaryContext = mot["Contexte 2"]!!, + relatedTo = mot["Lié à"]!!, + synonymous = mot["Synonyme"]!!, + antonym = mot["Antonyme"]!! + ) - if (mot["Antonyme"] == "\r") { - mot["Antonyme"] = "" + if (mot["Antonyme"] == "\r") { + mot["Antonyme"] = "" + } + if (mot["Mot"] == null || mot["Mot"] == "") { + return + } + if (!verifyRequiredFields(mot)) { + return + } + addWordToGlossary(glossary.jsonFilePath, nouveauWord) + }catch(e : NullPointerException){ + errorImportation.value = true } - if (mot["Mot"] == null || mot["Mot"] == "") { - return - } - if (!verifyRequiredFields(mot)) { - return - } - addWordToGlossary(glossary.jsonFilePath, nouveauWord) + } importedSuccessfully.value = true } diff --git a/src/main/kotlin/main/Home.kt b/src/main/kotlin/main/Home.kt index b92c6b0..14d34d7 100644 --- a/src/main/kotlin/main/Home.kt +++ b/src/main/kotlin/main/Home.kt @@ -22,14 +22,16 @@ enum class Language { @Composable fun homePage( languageManager: LanguageManager, - onGlossaryClick: () -> Unit, + onProjectClick: () -> Unit, onCodeToVerifyClick: () -> Unit ) { + val appState = AppState val noFileSnackbarVisibleState = remember { mutableStateOf(false) } var isCompareClicked by remember { mutableStateOf(false) } var selectedGlossary by remember { mutableStateOf(null) } + if (!isCompareClicked && selectedGlossary == null) { // Utilisez un Box pour placer le drapeau en haut à droite Box( @@ -61,7 +63,7 @@ fun homePage( horizontalArrangement = Arrangement.spacedBy(16.dp) ) { Button( - onClick = onGlossaryClick, + onClick = onProjectClick, modifier = Modifier.width(200.dp), colors = ButtonDefaults.buttonColors( backgroundColor = customRedColor, @@ -124,24 +126,44 @@ fun homePage( } } } + var selectedProject by remember { mutableStateOf(null) } - if (isCompareClicked && selectedGlossary == null) { - GlossaryList(glossaries = loadGlossaries()) { glossary -> - selectedGlossary = glossary - } + + if (isCompareClicked && selectedProject == null && selectedGlossary == null) { + ProjectList( + projects = loadProjects(), + onProjectSelected = { project -> + selectedProject = project + appState.selectedProject = selectedProject + println("selectedProject = $selectedProject") + }, + onBackClick = { isCompareClicked = false; selectedGlossary = null; selectedProject = null } + ) + + } else if (isCompareClicked && selectedGlossary == null) { + + GlossaryList( + glossaries = loadGlossaries(appState.selectedProject!!), + onGlossarySelected = { glossary -> + selectedGlossary = glossary + println("selectedGlossary = $selectedGlossary") + }, + onBackClick = { isCompareClicked = false; selectedGlossary = null; selectedProject = null } + ) } else if (isCompareClicked) { selectedGlossary?.let { compareResults( glossaryWords = loadDatasFromFile(it.jsonFilePath), codeWords = mostUsedWordList.keys.toList(), - onBackClick = { isCompareClicked = false; selectedGlossary = null } + onBackClick = { isCompareClicked = false; selectedGlossary = null; selectedProject = null } ) } + } } @Composable -fun GlossaryList(glossaries: List, onGlossarySelected: (Glossary) -> Unit) { +fun GlossaryList(glossaries: List, onGlossarySelected: (Glossary) -> Unit, onBackClick: () -> Unit) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, @@ -190,5 +212,81 @@ fun GlossaryList(glossaries: List, onGlossarySelected: (Glossary) -> U ) Spacer(modifier = Modifier.height(6.dp)) } + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = onBackClick, + colors = androidx.compose.material.ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text("Retour") + } + } +} + +@Composable +fun ProjectList(projects: List, onProjectSelected: (Project) -> Unit, onBackClick: () -> Unit) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Sélectionnez un Projet", style = MaterialTheme.typography.h5) + + Spacer(modifier = Modifier.height(16.dp)) + + Box( + modifier = Modifier + .height(500.dp) + .padding(10.dp) + ) { + val scrollState = rememberLazyListState() + LazyColumn( + state = scrollState, + modifier = Modifier.padding(10.dp).border(1.dp, Color.Black), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + items(projects) { project: Project -> + Row( + modifier = Modifier.width(200.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button( + onClick = { + onProjectSelected(project) + }, + modifier = Modifier + .width(150.dp) + .padding(10.dp, 0.dp, 0.dp, 0.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text(project.name) + } + } + } + } + VerticalScrollbar( + modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), + adapter = rememberScrollbarAdapter(scrollState) + ) + Spacer(modifier = Modifier.height(6.dp)) + } + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = onBackClick, + colors = androidx.compose.material.ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text("Retour") + } + } } diff --git a/src/main/kotlin/main/Main.kt b/src/main/kotlin/main/Main.kt index d0d3f91..82cd94f 100644 --- a/src/main/kotlin/main/Main.kt +++ b/src/main/kotlin/main/Main.kt @@ -27,6 +27,13 @@ import java.util.* val customRedColor = Color(0xFFB70D1B) val currentPage = mutableStateOf("accueil") +val glossaryPath : String = "src/main/resources/projects/" + +// Classe pour stocker l'état global +object AppState { + var selectedProject: Project? = null +} + @OptIn(ExperimentalMaterialApi::class) @Composable @Preview @@ -41,21 +48,22 @@ fun app() { var selectedGlossary by remember { mutableStateOf(null) } + val appState = AppState + val isEmptySnackbarVisibleState = remember { mutableStateOf(false) } val containsSpaceSnackbarVisibleState = remember { mutableStateOf(false) } val glossaryAlreadyExistsSnackbarVisibleState = remember { mutableStateOf(false) } - //Récupérer tous les glossaires à la racine du projet et les ajouter à la liste glossaries - glossaries = loadGlossaries() + when (currentPage.value) { "accueil" -> { homePage( languageManager, - onGlossaryClick = { currentPage.value = "glossaires" }, + onProjectClick = { currentPage.value = "projects" }, onCodeToVerifyClick = { currentPage.value = "choixLangage" }, ) } @@ -82,6 +90,8 @@ fun app() { modifier = Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp) ) { + glossaries = loadGlossaries(appState.selectedProject!!) + println(glossaries) items(glossaries) { glossary -> Row( modifier = Modifier.width(200.dp).fillMaxWidth(), @@ -106,7 +116,7 @@ fun app() { onClick = { // Handle delete glossary action glossaries = glossaries.filterNot { it == glossary } - val file = File(glossary.jsonFilePath) + val file = File(glossaryPath + (appState.selectedProject?.name ) + "/" + glossary.jsonFilePath) file.delete() } ) { @@ -208,10 +218,10 @@ fun app() { val newGlossary = Glossary(nouveauGlossaireName, "$nouveauGlossaireName.json") glossaries = glossaries + newGlossary //create new json file - val newFile = File(newGlossary.jsonFilePath) + val newFile = File(glossaryPath + (appState.selectedProject?.name ) + "/" + newGlossary.jsonFilePath) newFile.createNewFile() //update glossaries list - glossaries = loadGlossaries() + glossaries = loadGlossaries(appState.selectedProject!!) currentPage.value = "glossaires" // Revenir à la liste des glossaires }, modifier = Modifier @@ -240,6 +250,18 @@ fun app() { } } + "projects" -> { + projectsPage( + currentPage = currentPage + ) + } + + "nouveauProjet" ->{ + newProject( + currentPage = currentPage + ) + } + "glossaireOptions" -> { glossaryPage( languageManager, @@ -398,10 +420,10 @@ fun app() { } } -fun loadGlossaries(): List { +fun loadGlossaries(project: Project): List { val glossaries = mutableListOf() //Récupérer tous les fichiers json à la racine du projet - val glossaryFiles = File(".").listFiles { file -> + val glossaryFiles = File(glossaryPath + project.name).listFiles { file -> file.extension == "json" } glossaryFiles?.forEach { file -> @@ -413,6 +435,7 @@ fun loadGlossaries(): List { } + fun main() = application { val state = rememberWindowState( placement = WindowPlacement.Floating, diff --git a/src/main/kotlin/main/projects.kt b/src/main/kotlin/main/projects.kt new file mode 100644 index 0000000..2fa023f --- /dev/null +++ b/src/main/kotlin/main/projects.kt @@ -0,0 +1,198 @@ +package main + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import java.io.File + +data class Project(val name: String) + +fun loadProjects(): List { + val projectsDirectory = File("src/main/resources/projects/") + + return projectsDirectory.listFiles()?.map { Project(it.name) } ?: emptyList() +} + +@Composable +fun projectsPage( + currentPage: MutableState +) { + val appState = AppState + var selectedProject: Project? by remember { mutableStateOf(null) } + var projects: List by remember { mutableStateOf(loadProjects()) } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Sélectionnez un projet", style = MaterialTheme.typography.h5) + + Spacer(modifier = Modifier.height(16.dp)) + + LazyColumn( + modifier = Modifier.padding(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + items(projects, key = { project -> project.name }) { project -> + Row( + modifier = Modifier.width(200.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button( + onClick = { + appState.selectedProject = project + currentPage.value = "glossaires" + + }, + modifier = Modifier + .width(150.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text(project.name) + } + + IconButton( + onClick = { + // Handle delete project action + projects = projects.filterNot { it.name == project.name } + println(projects) + val directory = File("src/main/resources/projects/${project.name}/") + directory.deleteRecursively() + currentPage.value = "projects" + } + ) { + Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete Project") + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { + currentPage.value = "nouveauProjet" + }, + modifier = Modifier + .width(300.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text("Créer un nouveau project") + } + + Spacer(modifier = Modifier.height(6.dp)) + + Button( + onClick = { + currentPage.value = "accueil" + }, + modifier = Modifier + .width(250.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text("Retour") + } + } +} + +@Composable +fun mySuperCoolButton( + project: Project, +){ + var projects: List = loadProjects() + + IconButton( + onClick = { + // Handle delete glossary action + projects = projects.filterNot { it == project } + val directory = File("src/main/resources/projects/${project.name}/") + directory.deleteRecursively() + + } + ) { + Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete Project") + } +} + +@Composable +fun newProject( + currentPage: MutableState +){ + var projectName by remember { mutableStateOf("") } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ){ + Text("Nom du projet", style = MaterialTheme.typography.h5) + + Spacer(modifier = Modifier.height(16.dp)) + + TextField( + value = projectName, + onValueChange = { projectName = it }, + label = { Text("Nom du projet") }, + modifier = Modifier + .width(300.dp) + .fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { + val directory = File("src/main/resources/projects/$projectName/") + directory.mkdirs() + println("Project $projectName created") + currentPage.value = "projects" + }, + modifier = Modifier + .width(300.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text("Créer") + } + + Spacer(modifier = Modifier.height(6.dp)) + + Button( + onClick = { + currentPage.value = "projects" + }, + modifier = Modifier + .width(250.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = customRedColor, + contentColor = Color.White + ) + ) { + Text("Retour") + } + } +} + + + +