feat: add notes
This commit is contained in:
parent
5d4dd31b70
commit
c3d8f75ee6
6 changed files with 235 additions and 91 deletions
|
@ -4,15 +4,28 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.dazuoye.filemanager.fileSystem.DeleteHelper.Companion.delete
|
import com.dazuoye.filemanager.fileSystem.DeleteHelper.Companion.delete
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
open class BaseActivity: AppCompatActivity() {
|
/**
|
||||||
|
* BaseActivity 继承自 AppCompatActivity,提供了一些基础功能。
|
||||||
|
* 主要功能是在销毁 Activity 时清理缓存和释放系统资源。
|
||||||
|
*/
|
||||||
|
open class BaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Activity 销毁时调用的方法。
|
||||||
|
* 此方法用于删除缓存中的 "clipboard" 文件并触发垃圾回收。
|
||||||
|
*/
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
val clipFile = File(this.cacheDir,"clipboard")
|
|
||||||
if (clipFile.exists()){
|
// 创建一个指向缓存目录中 "clipboard" 文件的引用
|
||||||
|
val clipFile = File(this.cacheDir, "clipboard")
|
||||||
|
|
||||||
|
// 如果 "clipboard" 文件存在,调用 delete 方法删除文件
|
||||||
|
if (clipFile.exists()) {
|
||||||
delete(clipFile.path)
|
delete(clipFile.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显式调用垃圾回收器,提示系统进行内存回收
|
||||||
System.gc()
|
System.gc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,26 +13,36 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
// 扩展 Context,定义一个 DataStore,用于存储应用设置
|
||||||
val Context.settingStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")
|
val Context.settingStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SettingStorage 类用于管理和访问应用的设置存储。
|
||||||
|
* 它封装了 DataStore 访问逻辑,支持异步获取和设置偏好数据。
|
||||||
|
*/
|
||||||
class SettingStorage(private val context: Context) {
|
class SettingStorage(private val context: Context) {
|
||||||
val hideExtension = booleanPreferencesKey("hide_extension")
|
val hideExtension = booleanPreferencesKey("hide_extension")
|
||||||
val hideHiddenFile = booleanPreferencesKey("hide_hidden_file")
|
val hideHiddenFile = booleanPreferencesKey("hide_hidden_file")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取给定偏好设置键的值。
|
||||||
|
* @param key 偏好设置键
|
||||||
|
* @return 偏好设置的值,如果未设置则返回 null
|
||||||
|
*/
|
||||||
|
fun <T> get(key: Preferences.Key<T>): T? = runBlocking {
|
||||||
|
// 使用 DataStore 获取偏好数据,并获取与给定键关联的值
|
||||||
|
context.settingStore.data
|
||||||
|
.map { value -> value[key] }
|
||||||
|
.first() // 获取流的第一个值
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> get(key: Preferences.Key<T>): T? =
|
/**
|
||||||
runBlocking {
|
* 设置给定偏好设置键的值。
|
||||||
context.settingStore.data
|
* @param key 偏好设置键
|
||||||
.map { value ->
|
* @param value 要存储的值
|
||||||
value[key]
|
*/
|
||||||
}
|
fun <T> set(key: Preferences.Key<T>, value: T) = CoroutineScope(Dispatchers.Default).launch {
|
||||||
.first()
|
// 使用 DataStore 更新偏好数据
|
||||||
}
|
context.settingStore.edit { it[key] = value }
|
||||||
|
}
|
||||||
fun <T> set(key: Preferences.Key<T>, value: T) =
|
}
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
|
||||||
context.settingStore.edit {
|
|
||||||
it[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,34 +5,52 @@ import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.nio.channels.FileChannel
|
import java.nio.channels.FileChannel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PasteHelper 类提供了静态方法,用于复制文件和目录。
|
||||||
|
*/
|
||||||
class PasteHelper {
|
class PasteHelper {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制目录及其所有内容到目标目录。
|
||||||
|
* @param sourceDir 源目录
|
||||||
|
* @param destDir 目标目录
|
||||||
|
* 如果目标目录不存在,则创建它。
|
||||||
|
* 如果源目录不存在或参数不是目录,则抛出异常。
|
||||||
|
*/
|
||||||
fun copyDirectory(sourceDir: File, destDir: File) {
|
fun copyDirectory(sourceDir: File, destDir: File) {
|
||||||
// creates the destination directory if it does not exist
|
// 如果目标目录不存在,则创建它
|
||||||
if (!destDir.exists()) {
|
if (!destDir.exists()) {
|
||||||
destDir.mkdirs()
|
destDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
// throws exception if the source does not exist
|
// 如果源目录不存在,则抛出异常
|
||||||
require(sourceDir.exists()) { "sourceDir does not exist" }
|
require(sourceDir.exists()) { "sourceDir does not exist" }
|
||||||
|
|
||||||
// throws exception if the arguments are not directories
|
// 如果参数不是目录,则抛出异常
|
||||||
require(!(sourceDir.isFile || destDir.isFile)) { "Either sourceDir or destDir is not a directory" }
|
require(!(sourceDir.isFile || destDir.isFile)) { "Either sourceDir or destDir is not a directory" }
|
||||||
|
|
||||||
|
// 调用内部方法递归复制目录
|
||||||
copyDirectoryImpl(sourceDir, destDir)
|
copyDirectoryImpl(sourceDir, destDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现目录复制的内部方法。
|
||||||
|
* @param sourceDir 源目录
|
||||||
|
* @param destDir 目标目录
|
||||||
|
* 遍历源目录中的所有文件和子目录,并递归复制它们。
|
||||||
|
*/
|
||||||
private fun copyDirectoryImpl(sourceDir: File, destDir: File) {
|
private fun copyDirectoryImpl(sourceDir: File, destDir: File) {
|
||||||
val items = sourceDir.listFiles()
|
val items = sourceDir.listFiles()
|
||||||
if (items != null && items.isNotEmpty()) {
|
if (items != null && items.isNotEmpty()) {
|
||||||
for (anItem: File in items) {
|
for (anItem: File in items) {
|
||||||
if (anItem.isDirectory) {
|
if (anItem.isDirectory) {
|
||||||
val newDir = File(destDir, anItem.name)
|
val newDir = File(destDir, anItem.name)
|
||||||
newDir.mkdir()
|
newDir.mkdir() // 创建子目录
|
||||||
// copy the directory (recursive call)
|
// 递归复制目录
|
||||||
copyDirectory(anItem, newDir)
|
copyDirectory(anItem, newDir)
|
||||||
} else {
|
} else {
|
||||||
// copy the file
|
// 复制单个文件
|
||||||
val destFile = File(destDir, anItem.name)
|
val destFile = File(destDir, anItem.name)
|
||||||
copySingleFile(anItem, destFile)
|
copySingleFile(anItem, destFile)
|
||||||
}
|
}
|
||||||
|
@ -40,21 +58,30 @@ class PasteHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制单个文件到目标位置。
|
||||||
|
* @param sourceFile 源文件
|
||||||
|
* @param destFile 目标文件
|
||||||
|
* 如果目标文件不存在,则创建它。
|
||||||
|
*/
|
||||||
private fun copySingleFile(sourceFile: File, destFile: File) {
|
private fun copySingleFile(sourceFile: File, destFile: File) {
|
||||||
if (!destFile.exists()) {
|
if (!destFile.exists()) {
|
||||||
destFile.createNewFile()
|
destFile.createNewFile() // 创建目标文件
|
||||||
}
|
}
|
||||||
|
|
||||||
var sourceChannel: FileChannel? = null
|
var sourceChannel: FileChannel? = null
|
||||||
var destChannel: FileChannel? = null
|
var destChannel: FileChannel? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 使用文件通道进行文件复制
|
||||||
sourceChannel = FileInputStream(sourceFile).channel
|
sourceChannel = FileInputStream(sourceFile).channel
|
||||||
destChannel = FileOutputStream(destFile).channel
|
destChannel = FileOutputStream(destFile).channel
|
||||||
sourceChannel.transferTo(0, sourceChannel.size(), destChannel)
|
sourceChannel.transferTo(0, sourceChannel.size(), destChannel)
|
||||||
} finally {
|
} finally {
|
||||||
|
// 关闭文件通道,释放资源
|
||||||
sourceChannel?.close()
|
sourceChannel?.close()
|
||||||
destChannel?.close()
|
destChannel?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,84 +36,115 @@ import com.dazuoye.filemanager.fileSystem.byTypeFileLister.MusicLister
|
||||||
import com.dazuoye.filemanager.fileSystem.byTypeFileLister.VideoLister
|
import com.dazuoye.filemanager.fileSystem.byTypeFileLister.VideoLister
|
||||||
import com.dazuoye.filemanager.main_page
|
import com.dazuoye.filemanager.main_page
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RequirePermissionActivity 继承自 ComponentActivity,用于请求用户的存储权限。
|
||||||
|
* 如果权限已经被授予,应用将导航到主页面并初始化系统。
|
||||||
|
*/
|
||||||
class RequirePermissionActivity : ComponentActivity() {
|
class RequirePermissionActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Activity 创建时调用的方法。
|
||||||
|
* 该方法启用全屏模式,设置状态栏颜色,并构建一个界面来提示用户授予存储权限。
|
||||||
|
*/
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge() // 启用全屏边到边显示
|
||||||
val activity = this
|
val activity = this
|
||||||
window.statusBarColor = getColor(R.color.WhiteSmoke)
|
window.statusBarColor = getColor(R.color.WhiteSmoke) // 设置状态栏颜色
|
||||||
|
|
||||||
|
// 使用 Jetpack Compose 构建 UI
|
||||||
setContent {
|
setContent {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.statusBarsPadding()
|
.statusBarsPadding() // 为状态栏留出空间
|
||||||
.fillMaxHeight(0.9f)
|
.fillMaxHeight(0.9f) // 设置列高度为 90%
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(), // 填满宽度
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally, // 水平居中
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center // 垂直居中
|
||||||
) {
|
) {
|
||||||
|
// 显示提示文本,根据系统版本显示不同的提示信息
|
||||||
Text(
|
Text(
|
||||||
text = getString(
|
text = getString(
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.R) {
|
if (VERSION.SDK_INT >= VERSION_CODES.R) {
|
||||||
R.string.require_manage_storage
|
R.string.require_manage_storage // Android R 及以上需要的权限
|
||||||
} else {
|
} else {
|
||||||
R.string.require_permission_readwrite
|
R.string.require_permission_readwrite // 较低版本需要的权限
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
modifier = Modifier.padding(vertical = 10.dp),
|
modifier = Modifier.padding(vertical = 10.dp), // 设置垂直内边距
|
||||||
fontSize = 30.sp
|
fontSize = 30.sp // 设置字体大小
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 按钮,用于请求用户授予存储权限
|
||||||
Button(
|
Button(
|
||||||
onClick = { // Ask for permission
|
onClick = { // 按钮点击事件
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.R) {
|
if (VERSION.SDK_INT >= VERSION_CODES.R) {
|
||||||
|
// Android R 及以上版本
|
||||||
if (!Environment.isExternalStorageManager()) {
|
if (!Environment.isExternalStorageManager()) {
|
||||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||||
val uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
|
val uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
|
||||||
intent.setData(uri)
|
intent.setData(uri)
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) // 清空栈顶
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
} else { // for legacy system
|
} else {
|
||||||
val permissions =
|
// 针对较旧版本的系统请求权限
|
||||||
arrayOf(permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE)
|
val permissions = arrayOf(
|
||||||
|
permission.READ_EXTERNAL_STORAGE,
|
||||||
|
permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
ActivityCompat.requestPermissions(
|
ActivityCompat.requestPermissions(
|
||||||
activity, permissions, 100
|
activity, permissions, 100
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = ButtonColors(
|
colors = ButtonColors(
|
||||||
containerColor = Color(0xFF039BE5),
|
containerColor = Color(0xFF039BE5), // 按钮背景颜色
|
||||||
contentColor = Color.White,
|
contentColor = Color.White, // 按钮文本颜色
|
||||||
disabledContainerColor = Color.Gray,
|
disabledContainerColor = Color.Gray, // 按钮禁用状态背景颜色
|
||||||
disabledContentColor = Color.White
|
disabledContentColor = Color.White // 按钮禁用状态文本颜色
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
// 按钮文本
|
||||||
Text(text = getString(R.string.give_permission))
|
Text(text = getString(R.string.give_permission))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Activity 恢复时调用的方法。
|
||||||
|
* 检查是否已经授予权限,如果是,则导航到主页面并初始化系统。
|
||||||
|
*/
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (checkPermissions(this)) {
|
if (checkPermissions(this)) {
|
||||||
val intent = Intent(this, main_page::class.java)
|
val intent = Intent(this, main_page::class.java)
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) // 清空栈顶
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
initSystem()
|
initSystem() // 初始化系统
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查应用是否已获得所需的存储权限。
|
||||||
|
* @param context 上下文,用于访问权限检查方法
|
||||||
|
* @return Boolean 如果权限已被授予,返回 true;否则返回 false
|
||||||
|
*/
|
||||||
fun checkPermissions(context: Context): Boolean {
|
fun checkPermissions(context: Context): Boolean {
|
||||||
// Check storage permission
|
// 对于 Android R 及以上版本,检查是否具有管理所有文件的权限
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.R) {
|
if (VERSION.SDK_INT >= VERSION_CODES.R) {
|
||||||
// Check manage storage on R+
|
|
||||||
if (!Environment.isExternalStorageManager()) {
|
if (!Environment.isExternalStorageManager()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val permissions = arrayOf(permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE)
|
// 对于较低版本,逐个检查读写权限
|
||||||
|
val permissions = arrayOf(
|
||||||
|
permission.READ_EXTERNAL_STORAGE,
|
||||||
|
permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
permissions.forEach {
|
permissions.forEach {
|
||||||
if (context.checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED) {
|
if (context.checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED) {
|
||||||
return false
|
return false
|
||||||
|
@ -123,9 +154,12 @@ fun checkPermissions(context: Context): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化系统的资源列表,如图像、视频、音乐和文档。
|
||||||
|
*/
|
||||||
fun initSystem() {
|
fun initSystem() {
|
||||||
ImageLister.instance.initialize()
|
ImageLister.instance.initialize() // 初始化图像列表
|
||||||
VideoLister.instance.initialize()
|
VideoLister.instance.initialize() // 初始化视频列表
|
||||||
MusicLister.instance.initialize()
|
MusicLister.instance.initialize() // 初始化音乐列表
|
||||||
DocumentLister.instance.initialize()
|
DocumentLister.instance.initialize() // 初始化文档列表
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,33 +4,12 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AlertDialog.Builder
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.layout.*
|
||||||
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.Column
|
|
||||||
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.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
@ -52,20 +31,32 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于在搜索结果中显示文件列表的类
|
||||||
|
* @param context 上下文对象
|
||||||
|
* @param searchTypeName 搜索类型名称,用于显示在UI中
|
||||||
|
* @param searchRegex 搜索的正则表达式
|
||||||
|
*/
|
||||||
class SearchFileColumn(
|
class SearchFileColumn(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
private val searchTypeName: String,
|
private val searchTypeName: String,
|
||||||
private val searchRegex: Regex?
|
private val searchRegex: Regex?
|
||||||
) {
|
) {
|
||||||
|
// 保存搜索到的文件列表
|
||||||
private val fileList = mutableStateListOf<WrappedFile>()
|
private val fileList = mutableStateListOf<WrappedFile>()
|
||||||
|
// 搜索输入的文本状态
|
||||||
private val searchText = mutableStateOf("")
|
private val searchText = mutableStateOf("")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable函数,用于绘制搜索文件的主界面
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun Draw() {
|
fun Draw() {
|
||||||
var list by remember { mutableStateOf<List<String>>(emptyList()) }
|
var list by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||||
var isOkay by remember { mutableStateOf(false) }
|
var isOkay by remember { mutableStateOf(false) }
|
||||||
var sortByTime by remember { mutableStateOf(true) }
|
var sortByTime by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
// 当搜索结果或排序方式改变时,重新加载并排序文件列表
|
||||||
LaunchedEffect(isOkay, list, sortByTime) {
|
LaunchedEffect(isOkay, list, sortByTime) {
|
||||||
isOkay = false
|
isOkay = false
|
||||||
fileList.clear()
|
fileList.clear()
|
||||||
|
@ -78,6 +69,7 @@ class SearchFileColumn(
|
||||||
isOkay = true
|
isOkay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 主界面布局,包含返回按钮、标题和排序按钮
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
@ -91,6 +83,7 @@ class SearchFileColumn(
|
||||||
.padding(horizontal = 10.dp),
|
.padding(horizontal = 10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
// 返回按钮,点击时返回主页面
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
val intent = Intent(
|
val intent = Intent(
|
||||||
|
@ -106,6 +99,7 @@ class SearchFileColumn(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示搜索标题或搜索结果
|
||||||
Text(
|
Text(
|
||||||
text = if (list.isEmpty()) {
|
text = if (list.isEmpty()) {
|
||||||
context.getString(R.string.search_here, searchTypeName)
|
context.getString(R.string.search_here, searchTypeName)
|
||||||
|
@ -122,6 +116,7 @@ class SearchFileColumn(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
// 排序按钮,切换按时间或大小排序
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
sortByTime = !sortByTime
|
sortByTime = !sortByTime
|
||||||
|
@ -148,20 +143,23 @@ class SearchFileColumn(
|
||||||
), "sortMethod"
|
), "sortMethod"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载或显示搜索结果
|
||||||
if (!isOkay) {
|
if (!isOkay) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
|
// 显示“加载中”的文本
|
||||||
Text(
|
Text(
|
||||||
text = context.getString(R.string.loading),
|
text = context.getString(R.string.loading),
|
||||||
fontSize = 34.sp
|
fontSize = 34.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 显示搜索结果的文件列表
|
||||||
DrawColumns(
|
DrawColumns(
|
||||||
fileList,
|
fileList,
|
||||||
searchText = searchText.value,
|
searchText = searchText.value,
|
||||||
|
@ -186,6 +184,7 @@ class SearchFileColumn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
// 文件点击事件,打开对应的文件
|
||||||
val file = File(it)
|
val file = File(it)
|
||||||
if (file.isFile) {
|
if (file.isFile) {
|
||||||
val uri = FileProvider.getUriForFile(
|
val uri = FileProvider.getUriForFile(
|
||||||
|
@ -203,6 +202,13 @@ class SearchFileColumn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable函数,用于绘制文件列表
|
||||||
|
* @param fileList 文件列表
|
||||||
|
* @param searchText 搜索输入文本
|
||||||
|
* @param onSearch 搜索按钮点击事件
|
||||||
|
* @param onItemClick 文件点击事件
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawColumns(
|
private fun DrawColumns(
|
||||||
fileList: List<WrappedFile>,
|
fileList: List<WrappedFile>,
|
||||||
|
@ -213,7 +219,7 @@ class SearchFileColumn(
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.padding(vertical = 5.dp)
|
modifier = Modifier.padding(vertical = 5.dp)
|
||||||
) {
|
) {
|
||||||
// 最顶上那个
|
// 搜索输入框
|
||||||
item {
|
item {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -223,6 +229,7 @@ class SearchFileColumn(
|
||||||
) {
|
) {
|
||||||
var searchInput by remember { mutableStateOf(searchText) }
|
var searchInput by remember { mutableStateOf(searchText) }
|
||||||
|
|
||||||
|
// 搜索输入框
|
||||||
TextField(
|
TextField(
|
||||||
value = searchInput,
|
value = searchInput,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
@ -238,6 +245,7 @@ class SearchFileColumn(
|
||||||
),
|
),
|
||||||
textStyle = TextStyle(fontSize = 18.sp),
|
textStyle = TextStyle(fontSize = 18.sp),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
|
// 点击图标进行搜索
|
||||||
Image(
|
Image(
|
||||||
ImageVector.vectorResource(R.drawable.ic_search),
|
ImageVector.vectorResource(R.drawable.ic_search),
|
||||||
context.getString(R.string.search),
|
context.getString(R.string.search),
|
||||||
|
@ -245,6 +253,7 @@ class SearchFileColumn(
|
||||||
.clickable {
|
.clickable {
|
||||||
if (searchInput.isNotEmpty()) {
|
if (searchInput.isNotEmpty()) {
|
||||||
if (searchInput == "." || searchInput == "..") {
|
if (searchInput == "." || searchInput == "..") {
|
||||||
|
// 输入非法时提示错误
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
context,
|
context,
|
||||||
|
@ -269,7 +278,7 @@ class SearchFileColumn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下面的内容
|
// 显示每个文件的视图
|
||||||
items(fileList) { file ->
|
items(fileList) { file ->
|
||||||
FileSingleView(
|
FileSingleView(
|
||||||
file,
|
file,
|
||||||
|
@ -279,12 +288,16 @@ class SearchFileColumn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable函数,用于绘制单个文件项
|
||||||
|
* @param file 文件对象
|
||||||
|
* @param onItemClick 文件点击事件
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun FileSingleView(
|
private fun FileSingleView(
|
||||||
file: WrappedFile,
|
file: WrappedFile,
|
||||||
onItemClick: ((String) -> Unit)? = null
|
onItemClick: ((String) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
@ -299,6 +312,7 @@ class SearchFileColumn(
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
// 显示文件图标
|
||||||
Image(
|
Image(
|
||||||
ImageVector.vectorResource(
|
ImageVector.vectorResource(
|
||||||
when (file.mime.split('/').first()) {
|
when (file.mime.split('/').first()) {
|
||||||
|
@ -317,12 +331,14 @@ class SearchFileColumn(
|
||||||
.padding(horizontal = 15.dp)
|
.padding(horizontal = 15.dp)
|
||||||
.fillMaxWidth(0.8f)
|
.fillMaxWidth(0.8f)
|
||||||
) {
|
) {
|
||||||
|
// 文件名称
|
||||||
Text(
|
Text(
|
||||||
text = file.name,
|
text = file.name,
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
// 文件最后修改时间
|
||||||
Text(
|
Text(
|
||||||
text = file.getModifiedTimeString(context),
|
text = file.getModifiedTimeString(context),
|
||||||
fontSize = 15.sp,
|
fontSize = 15.sp,
|
||||||
|
@ -333,6 +349,7 @@ class SearchFileColumn(
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
|
// 显示文件信息的按钮
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
showFileInfoAlert(context, file.path)
|
showFileInfoAlert(context, file.path)
|
||||||
|
@ -346,6 +363,11 @@ class SearchFileColumn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示文件详细信息的弹窗
|
||||||
|
* @param context 上下文对象
|
||||||
|
* @param file 文件路径
|
||||||
|
*/
|
||||||
fun showFileInfoAlert(context: Context, file: String) {
|
fun showFileInfoAlert(context: Context, file: String) {
|
||||||
val f = File(file)
|
val f = File(file)
|
||||||
if (!f.exists()) {
|
if (!f.exists()) {
|
||||||
|
@ -369,4 +391,4 @@ class SearchFileColumn(
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,30 @@ use jni::JNIEnv;
|
||||||
use std::{fs, i64};
|
use std::{fs, i64};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
/// 获取指定文件夹的总大小,并将其格式化为字符串后返回给 Java
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
/// - `env`: JNI 环境对象,用于与 Java 交互
|
||||||
|
/// - `_`: `JClass` 类型,表示调用此方法的 Java 类(未使用)
|
||||||
|
/// - `input`: Java 传递的字符串,代表文件夹路径
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
/// - `jstring`: 表示文件夹大小的格式化字符串,如果发生错误,返回错误信息
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSizeNative<'local>(
|
pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSizeNative<'local>(
|
||||||
mut env: JNIEnv<'local>,
|
mut env: JNIEnv<'local>,
|
||||||
_: JClass<'local>,
|
_: JClass<'local>,
|
||||||
input: JString<'local>,
|
input: JString<'local>,
|
||||||
) -> jstring {
|
) -> jstring {
|
||||||
|
// 将 Java 字符串转换为 Rust 字符串
|
||||||
let dir: String = env
|
let dir: String = env
|
||||||
.get_string(&input)
|
.get_string(&input)
|
||||||
.expect("failed to parse input")
|
.expect("failed to parse input")
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
// 检查目录是否存在
|
||||||
if !fs::exists(&dir).expect(format!("Cannot stat {dir}").as_str()) {
|
if !fs::exists(&dir).expect(format!("Cannot stat {dir}").as_str()) {
|
||||||
|
// 如果目录不存在,返回相应的错误信息
|
||||||
return env
|
return env
|
||||||
.new_string(format!("{} not exists!", dir))
|
.new_string(format!("{} not exists!", dir))
|
||||||
.expect("Couldn't create java string!")
|
.expect("Couldn't create java string!")
|
||||||
|
@ -24,25 +36,29 @@ pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSize
|
||||||
// 从这里保证文件至少存在了
|
// 从这里保证文件至少存在了
|
||||||
|
|
||||||
let mut size: u64 = 0;
|
let mut size: u64 = 0;
|
||||||
|
// 使用 WalkDir 遍历目录中的所有文件和子目录
|
||||||
for entry in WalkDir::new(dir) {
|
for entry in WalkDir::new(dir) {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
|
// 尝试获取每个文件的元数据并累加其大小
|
||||||
match item.metadata() {
|
match item.metadata() {
|
||||||
Ok(metadata) => size += metadata.len(),
|
Ok(metadata) => size += metadata.len(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// 处理元数据获取失败的情况
|
||||||
eprintln!("Error getting metadata for item: {e:?}");
|
eprintln!("Error getting metadata for item: {e:?}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// 处理遍历目录时的错误
|
||||||
eprintln!("Error walking directory: {:?}", e);
|
eprintln!("Error walking directory: {:?}", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 将计算出的文件夹大小格式化为字符串并返回给 Java
|
||||||
let output = env
|
let output = env
|
||||||
.new_string(format_size(size))
|
.new_string(format_size(size))
|
||||||
.expect("Couldn't create java string!");
|
.expect("Couldn't create java string!");
|
||||||
|
@ -50,48 +66,70 @@ pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSize
|
||||||
output.into_raw()
|
output.into_raw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取指定文件夹的总大小(以字节为单位),返回给 Java
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
/// - `env`: JNI 环境对象,用于与 Java 交互
|
||||||
|
/// - `_`: `JClass` 类型,表示调用此方法的 Java 类(未使用)
|
||||||
|
/// - `input`: Java 传递的字符串,代表文件夹路径
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
/// - `jlong`: 文件夹的总大小(以字节为单位),如果发生错误则返回 0
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSizeBytesNative<'local>(
|
pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSizeBytesNative<'local>(
|
||||||
mut env: JNIEnv<'local>,
|
mut env: JNIEnv<'local>,
|
||||||
_: JClass<'local>,
|
_: JClass<'local>,
|
||||||
input: JString<'local>,
|
input: JString<'local>,
|
||||||
) -> jlong {
|
) -> jlong {
|
||||||
|
// 将 Java 字符串转换为 Rust 字符串
|
||||||
let dir: String = env
|
let dir: String = env
|
||||||
.get_string(&input)
|
.get_string(&input)
|
||||||
.expect("failed to parse input")
|
.expect("failed to parse input")
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
// 检查目录是否存在
|
||||||
if !fs::exists(&dir).expect(format!("Cannot stat {}", dir).as_str()) {
|
if !fs::exists(&dir).expect(format!("Cannot stat {}", dir).as_str()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// 从这里保证文件至少存在了
|
// 从这里保证文件至少存在了
|
||||||
|
|
||||||
let mut size: u64 = 0;
|
let mut size: u64 = 0;
|
||||||
|
// 使用 WalkDir 遍历目录中的所有文件和子目录
|
||||||
for entry in WalkDir::new(dir) {
|
for entry in WalkDir::new(dir) {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
|
// 尝试获取每个文件的元数据并累加其大小
|
||||||
match item.metadata() {
|
match item.metadata() {
|
||||||
Ok(metadata) => size += metadata.len(),
|
Ok(metadata) => size += metadata.len(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// 处理元数据获取失败的情况
|
||||||
eprintln!("Error getting metadata for item: {:?}", e);
|
eprintln!("Error getting metadata for item: {:?}", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// 处理遍历目录时的错误
|
||||||
eprintln!("Error walking directory: {:?}", e);
|
eprintln!("Error walking directory: {:?}", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 将文件夹大小转换为 `jlong`,如果溢出则返回 `i64::MAX`
|
||||||
match i64::try_from(size) {
|
match i64::try_from(size) {
|
||||||
Ok(compatible) => compatible,
|
Ok(compatible) => compatible,
|
||||||
Err(_) => i64::MAX
|
Err(_) => i64::MAX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 格式化文件大小为更易读的字符串格式
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
/// - `size`: 文件大小,以字节为单位
|
||||||
|
///
|
||||||
|
/// # 返回
|
||||||
|
/// - `String`: 格式化后的文件大小字符串(带单位,如 "KB", "MB" 等)
|
||||||
fn format_size(size: u64) -> String {
|
fn format_size(size: u64) -> String {
|
||||||
// 定义单位
|
// 定义单位
|
||||||
let units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
let units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
||||||
|
@ -106,4 +144,4 @@ fn format_size(size: u64) -> String {
|
||||||
|
|
||||||
// 保留两位小数格式化输出
|
// 保留两位小数格式化输出
|
||||||
format!("{:.2} {}", size, units[unit_index])
|
format!("{:.2} {}", size, units[unit_index])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue