forked from Writand/writand
Merge branch 'frontend/project-picker' into 'main'
feat: project picker startup Closes #11 See merge request EmmaVandewalle/writand!45
This commit is contained in:
commit
dd31c82f75
22 changed files with 561 additions and 39 deletions
|
@ -99,7 +99,7 @@ dependencies {
|
|||
|
||||
// Room
|
||||
implementation 'androidx.room:room-runtime:2.6.1'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.6.1'
|
||||
kapt 'androidx.room:room-compiler:2.6.1' // Use kapt instead of annotationProcessor
|
||||
implementation 'androidx.room:room-ktx:2.6.1'
|
||||
|
||||
// Proto DataStore
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package be.re.writand.data.local.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import be.re.writand.data.local.models.OpenFilePath
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface OpenedFilesDao {
|
||||
|
||||
/**
|
||||
* Get all the opened files from a project.
|
||||
*/
|
||||
@Query("SELECT * FROM openedFiles WHERE projectId = :fromProjectId")
|
||||
fun getAllOpenedFiles(fromProjectId: Int): Flow<List<OpenFilePath>>
|
||||
|
||||
/**
|
||||
* Insert a file into the database that was just opened.
|
||||
* @param[openFilePath] the OpenFilePath to add in the database.
|
||||
*/
|
||||
@Insert(entity = OpenedFilesEntity::class)
|
||||
suspend fun insertOpenedFilePath(openFilePath: OpenFilePath)
|
||||
|
||||
/**
|
||||
* Delete a OpenFilePath from the open files of a project.
|
||||
* @param[openFilePathId] the OpenFilePath to be deleted from the database.
|
||||
*/
|
||||
@Query("DELETE FROM openedFiles where id = :openFilePathId")
|
||||
suspend fun deleteOpenFilePath(openFilePathId: Int)
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package be.re.writand.data.local.db
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
/**
|
||||
* Database for all the open files corresponding to the saved projects.
|
||||
*/
|
||||
@Database(entities = [OpenedFilesEntity::class, ProjectEntity::class], version = 1)
|
||||
abstract class OpenedFilesDatabase: RoomDatabase() {
|
||||
|
||||
abstract fun openedFilesDao(): OpenedFilesDao
|
||||
|
||||
companion object {
|
||||
@Volatile private var instance: OpenedFilesDatabase? = null
|
||||
|
||||
fun getInstance(context: Context) = instance ?: synchronized(this) {
|
||||
return Room.databaseBuilder(
|
||||
context.applicationContext, OpenedFilesDatabase::class.java, "openedFiles-db"
|
||||
).build().also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package be.re.writand.data.local.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/**
|
||||
* Entity for storing the opened files of a project.
|
||||
* @param[projectId] the id of the project the file belongs to.
|
||||
* @param[path] the absolute path to the file starting from the root of the project.
|
||||
* @param[isSaved] info whether the file was saved before closing the app (for expansion of unsaved files).
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "openedFiles",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = ProjectEntity::class,
|
||||
parentColumns = arrayOf("projectId"),
|
||||
childColumns = arrayOf("projectId"),
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
],
|
||||
indices = [Index(value = ["projectId"])]
|
||||
)
|
||||
data class OpenedFilesEntity (
|
||||
@PrimaryKey(autoGenerate = true) val id: Int,
|
||||
val projectId: Int,
|
||||
val path: String,
|
||||
val isSaved: Boolean
|
||||
)
|
|
@ -1,19 +1,18 @@
|
|||
package be.re.writand.data.local.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
import be.re.writand.data.local.models.OpenFilePath
|
||||
|
||||
/**
|
||||
* Entity for recent projects to keep in memory, used in the ProjectsDAO.
|
||||
* @param[id] the unique id of a project.
|
||||
* @param[projectId] the unique id of a project.
|
||||
* @param[path] the absolute path to the directory/file.
|
||||
* A file in case of a project consisting of 1 file only.
|
||||
* @param[openedFiles] a list of objects to specify more about each last opened file.
|
||||
*/
|
||||
@Entity(tableName = "projects")
|
||||
data class ProjectEntity (
|
||||
@PrimaryKey val id: Int,
|
||||
val path: String,
|
||||
val openedFiles: List<OpenFilePath>
|
||||
@PrimaryKey(autoGenerate = true) val projectId: Int,
|
||||
val path: String
|
||||
)
|
|
@ -29,9 +29,9 @@ interface ProjectsDao {
|
|||
/**
|
||||
* Delete a project from the recent opened projects.
|
||||
* Should be called when the limit of saving projects is reached.
|
||||
* @param[project] the project to be deleted from the database.
|
||||
* @param[id] the project id to be deleted from the database.
|
||||
*/
|
||||
@Delete(entity = ProjectEntity::class)
|
||||
suspend fun deleteProject(project: Project)
|
||||
@Query("DELETE FROM projects where projectId = :id")
|
||||
suspend fun deleteProject(id: Int)
|
||||
|
||||
}
|
|
@ -2,15 +2,15 @@ package be.re.writand.data.local.models
|
|||
|
||||
/**
|
||||
* Data class used in the ProjectsDAO to store information about a recent project.
|
||||
* @param[id] a unique id for the project.
|
||||
* @param[projectId] a unique id for the project.
|
||||
* @param[path] the absolute path to the directory/file location of the project.
|
||||
*/
|
||||
data class Project(val id: Int, val path: String)
|
||||
data class Project(val projectId: Int, val path: String)
|
||||
|
||||
/**
|
||||
* Data class used in ProjectEntity to store information about a recent file of a project.
|
||||
* @param[projectId] the id of the project the file belongs to.
|
||||
* @param[path] the relative path to the file starting from the root fo the project.
|
||||
* @param[path] the relative path to the file starting from the root of the project.
|
||||
* @param[isSaved] info whether the file was saved before closing the app (for expansion of unsaved files).
|
||||
*/
|
||||
data class OpenFilePath(val projectId: Int, val path: String, var isSaved: Boolean)
|
||||
data class OpenFilePath(val id: Int, val projectId: Int, val path: String, var isSaved: Boolean)
|
|
@ -0,0 +1,29 @@
|
|||
package be.re.writand.data.repos.projects
|
||||
|
||||
import be.re.writand.data.local.models.OpenFilePath
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
/**
|
||||
* Repository that handles everything for the local OpenedFilesDatabase.
|
||||
* @param[dao] the dao provides get/add/delete functionality of the opened files from a project.
|
||||
*/
|
||||
interface IOpenedFilesRepository {
|
||||
|
||||
/**
|
||||
* Gets all the opened files from the local storage.
|
||||
*/
|
||||
fun getAllOpenedFiles(fromProjectId: Int): Flow<List<OpenFilePath>>
|
||||
|
||||
/**
|
||||
* Adds a file to the local storage of a recent project.
|
||||
* @param[openFilePath] the OpenedFilePath to be added to the database.
|
||||
*/
|
||||
suspend fun addOpenedFilePath(openFilePath: OpenFilePath)
|
||||
|
||||
/**
|
||||
* Deletes a file from the local storage of opened files of a project.
|
||||
* @param[openFilePathId] the OpenedFilePath to be deleted from the database.
|
||||
*/
|
||||
suspend fun deleteOpenFilePath(openFilePathId: Int)
|
||||
}
|
|
@ -23,7 +23,7 @@ interface IProjectsRepository {
|
|||
|
||||
/**
|
||||
* Deletes a project from the local storage of recent projects.
|
||||
* @param[project] the project to be deleted from the database.
|
||||
* @param[projectId] the project to be deleted from the database.
|
||||
*/
|
||||
suspend fun deleteProject(project: Project)
|
||||
suspend fun deleteProject(projectId: Int)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package be.re.writand.data.repos.projects
|
||||
|
||||
import be.re.writand.data.local.db.OpenedFilesDao
|
||||
import be.re.writand.data.local.models.OpenFilePath
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Repository that handles everything for the local OpenedFilesDatabase.
|
||||
* @param[dao] the dao provides get/add/delete functionality of the opened files from a project.
|
||||
*/
|
||||
@Singleton
|
||||
class OpenedFilesRepository @Inject constructor(private val dao: OpenedFilesDao): IOpenedFilesRepository {
|
||||
|
||||
override fun getAllOpenedFiles(fromProjectId: Int): Flow<List<OpenFilePath>> {
|
||||
return dao.getAllOpenedFiles(fromProjectId)
|
||||
}
|
||||
|
||||
override suspend fun addOpenedFilePath(openFilePath: OpenFilePath) {
|
||||
dao.insertOpenedFilePath(openFilePath)
|
||||
}
|
||||
|
||||
override suspend fun deleteOpenFilePath(openFilePathId: Int) {
|
||||
dao.deleteOpenFilePath(openFilePathId)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,10 @@ import kotlinx.coroutines.flow.Flow
|
|||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Repository that handles everything for the local ProjectsDatabase.
|
||||
* @param[dao] the dao provides get/add/delete functionality of the recent projects.
|
||||
*/
|
||||
@Singleton
|
||||
class ProjectsRepository @Inject constructor(private val dao: ProjectsDao): IProjectsRepository {
|
||||
|
||||
|
@ -17,7 +21,7 @@ class ProjectsRepository @Inject constructor(private val dao: ProjectsDao): IPro
|
|||
dao.insertProject(project)
|
||||
}
|
||||
|
||||
override suspend fun deleteProject(project: Project) {
|
||||
dao.deleteProject(project)
|
||||
override suspend fun deleteProject(projectId: Int) {
|
||||
dao.deleteProject(projectId)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package be.re.writand.di
|
||||
|
||||
import android.content.Context
|
||||
import be.re.writand.data.local.db.OpenedFilesDao
|
||||
import be.re.writand.data.local.db.OpenedFilesDatabase
|
||||
import be.re.writand.data.local.filemanager.FileManagerLocal
|
||||
import be.re.writand.data.local.filemanager.IFileManager
|
||||
import be.re.writand.data.local.db.ProjectsDao
|
||||
|
@ -28,11 +30,23 @@ class DatabaseModule {
|
|||
fun provideProjectsDatabase(@ApplicationContext context: Context): ProjectsDatabase {
|
||||
return ProjectsDatabase.getInstance(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideProjectsDao(projectsDatabase: ProjectsDatabase): ProjectsDao {
|
||||
return projectsDatabase.projectsDao()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideOpenedFilesDatabase(@ApplicationContext context: Context): OpenedFilesDatabase {
|
||||
return OpenedFilesDatabase.getInstance(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideOpenedFilesDao(openedFilesDatabase: OpenedFilesDatabase): OpenedFilesDao {
|
||||
return openedFilesDatabase.openedFilesDao()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the application context for TOSRepo.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package be.re.writand.domain.projects
|
||||
|
||||
import be.re.writand.data.local.models.Project
|
||||
import be.re.writand.data.repos.projects.ProjectsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use-case that adds a project to the local storage of recent projects.
|
||||
* @param[projectsRepository] Repository to interact with the projects data source.
|
||||
*/
|
||||
class AddRecentProjectUseCase @Inject constructor(
|
||||
private val projectsRepository: ProjectsRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(project: Project) {
|
||||
projectsRepository.addProject(project)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package be.re.writand.domain.projects
|
||||
|
||||
import be.re.writand.data.local.models.Project
|
||||
import be.re.writand.data.repos.projects.ProjectsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use-case that gets all the recent projects from the local storage.
|
||||
* @param[projectsRepository] the repository that provides the projects.
|
||||
*/
|
||||
class GetRecentProjectsUseCase @Inject constructor(
|
||||
private val projectsRepository: ProjectsRepository
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<List<Project>> {
|
||||
return projectsRepository.getAllProjects()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package be.re.writand.navigation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.compose.NavHost
|
||||
|
@ -11,6 +12,7 @@ import be.re.writand.screens.splash.SplashScreen
|
|||
import be.re.writand.screens.welcome.WelcomeSettingsScreen
|
||||
import be.re.writand.screens.welcome.WelcomeStartScreen
|
||||
import be.re.writand.screens.welcome.WelcomeTOSScreen
|
||||
import java.net.URLDecoder
|
||||
|
||||
/**
|
||||
* The navigation controller containing all the different screens a user can go to.
|
||||
|
@ -60,8 +62,17 @@ fun WNavGraph(navHostController: NavHostController, modifier: Modifier) {
|
|||
|
||||
// Editor view
|
||||
|
||||
composable(WAppDestinations.MAIN_EDITOR) {
|
||||
EditorScreen(navHostController = navHostController)
|
||||
composable(WAppDestinations.MAIN_EDITOR + "/{projectId}/{path}") { entry ->
|
||||
val projectId: Int? = entry.arguments?.getInt("projectId")
|
||||
val path: String? =
|
||||
entry.arguments?.getString("path")?.let { URLDecoder.decode(it, "UTF-8") }
|
||||
if (projectId != null) {
|
||||
EditorScreen(
|
||||
navHostController = navHostController,
|
||||
projectId = projectId,
|
||||
path = path ?: "./"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ enum class CreationType {
|
|||
* @param[onDismiss] a function belonging to the exit button if it needs to be shown (default is the empty function).
|
||||
*/
|
||||
@Composable
|
||||
private fun TopPopUpBar(title: String, showExit: Boolean = true, onDismiss: () -> Unit = {}) {
|
||||
fun TopPopUpBar(title: String, showExit: Boolean = true, onDismiss: () -> Unit = {}) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
|
@ -3,7 +3,6 @@ package be.re.writand.screens.components
|
|||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.absoluteOffset
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
|
@ -11,11 +10,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.AbsoluteAlignment
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
@ -75,4 +72,4 @@ fun WPopup(
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ import be.re.writand.screens.settings.SettingsPopup
|
|||
@Composable
|
||||
fun EditorScreen(
|
||||
navHostController: NavHostController,
|
||||
projectId: Int,
|
||||
path: String,
|
||||
vm: EditorScreenViewModel = hiltViewModel()
|
||||
) {
|
||||
// state for filetree view
|
||||
|
@ -48,7 +50,7 @@ fun EditorScreen(
|
|||
Row(modifier = Modifier.padding(it).padding(10.dp)) {
|
||||
if(isOpened) {
|
||||
WFiletree(
|
||||
root = "/storage/emulated/0/Documents/webserver",
|
||||
root = path,
|
||||
modifier = Modifier.weight(1F),
|
||||
onSelect = { path ->
|
||||
vm.setOpenedFile(path)
|
||||
|
|
|
@ -1,31 +1,23 @@
|
|||
package be.re.writand.screens.filetree
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
@ -45,7 +37,6 @@ import be.re.writand.screens.components.WDangerButton
|
|||
import be.re.writand.screens.components.WLabelAndTextField
|
||||
import be.re.writand.screens.components.WPopup
|
||||
import be.re.writand.screens.components.WText
|
||||
import be.re.writand.ui.theme.MainGreen
|
||||
import be.re.writand.utils.FileNode
|
||||
import java.nio.file.Path
|
||||
|
||||
|
|
|
@ -1,23 +1,286 @@
|
|||
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
|
||||
navHostController: NavHostController,
|
||||
vM: ProjectPickerViewModel = hiltViewModel()
|
||||
) {
|
||||
val projects by vM.projects.collectAsState()
|
||||
val openDialog = remember { mutableStateOf(false) }
|
||||
|
||||
Box(modifier = Modifier.size(20.dp, 20.dp)) {
|
||||
WButton(
|
||||
text = "Create new",
|
||||
onClick = { navHostController.navigate(WAppDestinations.MAIN_EDITOR) }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.secondary),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Scaffold(modifier = Modifier
|
||||
.size(600.dp)
|
||||
.border(
|
||||
1.dp,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
shape = RoundedCornerShape(WUIGlobals.cornerRadius)
|
||||
)
|
||||
.clip(shape = RoundedCornerShape(WUIGlobals.cornerRadius)),
|
||||
topBar = {
|
||||
Box(modifier = Modifier.height(75.dp)) {
|
||||
WLogoImage(
|
||||
modifier = Modifier.padding(25.dp),
|
||||
alignment = Alignment.CenterStart
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
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\"?")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package be.re.writand.screens.projectpicker
|
||||
|
||||
import be.re.writand.data.local.models.Project
|
||||
|
||||
/**
|
||||
* The ui state for the project picker.
|
||||
* - [Loading] the content is still loading in.
|
||||
* - [Success] the list of projects is ready.
|
||||
*/
|
||||
sealed interface ProjectPickerUiState {
|
||||
object Loading: ProjectPickerUiState
|
||||
data class Success(val projects: List<Project>): ProjectPickerUiState
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package be.re.writand.screens.projectpicker
|
||||
|
||||
import be.re.writand.data.local.models.Project
|
||||
import be.re.writand.domain.projects.AddRecentProjectUseCase
|
||||
import be.re.writand.domain.projects.GetRecentProjectsUseCase
|
||||
import be.re.writand.screens.WViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* View model to be used by ProjectPickerScreen 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.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class ProjectPickerViewModel @Inject constructor(
|
||||
getRecentProjects: GetRecentProjectsUseCase,
|
||||
private val addRecentProject: AddRecentProjectUseCase
|
||||
): WViewModel() {
|
||||
|
||||
private val _projects: MutableStateFlow<ProjectPickerUiState> =
|
||||
MutableStateFlow(ProjectPickerUiState.Loading)
|
||||
val projects: StateFlow<ProjectPickerUiState> = _projects
|
||||
|
||||
private val _amountOfProjects: MutableStateFlow<Int> = MutableStateFlow(0);
|
||||
val amountOfProjects: Int get() = _amountOfProjects.value
|
||||
|
||||
init {
|
||||
launchCatching {
|
||||
getRecentProjects().collect { projects ->
|
||||
_amountOfProjects.value = projects.size
|
||||
_projects.value = ProjectPickerUiState.Success(projects)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addProject(project: Project) {
|
||||
launchCatching {
|
||||
addRecentProject.invoke(project)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue