From 35fa01b8311f3ff8fa8881ab58945ba00b3aa638 Mon Sep 17 00:00:00 2001 From: Emma Vandewalle Date: Thu, 21 Nov 2024 20:53:03 +0000 Subject: [PATCH] File menu --- app/build.gradle | 2 +- .../be/re/writand/navigation/WNavGraph.kt | 4 +- .../re/writand/screens/editor/EditorScreen.kt | 4 +- .../screens/editor/bottom/BottomEditorBar.kt | 66 ++++- .../projectpicker/ProjectPickerComponent.kt | 257 ++++++++++++++++++ .../projectpicker/ProjectPickerScreen.kt | 234 +--------------- .../projectpicker/ProjectPickerViewModel.kt | 2 +- build.gradle | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 338 insertions(+), 241 deletions(-) create mode 100644 app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerComponent.kt diff --git a/app/build.gradle b/app/build.gradle index 30da443..2872edb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.5.0' + kotlinCompilerExtensionVersion '1.5.7' } packagingOptions { resources { diff --git a/app/src/main/java/be/re/writand/navigation/WNavGraph.kt b/app/src/main/java/be/re/writand/navigation/WNavGraph.kt index 5c8d0c5..a33da9e 100644 --- a/app/src/main/java/be/re/writand/navigation/WNavGraph.kt +++ b/app/src/main/java/be/re/writand/navigation/WNavGraph.kt @@ -63,13 +63,13 @@ fun WNavGraph(navHostController: NavHostController, modifier: Modifier) { // Editor view composable(WAppDestinations.MAIN_EDITOR + "/{projectId}/{path}") { entry -> - val projectId: Int? = entry.arguments?.getInt("projectId") + val projectId: String? = entry.arguments?.getString("projectId") val path: String? = entry.arguments?.getString("path")?.let { URLDecoder.decode(it, "UTF-8") } if (projectId != null) { EditorScreen( navHostController = navHostController, - projectId = projectId, + projectId = projectId.toInt(), path = path ?: "./" ) } diff --git a/app/src/main/java/be/re/writand/screens/editor/EditorScreen.kt b/app/src/main/java/be/re/writand/screens/editor/EditorScreen.kt index 046a142..332518f 100644 --- a/app/src/main/java/be/re/writand/screens/editor/EditorScreen.kt +++ b/app/src/main/java/be/re/writand/screens/editor/EditorScreen.kt @@ -44,7 +44,9 @@ fun EditorScreen( } }, bottomBar = { - BottomEditorBar() + BottomEditorBar( + navHostController = navHostController + ) } ) { Row(modifier = Modifier.padding(it).padding(10.dp)) { diff --git a/app/src/main/java/be/re/writand/screens/editor/bottom/BottomEditorBar.kt b/app/src/main/java/be/re/writand/screens/editor/bottom/BottomEditorBar.kt index 634614f..fa4d688 100644 --- a/app/src/main/java/be/re/writand/screens/editor/bottom/BottomEditorBar.kt +++ b/app/src/main/java/be/re/writand/screens/editor/bottom/BottomEditorBar.kt @@ -1,17 +1,44 @@ package be.re.writand.screens.editor.bottom +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.ExitToApp import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import be.re.writand.R +import be.re.writand.screens.WUIGlobals +import be.re.writand.screens.components.WButton +import be.re.writand.screens.components.WPopup import be.re.writand.screens.components.WText +import be.re.writand.screens.projectpicker.ProjectPickerComponent +import be.re.writand.screens.projectpicker.ProjectPickerViewModel /** * Bar component that represents the bottom of the editor itself. @@ -19,20 +46,55 @@ import be.re.writand.screens.components.WText */ @Composable fun BottomEditorBar( - vM: BottomEditorBarViewModel = hiltViewModel() + navHostController: NavHostController, + vM: BottomEditorBarViewModel = hiltViewModel(), ) { val row = vM.row.collectAsState() val column = vM.column.collectAsState() + val openFileMenu = remember { mutableStateOf(false) } + Column { HorizontalDivider() Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 15.dp, vertical = 15.dp), - horizontalArrangement = Arrangement.End + horizontalArrangement = Arrangement.SpaceBetween ) { + Box( + modifier = Modifier + .sizeIn(minWidth = 50.dp) + .clickable { openFileMenu.value = true }, + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Home, + contentDescription = "Open File Menu", + modifier = Modifier + .size(WUIGlobals.iconSize) + .padding(end = 5.dp) + ) + } WText(text = "${row.value}:${column.value}") } } + + if (openFileMenu.value) { + WPopup( + titleBar = {}, + width = 600.dp, + height = 500.dp, + onDismiss = { openFileMenu.value = false }) { + Box( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.primary), + contentAlignment = Alignment.Center + ) { + ProjectPickerComponent( + navHostController = navHostController + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerComponent.kt b/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerComponent.kt new file mode 100644 index 0000000..55c7d91 --- /dev/null +++ b/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerComponent.kt @@ -0,0 +1,257 @@ +package be.re.writand.screens.projectpicker + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import be.re.writand.data.local.models.Project +import be.re.writand.navigation.WAppDestinations +import be.re.writand.screens.WUIGlobals +import be.re.writand.screens.components.TopPopUpBar +import be.re.writand.screens.components.WBorderButton +import be.re.writand.screens.components.WButton +import be.re.writand.screens.components.WLoadingIndicator +import be.re.writand.screens.components.WPopup +import be.re.writand.screens.components.WText +import be.re.writand.screens.directoryprovider.DirectoryProvider +import kotlinx.coroutines.delay +import java.io.File +import java.net.URLEncoder + +/** + * A composable that shows a list of projects and allows the user to open them. + * The screen has two parts: a list of recent projects, and a set of buttons + * to open a project or create a new one. + * @param[navHostController] the navigation host controller used to navigate to other screens. + * @param[modifier] the basic modifier for the component + * @param[vM] the view model of the screen, used to get the projects. + */ +@Composable +fun ProjectPickerComponent( + navHostController: NavHostController, + modifier: Modifier = Modifier, + vM: ProjectPickerViewModel = hiltViewModel() +) { + val projects by vM.projects.collectAsState() + val openDialog = remember { mutableStateOf(false) } + + Column( + modifier = modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + WText( + text = "Recent projects", + settingsFontSizeAlterBy = WUIGlobals.HEADING + ) + + Column( + modifier = Modifier + .size(width = 500.dp, height = 400.dp) + ) { + Box( + modifier = Modifier + .height(350.dp) + .fillMaxWidth() + .border( + 1.dp, + color = MaterialTheme.colorScheme.tertiary, + shape = RoundedCornerShape(WUIGlobals.cornerRadius) + ) + .clip(shape = RoundedCornerShape(WUIGlobals.cornerRadius)), + ) { + when (val value = projects) { + ProjectPickerUiState.Loading -> { + WLoadingIndicator() + } + + is ProjectPickerUiState.Success -> { + LazyColumn { + items(value.projects.size) { project -> + ProjectItem(value.projects[project], navHostController) + } + } + } + } + } + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.End + ) { + WButton("Open/Create Project", onClick = { + openDialog.value = !openDialog.value + }) + } + } + } + + if (openDialog.value) { + DirectoryProvider( + onDismiss = { openDialog.value = false }, + onConfirm = { + val newProject = Project(projectId = vM.amountOfProjects + 1, path = it) + vM.addProject(newProject) + navHostController.navigate( + WAppDestinations.MAIN_EDITOR + + "/${newProject.projectId}/${ + URLEncoder.encode( + newProject.path, + "UTF-8" + ) + }" + ) + } + ) + } +} + +/** + * A composable that shows a single project. + * @param[project] the project to show. + * @param[navHostController] the navigation host controller to navigate to the editor screen. + */ +@Composable +fun ProjectItem( + project: Project, + navHostController: NavHostController +) { + val showProjectConfirmation = remember { mutableStateOf(false) } + val name = project.path.split(File.separator).last() + + var isClicked by remember { mutableStateOf(false) } + + Column { + Row( + modifier = Modifier + .background( + color = if (isClicked) MaterialTheme.colorScheme.secondary + else MaterialTheme.colorScheme.primary + ) + .fillMaxWidth() + .padding(vertical = 15.dp, horizontal = 10.dp) + .pointerInput(Unit) { + detectTapGestures( + onTap = { + isClicked = true + }, + onDoubleTap = { + isClicked = true + showProjectConfirmation.value = true + } + ) + }, + horizontalArrangement = Arrangement.SpaceBetween + ) { + WText(text = name) + WText(text = project.path) + } + HorizontalDivider() + } + + if (showProjectConfirmation.value) { + PickProjectPopup( + name = name, + project = project, + onDismiss = { showProjectConfirmation.value = false }, + navHostController = navHostController + ) + } + + LaunchedEffect(isClicked) { + if (isClicked) { + delay(200L) + isClicked = false + } + } +} + +/** + * A pop up asking for confirmation before opening a project. + * @param[name] the name of the project to be opened. + * @param[project] the project to be opened. + * @param[onDismiss] a function to be called when the pop up is to be closed. + * @param[navHostController] the navigation host controller used to navigate to the editor screen. + */ +@Composable +fun PickProjectPopup( + name: String, + project: Project, + onDismiss: () -> Unit, + navHostController: NavHostController +) { + WPopup( + titleBar = { + TopPopUpBar(title = "Open \"$name\"", onDismiss = onDismiss) + }, + width = 500.dp, + height = 250.dp, + onDismiss = onDismiss, + bottomBar = { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + WBorderButton( + text = "Cancel", + onClick = onDismiss + ) + + WButton( + text = "Continue", + onClick = { + onDismiss() + navHostController.navigate( + WAppDestinations.MAIN_EDITOR + + "/${project.projectId}/${ + URLEncoder.encode( + project.path, + "UTF-8" + ) + }" + ) + } + ) + } + } + ) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(it), + contentAlignment = Alignment.Center + ) { + WText(text = "Are you sure you want to open \"$name\"?") + } + } +} diff --git a/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerScreen.kt b/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerScreen.kt index cda8b33..000c31b 100644 --- a/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerScreen.kt +++ b/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerScreen.kt @@ -2,69 +2,35 @@ package be.re.writand.screens.projectpicker import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController -import be.re.writand.data.local.models.Project -import be.re.writand.navigation.WAppDestinations import be.re.writand.screens.WUIGlobals -import be.re.writand.screens.components.TopPopUpBar -import be.re.writand.screens.components.WBorderButton -import be.re.writand.screens.components.WButton -import be.re.writand.screens.components.WLoadingIndicator import be.re.writand.screens.components.WLogoImage -import be.re.writand.screens.components.WPopup -import be.re.writand.screens.components.WText -import be.re.writand.screens.directoryprovider.DirectoryProvider -import kotlinx.coroutines.delay -import java.io.File -import java.net.URLEncoder /** * A composable that shows a list of projects and allows the user to open them. * The screen has two parts: a list of recent projects, and a set of buttons * to open a project or create a new one. * @param[navHostController] the navigation host controller used to navigate to other screens. - * @param[vM] the view model of the screen, used to get the projects. */ @Composable fun ProjectPickerScreen( - navHostController: NavHostController, - vM: ProjectPickerViewModel = hiltViewModel() + navHostController: NavHostController ) { - val projects by vM.projects.collectAsState() - val openDialog = remember { mutableStateOf(false) } - Box( modifier = Modifier .background(color = MaterialTheme.colorScheme.secondary), @@ -87,200 +53,10 @@ fun ProjectPickerScreen( } } ) { - Column( - modifier = Modifier - .padding(it) - .fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - WText( - text = "Recent projects", - settingsFontSizeAlterBy = WUIGlobals.HEADING - ) - - Column( - modifier = Modifier - .size(width = 500.dp, height = 400.dp) - ) { - Box( - modifier = Modifier - .height(350.dp) - .fillMaxWidth() - .border( - 1.dp, - color = MaterialTheme.colorScheme.tertiary, - shape = RoundedCornerShape(WUIGlobals.cornerRadius) - ) - .clip(shape = RoundedCornerShape(WUIGlobals.cornerRadius)), - ) { - when (val value = projects) { - ProjectPickerUiState.Loading -> { - WLoadingIndicator() - } - - is ProjectPickerUiState.Success -> { - LazyColumn { - items(value.projects.size) { project -> - ProjectItem(value.projects[project], navHostController) - } - } - } - } - } - Row( - modifier = Modifier.fillMaxSize(), - verticalAlignment = Alignment.Bottom, - horizontalArrangement = Arrangement.End - ) { - WButton("Open/Create Project", onClick = { - openDialog.value = !openDialog.value - }) - } - } - } - } - } - - if (openDialog.value) { - DirectoryProvider( - onDismiss = { openDialog.value = false }, - onConfirm = { - val newProject = Project(projectId = vM.amountOfProjects, path = it) - vM.addProject(newProject) - navHostController.navigate( - WAppDestinations.MAIN_EDITOR - + "/${newProject.projectId}/${ - URLEncoder.encode( - newProject.path, - "UTF-8" - ) - }" - ) - } - ) - } -} - -/** - * A composable that shows a single project. - * @param[project] the project to show. - * @param[navHostController] the navigation host controller to navigate to the editor screen. - */ -@Composable -fun ProjectItem( - project: Project, - navHostController: NavHostController -) { - val showProjectConfirmation = remember { mutableStateOf(false) } - val name = project.path.split(File.separator).last() - - var isClicked by remember { mutableStateOf(false) } - - Column { - Row( - modifier = Modifier - .background( - color = if (isClicked) MaterialTheme.colorScheme.secondary - else MaterialTheme.colorScheme.primary - ) - .fillMaxWidth() - .padding(vertical = 15.dp, horizontal = 10.dp) - .pointerInput(Unit) { - detectTapGestures( - onTap = { - isClicked = true - }, - onDoubleTap = { - isClicked = true - showProjectConfirmation.value = true - } - ) - }, - horizontalArrangement = Arrangement.SpaceBetween - ) { - WText(text = name) - WText(text = project.path) - } - HorizontalDivider() - } - - if (showProjectConfirmation.value) { - PickProjectPopup( - name = name, - project = project, - onDismiss = { showProjectConfirmation.value = false }, - navHostController = navHostController - ) - } - - LaunchedEffect(isClicked) { - if (isClicked) { - delay(200L) - isClicked = false - } - } -} - -/** - * A pop up asking for confirmation before opening a project. - * @param[name] the name of the project to be opened. - * @param[project] the project to be opened. - * @param[onDismiss] a function to be called when the pop up is to be closed. - * @param[navHostController] the navigation host controller used to navigate to the editor screen. - */ -@Composable -fun PickProjectPopup( - name: String, - project: Project, - onDismiss: () -> Unit, - navHostController: NavHostController -) { - WPopup( - titleBar = { - TopPopUpBar(title = "Open \"$name\"", onDismiss = onDismiss) - }, - width = 500.dp, - height = 250.dp, - onDismiss = onDismiss, - bottomBar = { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - WBorderButton( - text = "Cancel", - onClick = onDismiss - ) - - WButton( - text = "Continue", - onClick = { - onDismiss() - navHostController.navigate( - WAppDestinations.MAIN_EDITOR - + "/${project.projectId}/${ - URLEncoder.encode( - project.path, - "UTF-8" - ) - }" - ) - } - ) - } - } - ) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(it), - contentAlignment = Alignment.Center - ) { - WText(text = "Are you sure you want to open \"$name\"?") + ProjectPickerComponent( + navHostController = navHostController, + modifier = Modifier.padding(it) + ) } } } \ No newline at end of file diff --git a/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerViewModel.kt b/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerViewModel.kt index 42b3a19..204680f 100644 --- a/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerViewModel.kt +++ b/app/src/main/java/be/re/writand/screens/projectpicker/ProjectPickerViewModel.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject /** - * View model to be used by ProjectPickerScreen that handles the UserSettings and buttons. + * View model to be used by ProjectPickerComponent that handles the UserSettings and buttons. * @param[getRecentProjects] use case to get the recent projects. * @param[addRecentProject] use case to add a project to the recent projects. */ diff --git a/build.gradle b/build.gradle index ac951a5..d10c930 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ buildscript { ext { - kotlin_version = '1.9.0' + kotlin_version = '1.9.21' compose_ui_version = '1.6.8' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.6.0' apply false - id 'com.android.library' version '8.6.0' apply false - id 'org.jetbrains.kotlin.android' version '1.9.0' apply false + id 'com.android.application' version '8.7.2' apply false + id 'com.android.library' version '8.7.2' apply false + id 'org.jetbrains.kotlin.android' version '1.9.21' apply false id 'com.google.dagger.hilt.android' version '2.49' apply false id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.21' } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bc9a016..cd86637 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jul 16 09:37:51 CEST 2024 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME