forked from Writand/writand
Merge branch 'frontend/renamefile' into 'main'
feat: rename and move file/directory support in filetree Closes #39 and #37 See merge request EmmaVandewalle/writand!49
This commit is contained in:
commit
58c65a12ee
11 changed files with 350 additions and 244 deletions
|
@ -5,7 +5,6 @@ import be.re.writand.utils.GenerateId
|
||||||
import be.re.writand.utils.WFileTreeAbstraction
|
import be.re.writand.utils.WFileTreeAbstraction
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okio.Path.Companion.toPath
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileFilter
|
import java.io.FileFilter
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
package be.re.writand.screens.components
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.layout.boundsInWindow
|
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
|
||||||
import androidx.compose.ui.unit.IntSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently this is unused, but the goal is to implement this feature into the filetree.
|
|
||||||
* based on: [this video](https://www.youtube.com/watch?v=ST99k8qK6SM)
|
|
||||||
*/
|
|
||||||
|
|
||||||
internal class DragTargetInfo {
|
|
||||||
var isDragging: Boolean by mutableStateOf(false)
|
|
||||||
var dragPosition by mutableStateOf(Offset.Zero)
|
|
||||||
var dragOffset by mutableStateOf(Offset.Zero)
|
|
||||||
var draggableComposable by mutableStateOf<(@Composable () -> Unit)?>(null)
|
|
||||||
var dataToDrop by mutableStateOf<Any?>(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val LocalDragTargetInfo = compositionLocalOf { DragTargetInfo() }
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun <T> DragTarget(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
dataToDrop: T,
|
|
||||||
onStartDragging: () -> Unit,
|
|
||||||
onStopDragging: () -> Unit,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
var currentPosition by remember { mutableStateOf(Offset.Zero) }
|
|
||||||
val currentState = LocalDragTargetInfo.current
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = modifier
|
|
||||||
.onGloballyPositioned {
|
|
||||||
currentPosition = it.windowToLocal(Offset.Zero)
|
|
||||||
}
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
detectDragGesturesAfterLongPress(
|
|
||||||
onDragStart = {
|
|
||||||
onStartDragging()
|
|
||||||
currentState.dataToDrop = dataToDrop
|
|
||||||
currentState.isDragging = true
|
|
||||||
currentState.dragPosition = currentPosition + it
|
|
||||||
currentState.draggableComposable = content
|
|
||||||
|
|
||||||
Log.d("DRAGGING", "states has been set")
|
|
||||||
},
|
|
||||||
onDrag = { change, dragAmount ->
|
|
||||||
change.consume()
|
|
||||||
currentState.dragOffset += dragAmount
|
|
||||||
},
|
|
||||||
onDragEnd = {
|
|
||||||
onStopDragging()
|
|
||||||
currentState.dragPosition = Offset.Zero
|
|
||||||
currentState.isDragging = false
|
|
||||||
},
|
|
||||||
onDragCancel = {
|
|
||||||
onStopDragging()
|
|
||||||
currentState.dragPosition = Offset.Zero
|
|
||||||
currentState.isDragging = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun <T> DropItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
content: @Composable (BoxScope.(isInBound: Boolean, data: T?) -> Unit)
|
|
||||||
) {
|
|
||||||
val dragInfo = LocalDragTargetInfo.current
|
|
||||||
val dragPosition = dragInfo.dragPosition
|
|
||||||
val dragOffset = dragInfo.dragOffset
|
|
||||||
var isCurrentDropTarget by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = modifier.onGloballyPositioned {
|
|
||||||
it.boundsInWindow().let { rect ->
|
|
||||||
isCurrentDropTarget = rect.contains(dragPosition + dragOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
val data =
|
|
||||||
if (isCurrentDropTarget && !dragInfo.isDragging) dragInfo.dataToDrop as T? else null
|
|
||||||
content(isCurrentDropTarget, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DraggableScreen(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
content: @Composable BoxScope.() -> Unit
|
|
||||||
) {
|
|
||||||
val state = remember { DragTargetInfo() }
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalDragTargetInfo provides state
|
|
||||||
) {
|
|
||||||
Box(modifier = modifier.fillMaxSize()) {
|
|
||||||
content()
|
|
||||||
if (state.isDragging) {
|
|
||||||
var targetSize by remember { mutableStateOf(IntSize.Zero) }
|
|
||||||
Box(modifier = Modifier
|
|
||||||
.graphicsLayer {
|
|
||||||
val offset = state.dragPosition + state.dragOffset
|
|
||||||
scaleX = 1.3f
|
|
||||||
scaleY = 1.3f
|
|
||||||
alpha = if (targetSize == IntSize.Zero) 0f else .9f
|
|
||||||
translationX = offset.x.minus(targetSize.width / 2)
|
|
||||||
translationY = offset.y.minus(targetSize.height / 2)
|
|
||||||
}
|
|
||||||
.onGloballyPositioned {
|
|
||||||
targetSize = it.size
|
|
||||||
}) {
|
|
||||||
state.draggableComposable?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DraggableLazyColumnScreen(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
content: LazyListScope.() -> Unit
|
|
||||||
) {
|
|
||||||
val state = remember { DragTargetInfo() }
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalDragTargetInfo provides state
|
|
||||||
) {
|
|
||||||
val listState = rememberLazyListState()
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
modifier = modifier.fillMaxSize(),
|
|
||||||
contentPadding = PaddingValues(10.dp)
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
if(state.isDragging) {
|
|
||||||
var targetSize by remember { mutableStateOf(IntSize.Zero) }
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.zIndex(1.0f)
|
|
||||||
.graphicsLayer {
|
|
||||||
val offset = state.dragPosition + state.dragOffset - Offset(0f, listState.firstVisibleItemScrollOffset.toFloat())
|
|
||||||
alpha = if (targetSize == IntSize.Zero) 0f else .9f
|
|
||||||
translationX = offset.x - targetSize.width / 2
|
|
||||||
translationY = offset.y - targetSize.height / 2
|
|
||||||
}
|
|
||||||
.onGloballyPositioned {
|
|
||||||
Log.d("DRAGGING", "original targetsize = $targetSize, new value = ${it.size}")
|
|
||||||
targetSize = it.size
|
|
||||||
}) {
|
|
||||||
state.draggableComposable?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -203,3 +203,60 @@ fun RemoveFilePopUp(filename: String, hidePopUp: () -> Unit, onConfirm: () -> Un
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenameFilePopUp(oldName: String, hidePopUp: () -> Unit, onConfirm: (String) -> Unit) {
|
||||||
|
val (newName, setNewName) = remember { mutableStateOf(oldName) }
|
||||||
|
WPopup(
|
||||||
|
titleBar = {
|
||||||
|
TopPopUpBar(
|
||||||
|
title = "Are you sure?",
|
||||||
|
showExit = false
|
||||||
|
)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
WBorderButton(
|
||||||
|
text = "Cancel",
|
||||||
|
onClick = hidePopUp
|
||||||
|
)
|
||||||
|
|
||||||
|
WButton(
|
||||||
|
text = "Rename",
|
||||||
|
onClick = {
|
||||||
|
onConfirm(newName)
|
||||||
|
hidePopUp()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
height = 200.dp,
|
||||||
|
width = 500.dp,
|
||||||
|
onDismiss = hidePopUp
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(padding)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(10.dp),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
WLabelAndTextField(
|
||||||
|
title = "Filename",
|
||||||
|
value = newName,
|
||||||
|
onTextChange = { setNewName(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,14 +23,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.VerticalDivider
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -54,6 +53,11 @@ import be.re.writand.screens.components.WPopup
|
||||||
import be.re.writand.screens.components.WText
|
import be.re.writand.screens.components.WText
|
||||||
import be.re.writand.ui.theme.MainGreen
|
import be.re.writand.ui.theme.MainGreen
|
||||||
|
|
||||||
|
enum class WProviderType {
|
||||||
|
OpenDirectoryProvider,
|
||||||
|
MoveFileProvider
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(onDismiss: () -> Unit, onBack: () -> Unit) {
|
private fun TopBar(onDismiss: () -> Unit, onBack: () -> Unit) {
|
||||||
Row(
|
Row(
|
||||||
|
@ -167,9 +171,15 @@ private fun ShortcutColumnItem(name: String, icon: Int, onClick: () -> Unit) {
|
||||||
@Composable
|
@Composable
|
||||||
fun DirectoryProvider(
|
fun DirectoryProvider(
|
||||||
vm: DirectoryProviderViewModel = hiltViewModel(),
|
vm: DirectoryProviderViewModel = hiltViewModel(),
|
||||||
|
type: WProviderType = WProviderType.OpenDirectoryProvider,
|
||||||
|
root: String = "/storage/emulated/0",
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onConfirm: (String) -> Unit
|
onConfirm: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
vm.initProvider(root)
|
||||||
|
}
|
||||||
|
|
||||||
// The shortcuts used in the sidebar, each element is a pair that links the name to its path
|
// The shortcuts used in the sidebar, each element is a pair that links the name to its path
|
||||||
val shortcuts = mapOf(
|
val shortcuts = mapOf(
|
||||||
Pair("Documents", Environment.DIRECTORY_DOCUMENTS),
|
Pair("Documents", Environment.DIRECTORY_DOCUMENTS),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package be.re.writand.screens.directoryprovider
|
package be.re.writand.screens.directoryprovider
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import be.re.writand.data.local.filemanager.FileType
|
import be.re.writand.data.local.filemanager.FileType
|
||||||
|
@ -24,18 +25,22 @@ class DirectoryProviderViewModel @Inject constructor(
|
||||||
private val listDirectoriesUseCase: ListDirectoriesUseCase,
|
private val listDirectoriesUseCase: ListDirectoriesUseCase,
|
||||||
private val createFileUseCase: CreateFileUseCase
|
private val createFileUseCase: CreateFileUseCase
|
||||||
) : WViewModel() {
|
) : WViewModel() {
|
||||||
private val root = "/storage/emulated/0"
|
private lateinit var root: String
|
||||||
val separator: String = File.separator
|
val separator: String = File.separator
|
||||||
|
|
||||||
private val _uiState =
|
private val _uiState =
|
||||||
MutableStateFlow<DirectoryProviderUiState>(DirectoryProviderUiState.Empty)
|
MutableStateFlow<DirectoryProviderUiState>(DirectoryProviderUiState.Empty)
|
||||||
val uiState: StateFlow<DirectoryProviderUiState> = _uiState
|
val uiState: StateFlow<DirectoryProviderUiState> = _uiState
|
||||||
|
|
||||||
val currentPath = mutableStateListOf(root)
|
val currentPath = mutableStateListOf<String>()
|
||||||
val selectedItem = mutableStateOf<String?>(null)
|
val selectedItem = mutableStateOf<String?>(null)
|
||||||
|
|
||||||
init {
|
fun initProvider(path: String = "/storage/emulated/0") {
|
||||||
listCurrentDirectory()
|
if (!this::root.isInitialized) {
|
||||||
|
root = path
|
||||||
|
currentPath.add(root)
|
||||||
|
listCurrentDirectory()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createNewDirectory(name: String) {
|
fun createNewDirectory(name: String) {
|
||||||
|
@ -73,7 +78,7 @@ class DirectoryProviderViewModel @Inject constructor(
|
||||||
|
|
||||||
fun moveUp() {
|
fun moveUp() {
|
||||||
if (currentPath.size > 1) {
|
if (currentPath.size > 1) {
|
||||||
currentPath.removeLast()
|
currentPath.removeAt(currentPath.lastIndex)
|
||||||
selectedItem.value = null
|
selectedItem.value = null
|
||||||
listCurrentDirectory()
|
listCurrentDirectory()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,13 @@ package be.re.writand.screens.filetree
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Info
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -144,8 +138,10 @@ fun WFiletree(
|
||||||
isDirectory = { id -> vm.isDirectory(id) },
|
isDirectory = { id -> vm.isDirectory(id) },
|
||||||
getIsOpened = { id -> vm.getIsOpened(id) },
|
getIsOpened = { id -> vm.getIsOpened(id) },
|
||||||
getPath = { id -> vm.getPath(id) },
|
getPath = { id -> vm.getPath(id) },
|
||||||
|
findNode = { p -> vm.findNode(p) },
|
||||||
|
projectRoot = root,
|
||||||
toggleIsOpened = { id -> vm.toggleOpen(id) },
|
toggleIsOpened = { id -> vm.toggleOpen(id) },
|
||||||
onMove = { from, to -> vm.moveFile(from, to) },
|
onMove = { from, to, name -> vm.moveFile(from, to, name) },
|
||||||
onCreate = { node, name, type -> vm.createFile(name, type, node) },
|
onCreate = { node, name, type -> vm.createFile(name, type, node) },
|
||||||
onDelete = { node -> vm.removeFile(node) },
|
onDelete = { node -> vm.removeFile(node) },
|
||||||
onSelect = onSelect
|
onSelect = onSelect
|
||||||
|
|
|
@ -100,16 +100,24 @@ class WFiletreeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveFile(from: FileNode, to: FileNode) {
|
fun moveFile(from: FileNode, to: FileNode, name: String) {
|
||||||
if (!this::fileTree.isInitialized) return
|
if (!this::fileTree.isInitialized) return
|
||||||
val fromPath = fileTree.getPath(from.item)!!
|
val fromPath = fileTree.getPath(from.item)!!
|
||||||
val toPath = fileTree.getPath(to.item)!!
|
val toPath = if (from != to) {
|
||||||
|
Paths.get(fileTree.getPath(to.item)!!.toString(), name)
|
||||||
|
} else {
|
||||||
|
Paths.get(fileTree.getPath(from.item)!!.parent.toString(), name)
|
||||||
|
}
|
||||||
|
|
||||||
fileTree.move(from, to)
|
fileTree.move(from, to, name)
|
||||||
launchCatching {
|
launchCatching {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
moveFileUseCase(fromPath, toPath)
|
moveFileUseCase(fromPath, toPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun findNode(path: String): FileNode? {
|
||||||
|
return fileTree.find(Paths.get(path))
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,47 +1,52 @@
|
||||||
package be.re.writand.screens.filetree
|
package be.re.writand.screens.filetree
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import be.re.writand.R
|
||||||
import be.re.writand.data.local.filemanager.FileType
|
import be.re.writand.data.local.filemanager.FileType
|
||||||
import be.re.writand.screens.WUIGlobals
|
import be.re.writand.screens.WUIGlobals
|
||||||
import be.re.writand.screens.components.CreateFilePopUp
|
import be.re.writand.screens.components.CreateFilePopUp
|
||||||
import be.re.writand.screens.components.CreationType
|
import be.re.writand.screens.components.CreationType
|
||||||
import be.re.writand.screens.components.RemoveFilePopUp
|
import be.re.writand.screens.components.RemoveFilePopUp
|
||||||
import be.re.writand.screens.components.WBorderButton
|
import be.re.writand.screens.components.RenameFilePopUp
|
||||||
import be.re.writand.screens.components.WButton
|
|
||||||
import be.re.writand.screens.components.WCheckbox
|
|
||||||
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.WPopup
|
||||||
import be.re.writand.screens.components.WText
|
import be.re.writand.screens.components.WText
|
||||||
|
import be.re.writand.screens.directoryprovider.DirectoryProvider
|
||||||
|
import be.re.writand.screens.directoryprovider.WProviderType
|
||||||
import be.re.writand.utils.FileNode
|
import be.re.writand.utils.FileNode
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
// TODO: add drag and drop functionality to move files from 1 place to another
|
|
||||||
|
|
||||||
// based on: https://stackoverflow.com/a/71709816
|
// based on: https://stackoverflow.com/a/71709816
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,6 +57,7 @@ import java.nio.file.Path
|
||||||
* @param[isDirectory] a function to determine if the node stands for a file or a directory.
|
* @param[isDirectory] a function to determine if the node stands for a file or a directory.
|
||||||
* @param[getIsOpened] a function to access the state for a given node representing a directory.
|
* @param[getIsOpened] a function to access the state for a given node representing a directory.
|
||||||
* @param[getPath] a function to retrieve the path of a node.
|
* @param[getPath] a function to retrieve the path of a node.
|
||||||
|
* @param[findNode] a function to search the filetree for the node corresponding to a given path.
|
||||||
* @param[toggleIsOpened] the state changer function, toggles between opened and closed.
|
* @param[toggleIsOpened] the state changer function, toggles between opened and closed.
|
||||||
* @param[onMove] the action function to move a file / directory from a to b.
|
* @param[onMove] the action function to move a file / directory from a to b.
|
||||||
* @param[onCreate] the action function to create a new file / directory.
|
* @param[onCreate] the action function to create a new file / directory.
|
||||||
|
@ -61,13 +67,15 @@ import java.nio.file.Path
|
||||||
@Composable
|
@Composable
|
||||||
fun WTreeComponent(
|
fun WTreeComponent(
|
||||||
root: FileNode,
|
root: FileNode,
|
||||||
|
projectRoot: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
getFilename: (ULong) -> String?,
|
getFilename: (ULong) -> String?,
|
||||||
isDirectory: (ULong) -> Boolean,
|
isDirectory: (ULong) -> Boolean,
|
||||||
getIsOpened: (ULong) -> Boolean,
|
getIsOpened: (ULong) -> Boolean,
|
||||||
getPath: (ULong) -> Path?,
|
getPath: (ULong) -> Path?,
|
||||||
|
findNode: (String) -> FileNode?,
|
||||||
toggleIsOpened: (ULong) -> Unit,
|
toggleIsOpened: (ULong) -> Unit,
|
||||||
onMove: (FileNode, FileNode) -> Unit,
|
onMove: (FileNode, FileNode, String) -> Unit,
|
||||||
onCreate: (FileNode, String, FileType) -> Unit,
|
onCreate: (FileNode, String, FileType) -> Unit,
|
||||||
onDelete: (FileNode) -> Unit,
|
onDelete: (FileNode) -> Unit,
|
||||||
onSelect: (Path?) -> Unit
|
onSelect: (Path?) -> Unit
|
||||||
|
@ -78,6 +86,8 @@ fun WTreeComponent(
|
||||||
depth = 0,
|
depth = 0,
|
||||||
getFilename = getFilename,
|
getFilename = getFilename,
|
||||||
getPath = getPath,
|
getPath = getPath,
|
||||||
|
findNode = findNode,
|
||||||
|
projectRoot = projectRoot,
|
||||||
isDirectory = isDirectory,
|
isDirectory = isDirectory,
|
||||||
getIsOpened = getIsOpened,
|
getIsOpened = getIsOpened,
|
||||||
toggleIsOpened = toggleIsOpened,
|
toggleIsOpened = toggleIsOpened,
|
||||||
|
@ -100,10 +110,12 @@ fun LazyListScope.WTreeItems(
|
||||||
depth: Int,
|
depth: Int,
|
||||||
getFilename: (ULong) -> String?,
|
getFilename: (ULong) -> String?,
|
||||||
getPath: (ULong) -> Path?,
|
getPath: (ULong) -> Path?,
|
||||||
|
findNode: (String) -> FileNode?,
|
||||||
|
projectRoot: String,
|
||||||
isDirectory: (ULong) -> Boolean,
|
isDirectory: (ULong) -> Boolean,
|
||||||
getIsOpened: (ULong) -> Boolean,
|
getIsOpened: (ULong) -> Boolean,
|
||||||
toggleIsOpened: (ULong) -> Unit,
|
toggleIsOpened: (ULong) -> Unit,
|
||||||
onMove: (FileNode, FileNode) -> Unit,
|
onMove: (FileNode, FileNode, String) -> Unit,
|
||||||
onCreate: (FileNode, String, FileType) -> Unit,
|
onCreate: (FileNode, String, FileType) -> Unit,
|
||||||
onDelete: (FileNode) -> Unit,
|
onDelete: (FileNode) -> Unit,
|
||||||
onSelect: (Path?) -> Unit
|
onSelect: (Path?) -> Unit
|
||||||
|
@ -114,6 +126,8 @@ fun LazyListScope.WTreeItems(
|
||||||
depth = depth,
|
depth = depth,
|
||||||
getFilename = getFilename,
|
getFilename = getFilename,
|
||||||
getPath = getPath,
|
getPath = getPath,
|
||||||
|
findNode = findNode,
|
||||||
|
projectRoot = projectRoot,
|
||||||
isDirectory = isDirectory,
|
isDirectory = isDirectory,
|
||||||
getIsOpened = getIsOpened,
|
getIsOpened = getIsOpened,
|
||||||
toggleIsOpened = toggleIsOpened,
|
toggleIsOpened = toggleIsOpened,
|
||||||
|
@ -135,10 +149,12 @@ fun LazyListScope.WTreeItem(
|
||||||
depth: Int,
|
depth: Int,
|
||||||
getFilename: (ULong) -> String?,
|
getFilename: (ULong) -> String?,
|
||||||
getPath: (ULong) -> Path?,
|
getPath: (ULong) -> Path?,
|
||||||
|
findNode: (String) -> FileNode?,
|
||||||
|
projectRoot: String,
|
||||||
isDirectory: (ULong) -> Boolean,
|
isDirectory: (ULong) -> Boolean,
|
||||||
getIsOpened: (ULong) -> Boolean,
|
getIsOpened: (ULong) -> Boolean,
|
||||||
toggleIsOpened: (ULong) -> Unit,
|
toggleIsOpened: (ULong) -> Unit,
|
||||||
onMove: (FileNode, FileNode) -> Unit,
|
onMove: (FileNode, FileNode, String) -> Unit,
|
||||||
onCreate: (FileNode, String, FileType) -> Unit,
|
onCreate: (FileNode, String, FileType) -> Unit,
|
||||||
onDelete: (FileNode) -> Unit,
|
onDelete: (FileNode) -> Unit,
|
||||||
onSelect: (Path?) -> Unit
|
onSelect: (Path?) -> Unit
|
||||||
|
@ -149,6 +165,10 @@ fun LazyListScope.WTreeItem(
|
||||||
item {
|
item {
|
||||||
val (showAddPopUp, setShowAddPopUp) = remember { mutableStateOf(false) }
|
val (showAddPopUp, setShowAddPopUp) = remember { mutableStateOf(false) }
|
||||||
val (showRemovePopUp, setShowRemovePopUp) = remember { mutableStateOf(false) }
|
val (showRemovePopUp, setShowRemovePopUp) = remember { mutableStateOf(false) }
|
||||||
|
val (isShowMore, setIsShowMore) = remember { mutableStateOf(false) }
|
||||||
|
val (showMovePopUp, setShowMovePopUp) = remember { mutableStateOf(false) }
|
||||||
|
val (showRenamePopUp, setShowRenamePopUp) = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
Filename(
|
Filename(
|
||||||
root = root,
|
root = root,
|
||||||
|
@ -160,32 +180,151 @@ fun LazyListScope.WTreeItem(
|
||||||
toggleIsOpened = toggleIsOpened,
|
toggleIsOpened = toggleIsOpened,
|
||||||
onSelect = onSelect
|
onSelect = onSelect
|
||||||
)
|
)
|
||||||
if (isDir) {
|
|
||||||
Icon(
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
imageVector = Icons.Default.Add,
|
|
||||||
contentDescription = "Add a file or directory",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(WUIGlobals.treeIconSize)
|
|
||||||
.clickable {
|
|
||||||
setShowAddPopUp(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Delete,
|
painter = painterResource(R.drawable.baseline_more_horiz_16),
|
||||||
contentDescription = "Delete the current file or directory",
|
contentDescription = "Show more",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(WUIGlobals.treeIconSize)
|
.size(WUIGlobals.treeIconSize)
|
||||||
.clickable {
|
.clickable {
|
||||||
setShowRemovePopUp(true)
|
setIsShowMore(true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (isShowMore) WPopup(
|
||||||
|
width = 300.dp,
|
||||||
|
height = 200.dp,
|
||||||
|
onDismiss = { setIsShowMore(false) },
|
||||||
|
titleBar = { /* Left empty on purpose */ }
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding)
|
||||||
|
.padding(10.dp)
|
||||||
|
) {
|
||||||
|
if (isDir) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 0.dp, vertical = 10.dp)
|
||||||
|
.clickable {
|
||||||
|
setIsShowMore(false)
|
||||||
|
setShowAddPopUp(true)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = "Add a file/directory",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(WUIGlobals.iconSize)
|
||||||
|
)
|
||||||
|
WText(
|
||||||
|
text = "Add a file/directory",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 0.dp, vertical = 10.dp)
|
||||||
|
.clickable {
|
||||||
|
setIsShowMore(false)
|
||||||
|
setShowMovePopUp(true)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_drive_file_move_16),
|
||||||
|
contentDescription = "Move file/directory",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(WUIGlobals.iconSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
WText(
|
||||||
|
text = "Move $filename",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 0.dp, vertical = 10.dp)
|
||||||
|
.clickable {
|
||||||
|
setIsShowMore(false)
|
||||||
|
setShowRenamePopUp(true)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Edit,
|
||||||
|
contentDescription = "Rename file/directory",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(WUIGlobals.iconSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
WText(
|
||||||
|
text = "Rename $filename",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = 0.dp,
|
||||||
|
vertical = 5.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(WUIGlobals.cornerRadius))
|
||||||
|
.background(Color.Red)
|
||||||
|
.padding(horizontal = 5.dp, vertical = 10.dp)
|
||||||
|
.clickable {
|
||||||
|
setIsShowMore(false)
|
||||||
|
setShowRemovePopUp(true)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = "Delete",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(WUIGlobals.iconSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
WText(
|
||||||
|
text = "Delete $filename",
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.padding(horizontal = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showRenamePopUp) RenameFilePopUp(
|
||||||
|
oldName = filename,
|
||||||
|
hidePopUp = { setShowRenamePopUp(false) },
|
||||||
|
onConfirm = { name -> onMove(root, root, name) }
|
||||||
|
)
|
||||||
|
|
||||||
if (showAddPopUp) CreateFilePopUp(
|
if (showAddPopUp) CreateFilePopUp(
|
||||||
type = CreationType.ALL,
|
|
||||||
hidePopUp = { setShowAddPopUp(false) },
|
hidePopUp = { setShowAddPopUp(false) },
|
||||||
onConfirm = { name, type -> onCreate(root, name, type) }
|
onConfirm = { name, type -> onCreate(root, name, type) },
|
||||||
|
type = CreationType.ALL
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showRemovePopUp) RemoveFilePopUp(
|
if (showRemovePopUp) RemoveFilePopUp(
|
||||||
|
@ -193,6 +332,20 @@ fun LazyListScope.WTreeItem(
|
||||||
hidePopUp = { setShowRemovePopUp(false) },
|
hidePopUp = { setShowRemovePopUp(false) },
|
||||||
onConfirm = { onDelete(root) }
|
onConfirm = { onDelete(root) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showMovePopUp) DirectoryProvider(
|
||||||
|
type = WProviderType.MoveFileProvider,
|
||||||
|
root = projectRoot,
|
||||||
|
onDismiss = { setShowMovePopUp(false) }
|
||||||
|
) { result ->
|
||||||
|
|
||||||
|
// find goal node in tree according to result:
|
||||||
|
val goal = findNode(result)
|
||||||
|
goal?.let {
|
||||||
|
getFilename(root.item)?.let { filename -> onMove(root, it, filename) }
|
||||||
|
}
|
||||||
|
setShowMovePopUp(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (getIsOpened(root.item)) {
|
if (getIsOpened(root.item)) {
|
||||||
WTreeItems(
|
WTreeItems(
|
||||||
|
@ -200,6 +353,8 @@ fun LazyListScope.WTreeItem(
|
||||||
depth = depth + 1,
|
depth = depth + 1,
|
||||||
getFilename = getFilename,
|
getFilename = getFilename,
|
||||||
getPath = getPath,
|
getPath = getPath,
|
||||||
|
findNode = findNode,
|
||||||
|
projectRoot = projectRoot,
|
||||||
isDirectory = isDirectory,
|
isDirectory = isDirectory,
|
||||||
getIsOpened = getIsOpened,
|
getIsOpened = getIsOpened,
|
||||||
toggleIsOpened = toggleIsOpened,
|
toggleIsOpened = toggleIsOpened,
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
package be.re.writand.utils
|
package be.re.writand.utils
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.neverEqualPolicy
|
import androidx.compose.runtime.neverEqualPolicy
|
||||||
import androidx.compose.runtime.snapshots.MutableSnapshot
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.AbstractQueue
|
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.PriorityQueue
|
|
||||||
import java.util.Queue
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.io.path.isDirectory
|
import kotlin.io.path.isDirectory
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
|
@ -33,10 +29,36 @@ class WFileTreeAbstraction @Inject constructor(
|
||||||
_root.value = _root.value
|
_root.value = _root.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all the children after a directory is renamed. Makes sure this change
|
||||||
|
* is reflected in the index.
|
||||||
|
* @param[node] the node which is renamed.
|
||||||
|
* @param[oldName] the old name of the node.
|
||||||
|
* @param[newName] the new name of the node.
|
||||||
|
*/
|
||||||
|
private fun renameChildren(node: FileNode, oldName: String, newName: String) {
|
||||||
|
val queue = LinkedList<FileNode>()
|
||||||
|
queue.addAll(node.children)
|
||||||
|
|
||||||
|
while (queue.isNotEmpty()) {
|
||||||
|
val top = queue.poll()!!
|
||||||
|
index[top.item] = Paths.get(index[top.item]!!.toString().replace(oldName, newName))
|
||||||
|
|
||||||
|
if (top.children.isNotEmpty()) {
|
||||||
|
queue.addAll(top.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val comparator = compareBy<FileNode> { index[it.item]?.isDirectory() }
|
private val comparator = compareBy<FileNode> { index[it.item]?.isDirectory() }
|
||||||
.reversed()
|
.reversed()
|
||||||
.thenBy { index[it.item]?.name }
|
.thenBy { index[it.item]?.name }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the path according to [id].
|
||||||
|
* @param[id] the id of the node.
|
||||||
|
* @return the path of the node.
|
||||||
|
*/
|
||||||
fun getPath(id: ULong): Path? {
|
fun getPath(id: ULong): Path? {
|
||||||
return index[id]
|
return index[id]
|
||||||
}
|
}
|
||||||
|
@ -53,7 +75,7 @@ class WFileTreeAbstraction @Inject constructor(
|
||||||
val queue = LinkedList<FileNode>()
|
val queue = LinkedList<FileNode>()
|
||||||
queue.add(node)
|
queue.add(node)
|
||||||
|
|
||||||
while(!queue.isEmpty()) {
|
while (!queue.isEmpty()) {
|
||||||
val top = queue.poll()!!
|
val top = queue.poll()!!
|
||||||
queue.addAll(top.children)
|
queue.addAll(top.children)
|
||||||
index.remove(top.item)
|
index.remove(top.item)
|
||||||
|
@ -78,26 +100,53 @@ class WFileTreeAbstraction @Inject constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a file from 1 location to another.
|
* Move a file from 1 location to another.
|
||||||
|
* If [from] and [to] are equal, a renaming operation took place
|
||||||
* @param[from] the source location to be moved.
|
* @param[from] the source location to be moved.
|
||||||
* @param[to] the destination directory.
|
* @param[to] the destination directory.
|
||||||
*/
|
*/
|
||||||
fun move(from: FileNode, to: FileNode) {
|
fun move(from: FileNode, to: FileNode, name: String) {
|
||||||
from.parent?.children?.remove(from)
|
val oldName = index[from.item]!!.fileName ?: throw IllegalArgumentException()
|
||||||
to.children.add(from)
|
if (from != to) {
|
||||||
from.parent = to
|
from.parent?.children?.remove(from)
|
||||||
to.children.sortWith(comparator)
|
to.children.add(from)
|
||||||
|
from.parent = to
|
||||||
|
to.children.sortWith(comparator)
|
||||||
|
|
||||||
// update the path stored in the index
|
// update the path stored in the index
|
||||||
val parentPath = index[to.item] ?: throw IllegalArgumentException()
|
val parentPath = index[to.item] ?: throw IllegalArgumentException()
|
||||||
val fromPath = index[from.item] ?: throw IllegalArgumentException()
|
index[from.item] = Paths.get(parentPath.toString(), name)
|
||||||
index[from.item] = Paths.get(parentPath.toString(), fromPath.name)
|
} else {
|
||||||
|
val path = index[from.item] ?: throw IllegalArgumentException()
|
||||||
|
index[from.item] = Paths.get(path.parent.toString(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from.children.isNotEmpty()) renameChildren(from, oldName.toString(), name)
|
||||||
|
|
||||||
triggerRerender()
|
triggerRerender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a node according to a given path.
|
||||||
|
* @param[path] the path to find.
|
||||||
|
* @return null if no node is found, or the node corresponding to [path].
|
||||||
|
*/
|
||||||
|
fun find(path: Path): FileNode? {
|
||||||
|
var currentNode: FileNode? = _root.value
|
||||||
|
while (currentNode != null) {
|
||||||
|
val currentPath = index[currentNode.item]
|
||||||
|
if (currentPath == path) return currentNode
|
||||||
|
currentNode = currentNode.children.find {
|
||||||
|
path.contains(index[it.item]?.fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
override var value: FileNode
|
override var value: FileNode
|
||||||
get() = _root.value
|
get() = _root.value
|
||||||
set(value) {_root.value = value}
|
set(value) {
|
||||||
|
_root.value = value
|
||||||
|
}
|
||||||
|
|
||||||
override fun component1(): FileNode {
|
override fun component1(): FileNode {
|
||||||
return _root.value
|
return _root.value
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="16dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="16dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM14,18v-3h-4v-4h4L14,8l5,5 -5,5z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/baseline_more_horiz_16.xml
Normal file
5
app/src/main/res/drawable/baseline_more_horiz_16.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="16dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||||
|
|
||||||
|
</vector>
|
Loading…
Reference in a new issue