Skip to content

Commit

Permalink
FileManager selection, renaming, file options (#966)
Browse files Browse the repository at this point in the history
**Background**

Add more features to file manager

**Changes**

- Selection
- Multiple deletions
- Rename(aka move)
- Fix uploading doesn't start(replaced SharedFlow with Channel)
- Improve OrangeAppBar
- BottomSheet options for file/folder

**Test plan**

- Open bridge connection sample
- Try click `more` button
- Try click select
- Use new options and see cool animations
  • Loading branch information
makeevrserg authored Oct 10, 2024
1 parent 989bb69 commit 247fce4
Show file tree
Hide file tree
Showing 36 changed files with 1,487 additions and 327 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Attention: don't forget to add the flag for F-Droid before release
- [Feature] Add new icons for remote-controls
- [Feature] Add experimental option to enable remote controls
- [Feature] Better information during synchronization
- [Feature] New File Manager listing and uploading
- [Feature] Add vibration off switch
- [Feature] New File Manager listing and uploading
- [Refactor] Load RemoteControls from flipper, emulating animation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ interface FFileUploadApi {
pathOnFlipper: String,
priority: StorageRequestPriority = StorageRequestPriority.DEFAULT
): Sink

suspend fun move(
oldPath: Path,
newPath: Path,
): Result<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.flipperdevices.core.progress.FixedProgressListener
import com.flipperdevices.core.progress.copyWithProgress
import com.flipperdevices.protobuf.Main
import com.flipperdevices.protobuf.storage.MkdirRequest
import com.flipperdevices.protobuf.storage.RenameRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -63,4 +64,16 @@ class FFileUploadApiImpl(
): Sink = FFlipperSink(
requestLooper = WriteRequestLooper(rpcFeatureApi, pathOnFlipper, priority.toRpc(), scope),
)

override suspend fun move(
oldPath: Path,
newPath: Path,
): Result<Unit> = rpcFeatureApi.requestOnce(
command = Main(
storage_rename_request = RenameRequest(
old_path = oldPath.toString(),
new_path = newPath.toString()
)
).wrapToRequest()
).map { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import com.flipperdevices.bridge.connection.feature.rpc.model.FlipperRequest
import com.flipperdevices.bridge.connection.feature.rpc.model.FlipperRequestPriority
import com.flipperdevices.bridge.connection.feature.rpc.model.wrapToRequest
import com.flipperdevices.core.ktx.jre.FlipperDispatchers
import com.flipperdevices.core.log.LogTagProvider
import com.flipperdevices.protobuf.Main
import com.flipperdevices.protobuf.storage.File
import com.flipperdevices.protobuf.storage.WriteRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
Expand All @@ -25,15 +26,18 @@ internal class WriteRequestLooper(
private val pathOnFlipper: String,
private val priority: FlipperRequestPriority,
scope: CoroutineScope
) {
private val commands = MutableSharedFlow<FlipperRequest>()
) : LogTagProvider {
override val TAG = "WriteRequestLooper"

// We can't send to channel when it has zero subscribers
private val commands = Channel<FlipperRequest>()
private val result = MutableStateFlow<Result<Main>?>(null)

init {
scope.launch(FlipperDispatchers.workStealingDispatcher) {
result.emit(
rpcFeatureApi.request(
commandFlow = commands,
commandFlow = commands.consumeAsFlow(),
onCancel = {
withContext(NonCancellable) {
rpcFeatureApi.requestOnce(
Expand All @@ -56,7 +60,7 @@ internal class WriteRequestLooper(
hasNext: Boolean = true
): Unit = runBlocking {
val waiter = Channel<Unit>()
commands.emit(
commands.send(
FlipperRequest(
data = Main(
has_next = hasNext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,132 @@ import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.flipperdevices.core.ui.theme.LocalPallet
import com.flipperdevices.core.ui.theme.FlipperThemeInternal
import com.flipperdevices.core.ui.theme.LocalPalletV2
import com.flipperdevices.core.ui.theme.LocalTypography
import com.flipperdevices.core.ui.res.R as DesignSystem

@Composable
fun OrangeAppBar(
@StringRes titleId: Int,
modifier: Modifier = Modifier,
title: (@Composable RowScope.() -> Unit)? = null,
startBlock: (@Composable RowScope.() -> Unit)? = null,
endBlock: (@Composable RowScope.(Modifier) -> Unit)? = null
) {
Row(
modifier = modifier
.fillMaxWidth()
.heightIn(42.dp)
.background(LocalPalletV2.current.surface.navBar.body.accentBrand)
.statusBarsPadding(),
verticalAlignment = Alignment.CenterVertically
) {
startBlock?.invoke(this)
title?.invoke(this)
endBlock?.invoke(this, Modifier.padding(end = 14.dp))
}
}

@Composable
fun OrangeAppBar(
title: String,
modifier: Modifier = Modifier,
onBack: (() -> Unit)? = null,
endBlock: (@Composable (Modifier) -> Unit)? = null
) {
OrangeAppBar(
modifier = modifier,
title = stringResource(titleId),
onBack = onBack,
endBlock = endBlock
startBlock = {
if (onBack != null) {
Image(
modifier = Modifier
.padding(top = 11.dp, bottom = 11.dp, start = 16.dp, end = 2.dp)
.size(24.dp)
.clickableRipple(bounded = false, onClick = onBack),
painter = painterResource(DesignSystem.drawable.ic_back),
contentDescription = null
)
}
},
title = {
Text(
modifier = Modifier
.padding(start = 14.dp, end = 14.dp, top = 8.dp, bottom = 11.dp)
.weight(1f),
text = title,
style = LocalTypography.current.titleB20,
color = LocalPalletV2.current.text.label.blackOnColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
endBlock = {
if (endBlock != null) {
endBlock(Modifier.padding(end = 14.dp))
}
}
)
}

@Composable
fun OrangeAppBar(
fun OrangeAppBarWithIcon(
title: String,
endIconPainter: Painter,
modifier: Modifier = Modifier,
onBack: (() -> Unit)? = null,
endBlock: (@Composable (Modifier) -> Unit)? = null
onEndClick: () -> Unit
) {
Row(
modifier = modifier
.fillMaxWidth()
.background(LocalPallet.current.accent)
.statusBarsPadding(),
verticalAlignment = Alignment.CenterVertically
) {
if (onBack != null) {
Image(
OrangeAppBar(
modifier = modifier,
title = title,
onBack = onBack,
endBlock = {
Icon(
modifier = Modifier
.padding(top = 11.dp, bottom = 11.dp, start = 16.dp, end = 2.dp)
.size(20.dp)
.clickableRipple(bounded = false, onClick = onBack),
painter = painterResource(DesignSystem.drawable.ic_back),
contentDescription = null
.padding(end = 14.dp)
.size(24.dp)
.clickableRipple(onClick = onEndClick),
painter = endIconPainter,
contentDescription = null,
tint = LocalPalletV2.current.icon.blackAndWhite.default
)
}
Text(
modifier = Modifier
.padding(start = 14.dp, end = 14.dp, top = 8.dp, bottom = 11.dp)
.weight(1f),
text = title,
style = LocalTypography.current.titleB20,
color = LocalPallet.current.onAppBar,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (endBlock != null) {
endBlock(Modifier.padding(end = 14.dp))
}
}
)
}

@Composable
fun OrangeAppBar(
@StringRes titleId: Int,
modifier: Modifier = Modifier,
onBack: (() -> Unit)? = null,
endBlock: (@Composable (Modifier) -> Unit)? = null
) {
OrangeAppBar(
modifier = modifier,
title = stringResource(titleId),
onBack = onBack,
endBlock = endBlock
)
}

@Composable
Expand All @@ -100,20 +156,43 @@ fun OrangeAppBarWithIcon(
onBack: (() -> Unit)? = null,
onEndClick: () -> Unit
) {
OrangeAppBar(
modifier = modifier,
OrangeAppBarWithIcon(
title = title,
endIconPainter = painterResource(endIconId),
modifier = modifier,
onBack = onBack,
endBlock = {
Icon(
modifier = Modifier
.padding(end = 14.dp)
.size(24.dp)
.clickableRipple(onClick = onEndClick),
painter = painterResource(endIconId),
contentDescription = null,
tint = LocalPallet.current.onAppBar
)
}
onEndClick = onEndClick
)
}

@Preview
@Composable
private fun OrangeAppBarPreview() {
FlipperThemeInternal {
OrangeAppBar(
title = "Screenname",
onBack = {}
)
}
}

@Preview
@Composable
private fun OrangeAppBarEndBlockPreview() {
FlipperThemeInternal {
OrangeAppBar(
title = "Screenname",
onBack = {},
endBlock = {
Icon(
modifier = Modifier
.padding(end = 14.dp)
.size(24.dp),
painter = rememberVectorPainter(Icons.Filled.Settings),
contentDescription = null,
tint = LocalPalletV2.current.icon.blackAndWhite.default
)
}
)
}
}
2 changes: 2 additions & 0 deletions components/filemngr/listing/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ androidDependencies {

implementation(projects.components.filemngr.uiComponents)
implementation(projects.components.filemngr.listing.api)
implementation(projects.components.filemngr.main.api)

// Compose
implementation(libs.compose.ui)
implementation(libs.compose.tooling)
implementation(libs.compose.foundation)
implementation(libs.compose.material)
implementation(libs.compose.material3)
implementation(libs.compose.material.icons.core)
implementation(libs.compose.material.icons.extended)

Expand Down
Loading

0 comments on commit 247fce4

Please sign in to comment.