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