File menu

This commit is contained in:
Emma Vandewalle 2024-11-21 20:53:03 +00:00
parent f2dd8715e1
commit 35fa01b831
9 changed files with 338 additions and 241 deletions

View file

@ -42,7 +42,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.0'
kotlinCompilerExtensionVersion '1.5.7'
}
packagingOptions {
resources {

View file

@ -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 ?: "./"
)
}

View file

@ -44,7 +44,9 @@ fun EditorScreen(
}
},
bottomBar = {
BottomEditorBar()
BottomEditorBar(
navHostController = navHostController
)
}
) {
Row(modifier = Modifier.padding(it).padding(10.dp)) {

View file

@ -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
)
}
}
}
}

View file

@ -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\"?")
}
}
}

View file

@ -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)
)
}
}
}

View file

@ -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.
*/

View file

@ -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'
}

View file

@ -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