Skip to content

Commit

Permalink
Add move-to into new file manager (#997)
Browse files Browse the repository at this point in the history
**Background**

Move-to feature allows multiple or single files to be moved inside
flipper zero file system. The feature itself contains limited
functionality of Listing. Create folders, sort, show hidden files and
etc what is often used inside move-to feature of other file managers.

**Changes**

- Add move-to feature for files
- Edit FlipperButton to add loader. Also made color change animated
- Increase timeout duration to 30 seconds

**Test plan**

- Open file manager, try move one file or multiple files
- Try the same for folders
- Try move parent folder inside parent's child folder and see it's
impossible
  • Loading branch information
makeevrserg authored Dec 3, 2024
1 parent f234ff0 commit 57d93f6
Show file tree
Hide file tree
Showing 43 changed files with 1,214 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

- [Feature] Add count subfolders for new file manager
- [Feature] Add file downloading for new file manager
- [Feature] Add move-to to new file manager
- [Refactor] Move rename and file create to separated modules
- [Refactor] Improve and refactor new FileManager Editor
- [FIX] Migrate url host from metric.flipperdevices.com to metric.flipp.dev
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ internal class PendingResponseCounter(
}

companion object {
internal const val LAGS_FLIPPER_DETECT_TIMEOUT_MS = 10 * 1000L // 10 seconds
internal const val LAGS_FLIPPER_DETECT_TIMEOUT_MS = 30 * 1000L // 30 seconds
}
}
2 changes: 2 additions & 0 deletions components/bridge/connection/sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ dependencies {
implementation(projects.components.filemngr.rename.impl)
implementation(projects.components.filemngr.create.api)
implementation(projects.components.filemngr.create.impl)
implementation(projects.components.filemngr.transfer.api)
implementation(projects.components.filemngr.transfer.impl)

implementation(projects.components.newfilemanager.api)
implementation(projects.components.newfilemanager.impl)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package com.flipperdevices.core.ui.ktx.elements

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import com.flipperdevices.core.ui.ktx.clickableRipple
import com.flipperdevices.core.ui.ktx.placeholderByLocalProvider
import com.flipperdevices.core.ui.theme.LocalPallet
import com.flipperdevices.core.ui.theme.LocalPalletV2
import com.flipperdevices.core.ui.theme.LocalTypography

@Composable
Expand All @@ -24,27 +31,45 @@ fun ComposableFlipperButton(
textPadding: PaddingValues = PaddingValues(vertical = 16.dp, horizontal = 38.dp),
onClick: () -> Unit = {},
textStyle: TextStyle = TextStyle(),
enabled: Boolean = true
enabled: Boolean = true,
isLoading: Boolean = false,
) {
val background = if (enabled) {
LocalPallet.current.accentSecond
} else {
LocalPallet.current.flipperDisableButton
}
val background by animateColorAsState(
targetValue = if (enabled && !isLoading) {
LocalPallet.current.accentSecond
} else {
LocalPallet.current.flipperDisableButton
}
)

Box(
modifier = modifier
.clip(RoundedCornerShape(size = 30.dp))
.placeholderByLocalProvider()
.background(background)
.clickableRipple(onClick = onClick),
.clickableRipple(onClick = onClick, enabled = enabled && !isLoading),
contentAlignment = Alignment.Center
) {
Text(
modifier = Modifier.padding(textPadding),
text = text,
color = LocalPallet.current.onFlipperButton,
style = LocalTypography.current.buttonB16.merge(textStyle)
AnimatedContent(
targetState = isLoading,
contentAlignment = Alignment.Center,
content = { animatedIsLoading ->
if (animatedIsLoading) {
CircularProgressIndicator(
color = LocalPalletV2.current.action.blue.icon.onColor,
modifier = Modifier.padding(textPadding).size(22.dp),
strokeCap = StrokeCap.Round,
strokeWidth = 2.dp
)
} else {
Text(
modifier = Modifier.padding(textPadding),
text = text,
color = LocalPallet.current.onFlipperButton,
style = LocalTypography.current.buttonB16.merge(textStyle)
)
}
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ class CreateFileDecomposeComponentImpl @AssistedInject constructor(
onTextChange = createFileViewModel::onNameChange,
onDismissRequest = createFileViewModel::dismiss,
onOptionSelect = createFileViewModel::onOptionSelected,
options = localState.options
options = localState.options,
isLoading = localState.isCreating
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ class DownloadViewModel @Inject constructor(
}
}.catch { it.printStackTrace() }.launchIn(viewModelScope)
_featureJob?.join()
println("DownloadViewModel out of mutex")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fun CreateFileDialogComposable(
onTextChange = editFileNameViewModel::onChange,
onDismissRequest = onDismiss,
onOptionSelect = editFileNameViewModel::onOptionSelected,
options = state.options
options = state.options,
isLoading = false
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class EditFileNameViewModel @AssistedInject constructor(

data class State(
val name: String = "",
val isValid: Boolean = false
val isValid: Boolean = false,
) {
val options = FileManagerConstants.FILE_EXTENSION_HINTS.toImmutableList()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ abstract class FilesDecomposeComponent(
path: Path,
pathChangedCallback: PathChangedCallback,
fileSelectedCallback: FileSelectedCallback,
moveToCallback: MoveToCallback,
searchCallback: SearchCallback,
): FilesDecomposeComponent
}
Expand All @@ -38,4 +39,7 @@ abstract class FilesDecomposeComponent(
fun interface UploadCallback {
fun invoke()
}
fun interface MoveToCallback {
fun invoke(fullPaths: List<Path>)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.flipperdevices.filemanager.listing.impl.model
package com.flipperdevices.filemanager.listing.api.model

import com.flipperdevices.bridge.connection.feature.storage.api.model.FileType
import com.flipperdevices.bridge.connection.feature.storage.api.model.ListingItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class FilesDecomposeComponentImpl @AssistedInject constructor(
@Assisted private val pathChangedCallback: PathChangedCallback,
@Assisted private val fileSelectedCallback: FileSelectedCallback,
@Assisted private val searchCallback: SearchCallback,
@Assisted private val moveToCallback: MoveToCallback,
private val storageInfoViewModelFactory: Provider<StorageInfoViewModel>,
private val optionsInfoViewModelFactory: Provider<OptionsViewModel>,
private val deleteFilesViewModelFactory: Provider<DeleteFilesViewModel>,
Expand Down Expand Up @@ -168,6 +169,9 @@ class FilesDecomposeComponentImpl @AssistedInject constructor(
.value,
onCreate = { type ->
createDecomposeComponent.startCreate(path, type)
},
onMove = { pathsWithType ->
moveToCallback.invoke(pathsWithType.map(PathWithType::fullPath))
}
)
FileOptionsBottomSheet(
Expand All @@ -178,6 +182,9 @@ class FilesDecomposeComponentImpl @AssistedInject constructor(
onDownloadFile = downloadDecomposeComponent::download,
onRename = { pathWithType ->
renameDecomposeComponent.startRename(pathWithType.fullPath, pathWithType.fileType)
},
onMoveTo = { pathWithType ->
moveToCallback.invoke(listOf(pathWithType.fullPath))
}
)
uploadDecomposeComponent.Render()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fun ComposableFileListScreen(
onFileMoreClick: (PathWithType) -> Unit,
onCreate: (FileType) -> Unit,
onRename: (PathWithType) -> Unit,
onMove: (List<PathWithType>) -> Unit,
modifier: Modifier = Modifier
) {
val canDeleteFiles by deleteFileViewModel.canDeleteFiles.collectAsState()
Expand Down Expand Up @@ -129,7 +130,8 @@ fun ComposableFileListScreen(
filesListState = filesListState,
selectionViewModel = selectionViewModel,
deleteFileViewModel = deleteFileViewModel,
onRename = onRename
onRename = onRename,
onMove = onMove
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import com.flipperdevices.bridge.connection.feature.storage.api.model.FileType
import com.flipperdevices.core.ktx.jre.toFormattedSize
import com.flipperdevices.core.preference.pb.FileManagerOrientation
import com.flipperdevices.filemanager.listing.impl.model.ExtendedListingItem
import com.flipperdevices.filemanager.listing.api.model.ExtendedListingItem
import com.flipperdevices.filemanager.listing.impl.model.PathWithType
import com.flipperdevices.filemanager.listing.impl.viewmodel.DeleteFilesViewModel
import com.flipperdevices.filemanager.listing.impl.viewmodel.FilesViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ fun BottomSheetOptionsContent(
text = stringResource(FML.string.fml_move_to),
painter = painterResource(FR.drawable.ic_move),
onClick = onMoveTo,
isEnabled = false
isEnabled = true
)
HorizontalTextIconButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(FML.string.fml_export),
painter = painterResource(FR.drawable.ic_upload),
onClick = onExport,
isEnabled = true
isEnabled = fileType == FileType.FILE
)
HorizontalTextIconButton(
modifier = Modifier.fillMaxWidth(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fun FileOptionsBottomSheet(
deleteFileViewModel: DeleteFilesViewModel,
onDownloadFile: (Path, Long) -> Unit,
onRename: (PathWithType) -> Unit,
onMoveTo: (PathWithType) -> Unit,
modifier: Modifier = Modifier
) {
SlotModalBottomSheet(
Expand Down Expand Up @@ -47,7 +48,9 @@ fun FileOptionsBottomSheet(
deleteFileViewModel.tryDelete(pathWithType.fullPath)
slotNavigation.dismiss()
},
onMoveTo = {} // todo
onMoveTo = {
onMoveTo.invoke(pathWithType)
}
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ fun FullScreenBottomBarOptions(
filesListState: FilesViewModel.State,
selectionState: SelectionViewModel.State,
onRename: (PathWithType) -> Unit,
onMove: (List<PathWithType>) -> Unit,
modifier: Modifier = Modifier
) {
Box(
Expand All @@ -156,9 +157,12 @@ fun FullScreenBottomBarOptions(
) {
BottomBarOptions(
canRename = selectionState.canRename,
onMove = {}, // todo
onMove = {
onMove.invoke(selectionState.selected.toList())
},
onRename = {
val pathWithType = selectionState.selected.firstOrNull() ?: return@BottomBarOptions
val pathWithType =
selectionState.selected.firstOrNull() ?: return@BottomBarOptions
selectionViewModel.toggleMode()
onRename.invoke(pathWithType)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import com.flipperdevices.core.log.error
import com.flipperdevices.core.preference.pb.FileManagerSort
import com.flipperdevices.core.preference.pb.Settings
import com.flipperdevices.core.ui.lifecycle.DecomposeViewModel
import com.flipperdevices.filemanager.listing.impl.model.ExtendedListingItem
import com.flipperdevices.filemanager.listing.api.model.ExtendedListingItem
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand Down
1 change: 1 addition & 0 deletions components/filemngr/main/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ commonDependencies {

implementation(projects.components.filemngr.uiComponents)
implementation(projects.components.filemngr.main.api)
implementation(projects.components.filemngr.transfer.api)
implementation(projects.components.filemngr.listing.api)
implementation(projects.components.filemngr.upload.api)
implementation(projects.components.filemngr.search.api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import com.flipperdevices.filemanager.listing.api.FilesDecomposeComponent
import com.flipperdevices.filemanager.main.api.FileManagerDecomposeComponent
import com.flipperdevices.filemanager.main.impl.model.FileManagerNavigationConfig
import com.flipperdevices.filemanager.search.api.SearchDecomposeComponent
import com.flipperdevices.filemanager.transfer.api.TransferDecomposeComponent
import com.flipperdevices.filemanager.transfer.api.model.TransferType
import com.flipperdevices.ui.decompose.DecomposeComponent
import com.flipperdevices.ui.decompose.DecomposeOnBackParameter
import com.flipperdevices.ui.decompose.findComponentByConfig
Expand All @@ -28,6 +30,7 @@ class FileManagerDecomposeComponentImpl @AssistedInject constructor(
private val filesDecomposeComponentFactory: FilesDecomposeComponent.Factory,
private val searchDecomposeComponentFactory: SearchDecomposeComponent.Factory,
private val editorDecomposeComponentFactory: FileManagerEditorDecomposeComponent.Factory,
private val transferDecomposeComponentFactory: TransferDecomposeComponent.Factory
) : FileManagerDecomposeComponent<FileManagerNavigationConfig>(),
ComponentContext by componentContext {

Expand All @@ -39,6 +42,7 @@ class FileManagerDecomposeComponentImpl @AssistedInject constructor(
childFactory = ::child,
)

@Suppress("LongMethod")
private fun child(
config: FileManagerNavigationConfig,
componentContext: ComponentContext
Expand All @@ -54,6 +58,15 @@ class FileManagerDecomposeComponentImpl @AssistedInject constructor(
fileSelectedCallback = {
navigation.pushNew(FileManagerNavigationConfig.Edit(it))
},
moveToCallback = { fullPaths ->
navigation.pushNew(
FileManagerNavigationConfig.Transfer(
path = config.path,
transferType = TransferType.MOVE,
fullPathToMove = fullPaths
)
)
},
searchCallback = { navigation.pushNew(FileManagerNavigationConfig.Search(config.path)) },
)
}
Expand All @@ -80,5 +93,29 @@ class FileManagerDecomposeComponentImpl @AssistedInject constructor(
}
)
}

is FileManagerNavigationConfig.Transfer -> {
transferDecomposeComponentFactory.invoke(
componentContext = componentContext,
param = TransferDecomposeComponent.Param(
path = config.path,
transferType = config.transferType,
fullPathToMove = config.fullPathToMove
),
onBack = { navigation.popOr(onBack::invoke) },
onMoved = { path ->
navigation.replaceAll(FileManagerNavigationConfig.FileTree(path))
},
onPathChange = { path ->
navigation.replaceCurrent(
FileManagerNavigationConfig.Transfer(
path = path,
transferType = config.transferType,
fullPathToMove = config.fullPathToMove
)
)
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.flipperdevices.filemanager.main.impl.model

import androidx.compose.runtime.Stable
import com.flipperdevices.filemanager.transfer.api.model.TransferType
import com.flipperdevices.filemanager.util.serialization.PathSerializer
import kotlinx.serialization.Serializable
import okio.Path
Expand All @@ -27,6 +28,17 @@ sealed interface FileManagerNavigationConfig {
val path: Path
) : FileManagerNavigationConfig

@Serializable
data class Transfer(
@Serializable(with = PathSerializer::class)
val path: Path,
val transferType: TransferType,
val fullPathToMove: List<
@Serializable(with = PathSerializer::class)
Path
>,
) : FileManagerNavigationConfig

companion object {
val DefaultFileTree: FileTree
get() = FileTree("/".toPath())
Expand Down
Loading

0 comments on commit 57d93f6

Please sign in to comment.