Skip to content

Commit

Permalink
refactor: check
Browse files Browse the repository at this point in the history
  • Loading branch information
jing332 committed Feb 1, 2024
1 parent fcd6466 commit 27c6186
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,74 @@
package com.github.jing332.compose_filepicker

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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.unit.dp
import com.github.jing332.compose_filepicker.ui.theme.ComposefilepickerTheme
import com.github.jing332.filepicker.FilePicker
import com.github.jing332.filepicker.FilePickerConfig
import com.github.jing332.filepicker.model.IFileModel

class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ComposefilepickerTheme {
Scaffold {
Column(Modifier.padding(bottom = it.calculateBottomPadding())) {
FilePicker(config = FilePickerConfig(
var showSelectedList by remember { mutableStateOf<List<IFileModel>?>(null) }
if (showSelectedList != null) {
val list = showSelectedList!!
ModalBottomSheet(onDismissRequest = { showSelectedList = null }) {
LazyColumn {
items(list) {
Column(Modifier.padding(16.dp)) {
Text(text = it.name)
Text(text = it.path)
}
}
}
}
}

Scaffold { paddingValues ->
Column(Modifier.padding(bottom = paddingValues.calculateBottomPadding())) {
FilePicker(
config = FilePickerConfig(
// fileFilter = { it.name.startsWith("a", ignoreCase = true) },
fileSelector = { it.name.startsWith("A") }
))

// Only checkable name prefix is 'A' files
fileSelector = { _, checked ->
checked.name.startsWith("A").also {
if (!it) {
Toast.makeText(
this@MainActivity,
"Only items starting with 'A' can be checked",
Toast.LENGTH_SHORT
).show()
}
}
}
),
onConfirmSelect = {
showSelectedList = it
}
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ val LocalFilePickerConfig = compositionLocalOf<FilePickerConfig> { error("No con

object Contants {
const val ROUTE_PAGE = "page"
const val ARG_URI = "uri"
const val ARG_PATH = "uri"
const val DEFAULT_ROOT_PATH = "/storage/emulated/0"

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -28,8 +27,11 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.github.jing332.filepicker.Contants.ARG_URI
import com.github.jing332.filepicker.Contants.ARG_PATH
import com.github.jing332.filepicker.Contants.ROUTE_PAGE
import com.github.jing332.filepicker.listpage.FileListPage
import com.github.jing332.filepicker.listpage.FileListPageState
import com.github.jing332.filepicker.model.IFileModel
import com.github.jing332.filepicker.model.NormalFile
import com.github.jing332.filepicker.utils.navigate
import com.google.accompanist.permissions.ExperimentalPermissionsApi
Expand Down Expand Up @@ -75,7 +77,11 @@ private fun PermissionGrant() {


@Composable
fun FilePicker(modifier: Modifier = Modifier, config: FilePickerConfig = FilePickerConfig()) {
fun FilePicker(
modifier: Modifier = Modifier,
config: FilePickerConfig = FilePickerConfig(),
onConfirmSelect: (List<IFileModel>) -> Unit
) {
val vm: FilePickerViewModel = viewModel()
val navController = rememberNavController()
val navBarItems = remember { mutableStateListOf<NavBarItem>() }
Expand All @@ -85,10 +91,17 @@ fun FilePicker(modifier: Modifier = Modifier, config: FilePickerConfig = FilePic
navController.popBackStack()
}

fun navigateNewPath(path: String) {
vm.fileListStates[path] = FileListPageState()
navController.navigate(ROUTE_PAGE, Bundle().apply {
putString(ARG_PATH, path)
})
}

fun update() {
navController.popBackStack()
navController.navigate(ROUTE_PAGE, Bundle().apply {
putString(ARG_URI, config.rootPath)
putString(ARG_PATH, config.rootPath)
})
}

Expand All @@ -98,9 +111,10 @@ fun FilePicker(modifier: Modifier = Modifier, config: FilePickerConfig = FilePic
LocalNavController provides navController,
LocalFilePickerConfig provides config,
) {
var selectedCount by remember { mutableIntStateOf(0) }
Column(modifier) {
var sortConfig by remember { mutableStateOf(config.sortConfig) }
fun getState() = vm.fileListStates[vm.currentPath]
val selectedCount = getState()?.items?.count { it.isChecked.value } ?: 0
FilePickerToolbar(
modifier = Modifier.fillMaxWidth(),
title = navBarItems.lastOrNull()?.name ?: "",
Expand All @@ -111,8 +125,13 @@ fun FilePicker(modifier: Modifier = Modifier, config: FilePickerConfig = FilePic
update()
},
selectedCount = selectedCount,
onCancelSelect = { selectedCount = 0},
onConfirmSelect = { }
onCancelSelect = {
getState()?.uncheckAll()
},
onConfirmSelect = {
onConfirmSelect(getState()?.items?.filter { it.isChecked.value }
?.map { it.model } ?: emptyList())
}
)

FileNavBar(
Expand All @@ -121,7 +140,7 @@ fun FilePicker(modifier: Modifier = Modifier, config: FilePickerConfig = FilePic
onClick = { item ->
while (true) {
val uri =
navController.currentBackStackEntry?.arguments?.getString(ARG_URI)
navController.currentBackStackEntry?.arguments?.getString(ARG_PATH)
?: break
if (uri == item.path) break
else popBack()
Expand All @@ -134,32 +153,36 @@ fun FilePicker(modifier: Modifier = Modifier, config: FilePickerConfig = FilePic
startDestination = ROUTE_PAGE
) {
composable(ROUTE_PAGE) { entry ->
LaunchedEffect(key1 = Unit) {

navController.enableOnBackPressed(false)
val path = entry.arguments?.getString(ARG_PATH) ?: config.rootPath
val fileListState = vm.fileListStates[path] ?: FileListPageState().run {
vm.fileListStates[path] = this
this
}

navController.enableOnBackPressed(false)
val uri = entry.arguments?.getString(ARG_URI) ?: config.rootPath
BackHandler(uri != config.rootPath) {
LaunchedEffect(key1 = Unit) {
vm.currentPath = path
}
BackHandler(path != config.rootPath) {
popBack()
}
BackHandler(selectedCount > 0) {
getState()?.uncheckAll()
}

val file = File(uri)
val file = File(path)
if (navBarItems.isEmpty())
navBarItems.add(NavBarItem(name = file.name, path = file.path))

FileListPage(
file = NormalFile(file),
state = fileListState,
onBack = { popBack() },
onEnter = { enterFile ->
navBarItems += NavBarItem(name = enterFile.name, path = enterFile.path)
navController.navigate(ROUTE_PAGE, Bundle().apply {
putString(ARG_URI, enterFile.path)
})
navigateNewPath(enterFile.path)
},
selectedCount = selectedCount,
onSelectedCountChange = { selectedCount = it }
)

)

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ fun interface FileFilter {
}

fun interface FileSelector {
fun select(file: IFileModel): Boolean
fun select(checkedList: List<IFileModel>, check: IFileModel): Boolean
}

data class FilePickerConfig(
val rootPath: String = DEFAULT_ROOT_PATH,

val fileDetector: FileDetector = FileDetector(),
val fileFilter: FileFilter = FileFilter { true },
val fileSelector: FileSelector = FileSelector { true },
val fileSelector: FileSelector = FileSelector { _, _ -> true },

var sortConfig: SortConfig = SortConfig(),
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package com.github.jing332.filepicker

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.github.jing332.filepicker.listpage.FileListPageState

class FilePickerViewModel : ViewModel() {
var currentPath by mutableStateOf("")
val fileListStates = mutableMapOf<String, FileListPageState>()

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.jing332.filepicker
package com.github.jing332.filepicker.listpage

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.ExperimentalFoundationApi
Expand Down Expand Up @@ -27,52 +27,57 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.github.jing332.filepicker.LocalFilePickerConfig
import com.github.jing332.filepicker.R
import com.github.jing332.filepicker.model.IFileModel
import com.github.jing332.filepicker.utils.performLongPress


@Composable
fun FileListPage(
modifier: Modifier = Modifier,
state: FileListPageState = FileListPageState(),
file: IFileModel,
onBack: () -> Unit,
onEnter: (IFileModel) -> Unit,

selectedCount: Int,
onSelectedCountChange: (Int) -> Unit
vm: FileListPageViewModel = viewModel(key = file.name + "_" + file.path)
) {
val vm: FileListPageViewModel = viewModel(key = file.name + "_" + file.path)
val hasChecked by rememberUpdatedState(newValue = vm.hasChecked())
val hasChecked by rememberUpdatedState(newValue = state.hasChecked())
val config = LocalFilePickerConfig.current

val view = LocalView.current
LaunchedEffect(key1 = state) {
vm.state = state
}
LaunchedEffect(key1 = file) {
if (vm.files.isEmpty())
if (state.items.isEmpty())
vm.updateFiles(file, config)
}

LaunchedEffect(key1 = hasChecked) {
if (hasChecked) view.performLongPress()
}

LaunchedEffect(key1 = selectedCount) {
if (selectedCount == 0)
vm.cancelSelect()
}

LazyColumn(
modifier = modifier,
state = vm.listState
state = state.listState
) {
itemsIndexed(vm.files, key = { _, item -> item.key }) { _, item ->
itemsIndexed(state.items, key = { _, item -> item.key }) { _, item ->
fun isCheckable(): Boolean {
if (item.isBackType) return false
val checkedList = state.items.filter { it.isChecked.value }.map { it.model }
return config.fileSelector.select(checkedList, item.model)
}

fun check(checked: Boolean = !item.isChecked.value) {
item.isChecked.value = checked
onSelectedCountChange(vm.selectedCount())
if (isCheckable())
state.check(item, checked)
}

Item(
isChecked = item.isChecked.value,
isCheckable = item.isCheckable.value,
// isCheckable = item.isCheckable.value,
icon = {
if (item.isDirectory) {
Icon(
Expand Down Expand Up @@ -111,26 +116,24 @@ fun FileListPage(
)
}
},
onCheckedChange = {
item.isChecked.value = it
onCheckedChange = { checked ->
if (checked) {
if (isCheckable())
check(true)
} else
item.isChecked.value = false
},
onClick = {
if (item.isBackType)
onBack()
else if (!hasChecked && !item.isChecked.value && item.isDirectory)
onEnter(item.model)
else if (config.fileSelector.select(item.model))
check()
else
else if (isCheckable())
check()
},
onLongClick = {
if (item.isBackType)
onBack()
else if (!config.fileSelector.select(item.model)) return@Item
else if (item.isCheckable.value) {
check()
}
if (item.isBackType) onBack()
else if (isCheckable()) check()
}
)
}
Expand All @@ -144,7 +147,7 @@ private fun Item(
modifier: Modifier = Modifier,
isChecked: Boolean = false,
onCheckedChange: (Boolean) -> Unit,
isCheckable: Boolean = false,
isCheckable: Boolean = true,
icon: @Composable () -> Unit,
title: @Composable () -> Unit,
subtitle: @Composable () -> Unit,
Expand Down
Loading

0 comments on commit 27c6186

Please sign in to comment.