add search and sort

This commit is contained in:
kagura 2024-10-10 21:12:46 +08:00
parent f133a4446b
commit 2190a509ef
21 changed files with 825 additions and 183 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
<bytecodeTargetLevel target="21" />
</component>
</project>

View file

@ -4,8 +4,9 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="gradleJvm" value="#JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -69,6 +69,7 @@ dependencies {
implementation(libs.ui.graphics)
implementation(libs.ui.tooling.preview)
implementation(libs.material3)
implementation(libs.androidx.datastore.preferences)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)

View file

@ -2,75 +2,76 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/ic_launcher_foreground"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_launcher_foreground"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<activity
android:name=".compose.SearchActivity"
android:exported="false"
android:label="@string/title_activity_search"
android:theme="@style/Theme.MyApplication" />
<activity
android:name=".compose.RequirePermissionActivity"
android:exported="true"
android:theme="@style/Theme.MyApplication">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".compose.ViewFileActivity"
android:exported="false" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/ic_launcher_foreground"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_launcher_foreground"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<activity
android:name=".compose.SettingActivity"
android:exported="false"
android:label="@string/title_activity_setting"
android:theme="@style/Theme.MyApplication" />
<activity
android:name=".compose.SearchActivity"
android:exported="false"
android:label="@string/title_activity_search"
android:theme="@style/Theme.MyApplication" />
<activity
android:name=".compose.RequirePermissionActivity"
android:exported="true"
android:theme="@style/Theme.MyApplication">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<activity
android:name=".main_page"
android:exported="false" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".compose.ViewFileActivity"
android:exported="false" />
<activity
android:name=".main_page"
android:exported="false" />
<activity
android:name=".store_page"
android:exported="false" />
<activity
android:name=".document_page"
android:exported="false" />
<activity
android:name=".picture_page"
android:exported="false" />
<activity
android:name=".video_page"
android:exported="false" />
<activity
android:name=".music_page"
android:exported="false" />
<activity
android:name=".store_page"
android:exported="false" />
<activity
android:name=".document_page"
android:exported="false" />
<activity
android:name=".picture_page"
android:exported="false" />
<activity
android:name=".video_page"
android:exported="false" />
<activity
android:name=".music_page"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapplication.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapplication.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

View file

@ -0,0 +1,37 @@
package com.example.myapplication
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
val Context.settingStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")
class SettingStorage(private val context: Context) {
val showExtension = booleanPreferencesKey("show_extension")
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
}
}
}

View file

@ -4,6 +4,13 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.example.myapplication.R
import com.example.myapplication.compose.ui.SearchFileColumn
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister
import com.example.myapplication.fileSystem.byTypeFileLister.ImageLister
import com.example.myapplication.fileSystem.byTypeFileLister.MusicLister
@ -20,10 +27,22 @@ class SearchActivity : ComponentActivity() {
"document" -> DocumentLister.regex
else -> null
}
val searchFileColumn = SearchFileColumn(this,when (type) {
"music" -> getString(R.string.music)
"image" -> getString(R.string.picture)
"video" -> getString(R.string.video)
"document" -> getString(R.string.document)
else -> ""
},searchTypeRegex)
enableEdgeToEdge()
setContent {
Surface(
modifier = Modifier
.fillMaxSize()
.background(Color(getColor(R.color.WhiteSmoke)))
)
{ searchFileColumn.Draw() }
}
}
}
}

View file

@ -0,0 +1,17 @@
package com.example.myapplication.compose
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
class SettingActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
}
}
}

View file

@ -63,6 +63,9 @@ import com.example.myapplication.fileSystem.WrappedFile.Type
import com.example.myapplication.main_page
import com.example.myapplication.utils.AlertHelper
import com.example.myapplication.utils.ClipHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.apache.commons.io.IOUtils
import java.io.File
import java.io.FileNotFoundException
@ -74,17 +77,26 @@ class FileColumn(val context: Context) {
fun Draw(startFolder: String) {
var path by remember { mutableStateOf(startFolder) }
var shouldUpdate by remember { mutableStateOf(false) }
// 检查当前在的目录,有问题就不渲染
val cwd = File(path)
if (!cwd.isDirectory) {
return
}
var isOkay by remember { mutableStateOf(false) }
LaunchedEffect(path, shouldUpdate) {
var isOkay by remember { mutableStateOf(false) }
var sortByTime by remember { mutableStateOf(true) }
LaunchedEffect(path, shouldUpdate, sortByTime) {
isOkay = false
fileList.clear()
cwd.listFiles()?.forEach { f ->
fileList.add(WrappedFile(f))
val wfList = cwd.listFiles()?.map { WrappedFile(it) }
if (wfList!=null) {
if (sortByTime) {
fileList.addAll(wfList.sortedBy { it.lastModifiedTime })
} else {
fileList.addAll(wfList.sortedBy { it.size })
}
}
isOkay = true
}
@ -120,11 +132,42 @@ class FileColumn(val context: Context) {
Text(
text = path,
fontSize = 24.sp,
modifier = Modifier.padding(start = 16.dp),
modifier = Modifier.padding(start = 10.dp)
.fillMaxWidth(0.75f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.weight(1f))
IconButton(
onClick = {
sortByTime = !sortByTime
CoroutineScope(Dispatchers.Main).launch {
Toast.makeText(
context, context.getString(
if (sortByTime) {
R.string.sort_by_time
} else {
R.string.sort_by_size
}
), Toast.LENGTH_SHORT
).show()
}
},
) {
Image(
imageVector = ImageVector.vectorResource(
if (sortByTime) {
R.drawable.baseline_access_time_24
} else {
R.drawable.baseline_storage_24
}
), "sortMethod"
)
}
}
if (!isOkay) {
Column(

View file

@ -0,0 +1,358 @@
package com.example.myapplication.compose.ui
import android.content.Context
import android.content.Intent
import android.widget.Toast
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.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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.FileProvider
import com.example.myapplication.R
import com.example.myapplication.fileSystem.CutHelper
import com.example.myapplication.fileSystem.WrappedFile
import com.example.myapplication.fileSystem.WrappedFile.Type
import com.example.myapplication.fileSystem.searchFile
import com.example.myapplication.main_page
import com.example.myapplication.utils.AlertHelper
import com.example.myapplication.utils.ClipHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class SearchFileColumn(
val context: Context,
private val searchTypeName: String,
private val searchRegex: Regex?
) {
private val fileList = mutableStateListOf<WrappedFile>()
private val searchText = mutableStateOf("")
@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()
val wfList = list.map { WrappedFile(File(it)) }
if (sortByTime) {
fileList.addAll(wfList.sortedBy { it.lastModifiedTime })
} else {
fileList.addAll(wfList.sortedBy { it.size })
}
isOkay = true
}
Column(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.navigationBarsPadding()
.background(Color(context.getColor(R.color.WhiteSmoke)))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = {
val intent = Intent(
context,
main_page::class.java
)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
context.startActivity(intent)
}
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_left_arrow), "back"
)
}
Text(
text = context.getString(R.string.search_result, searchTypeName),
fontSize = 28.sp,
modifier = Modifier
.padding(start = 10.dp)
.padding(vertical = 5.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.weight(1f))
IconButton(
onClick = {
sortByTime = !sortByTime
CoroutineScope(Dispatchers.Main).launch {
Toast.makeText(
context, context.getString(
if (sortByTime) {
R.string.sort_by_time
} else {
R.string.sort_by_size
}
), Toast.LENGTH_SHORT
).show()
}
},
) {
Image(
imageVector = ImageVector.vectorResource(
if (sortByTime) {
R.drawable.baseline_access_time_24
} else {
R.drawable.baseline_storage_24
}
), "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,
onSearch = { name, searchWhat ->
searchText.value = searchWhat
searchFile(name, searchRegex) {
list = it
CoroutineScope(Dispatchers.Main).launch {
if (list.isEmpty()) {
Toast.makeText(
context,
context.getString(R.string.search_no_result), Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
context.getString(R.string.search_some_result, list.size), Toast.LENGTH_SHORT
).show()
}
isOkay = !isOkay
}
}
}
) {
val file = File(it)
if (file.isFile) {
val uri = FileProvider.getUriForFile(
context,
context.packageName + ".provider",
file
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, WrappedFile(file).mime)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
}
}
}
}
}
@Composable
private fun DrawColumns(
fileList: List<WrappedFile>,
update: (() -> Unit)? = null,
searchText: String = "",
onSearch: ((String, String) -> Unit)? = null,
onItemClick: ((String) -> Unit)? = null
) {
LazyColumn(
modifier = Modifier.padding(vertical = 5.dp)
) {
// 最顶上那个
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 3.dp),
verticalAlignment = Alignment.CenterVertically,
) {
var searchInput by remember { mutableStateOf(searchText) }
TextField(
value = searchInput,
maxLines = 1,
onValueChange = { searchInput = it },
modifier = Modifier
.padding(horizontal = 15.dp, vertical = 10.dp)
.fillParentMaxWidth(0.9f),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color(0xFFFFFAFA),
unfocusedContainerColor = Color.White,
focusedIndicatorColor = Color(0xFF03A9F4),
unfocusedIndicatorColor = Color.Transparent
),
textStyle = TextStyle(fontSize = 18.sp),
trailingIcon = {
Image(
ImageVector.vectorResource(R.drawable.ic_search),
context.getString(R.string.search),
modifier = Modifier
.clickable {
if (searchInput.isNotEmpty()) {
if (searchInput == "." || searchInput == "..") {
Toast
.makeText(
context,
context.getString(R.string.error_search_input_illegal),
Toast.LENGTH_SHORT
)
.show()
}
onSearch?.invoke(searchInput, searchInput)
} else {
Toast
.makeText(
context,
context.getString(R.string.error_need_search_input), Toast.LENGTH_SHORT
)
.show()
}
}
)
}
)
}
}
// 下面的内容
items(fileList) { file ->
FileSingleView(
file,
update = update,
onItemClick = onItemClick
)
}
}
}
@Composable
private fun FileSingleView(
file: WrappedFile,
update: (() -> Unit)? = null,
onItemClick: ((String) -> Unit)? = null
) {
Row(
modifier = Modifier
.fillMaxWidth()
.border(
width = Dp.Hairline,
color = Color.Gray,
shape = RectangleShape
)
.padding(vertical = 3.dp)
.clickable {
onItemClick?.invoke(file.path)
},
verticalAlignment = Alignment.CenterVertically,
) {
Image(
ImageVector.vectorResource(
when (file.mime.split('/').first()) {
"dir" -> R.drawable.type_directory
"image" -> R.drawable.type_image
"video" -> R.drawable.type_video
"audio" -> R.drawable.type_audio
else -> R.drawable.type_file
}
), file.mime,
modifier = Modifier
.padding(horizontal = 8.dp)
)
Column(
modifier = Modifier
.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,
color = Color.Gray,
maxLines = 1
)
}
Spacer(Modifier.weight(1f))
IconButton(
onClick = {
if (file.type == Type.FILE) { // 普通文件
AlertHelper.showOnlyInfoNewAlert(context,
onInfo = {
AlertHelper.showFileInfoAlert(context, file.path)
}
)
}
},
modifier = Modifier.padding(horizontal = 10.dp)
) {
Image(
ImageVector.vectorResource(R.drawable.outline_info_24), "info"
)
}
}
}
}

View file

@ -0,0 +1,68 @@
package com.example.myapplication.compose.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
class Setting(
private val rowModifier: Modifier = Modifier.fillMaxWidth(),
private val nameModifier: Modifier = Modifier,
private val nameTextStyle: TextStyle = TextStyle.Default,
private val descriptionModifier: Modifier = Modifier,
private val descriptionTextStyle: TextStyle = TextStyle.Default,
private val switchModifier: Modifier = Modifier
) {
@Composable
fun BooleanSetting(
name: String,
initialState: Boolean = true,
description: String? = null,
onCheckedChange: ((Boolean) -> Unit)? = null
) {
var checkState by remember { mutableStateOf(initialState) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = rowModifier
) {
Column(
modifier = Modifier.fillMaxWidth(0.8f)
) {
Text(
text = name,
modifier = nameModifier,
style = nameTextStyle
)
Spacer(modifier = Modifier.size(5.dp))
Text(
text = description ?: "",
modifier = descriptionModifier,
style = descriptionTextStyle
)
}
Spacer(modifier = Modifier.weight(1f))
Switch(
checked = checkState,
onCheckedChange = {
checkState = it
onCheckedChange?.invoke(it)
},
modifier = switchModifier
)
}
}
}

View file

@ -222,7 +222,7 @@ class document_page : AppCompatActivity() {
loadingTextView.visibility = View.GONE
Toast.makeText(
this@document_page,
"已选择按时间排序",
getString(R.string.sort_by_time),
Toast.LENGTH_SHORT
).show()
}
@ -245,7 +245,7 @@ class document_page : AppCompatActivity() {
loadingTextView.visibility = View.GONE
Toast.makeText(
this@document_page,
"已选择按大小排序",
getString(R.string.sort_by_size),
Toast.LENGTH_SHORT
).show()
}

View file

@ -0,0 +1,45 @@
package com.example.myapplication.fileSystem
import android.content.Context
import android.os.Environment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import java.io.File
fun searchFile(name: String,typeRegex: Regex? = null, onFinished: (List<String>) -> Unit){
CoroutineScope(Dispatchers.IO).launch {
val resultList = searchDir(name,Environment.getExternalStorageDirectory().path)
if (typeRegex == null){
onFinished.invoke(resultList)
}else{
val finalResult = resultList.filter { it.contains(typeRegex) }
onFinished.invoke(finalResult)
}
}
}
suspend fun searchDir(name: String, directory: String): List<String> {
val list = mutableListOf<String>()
val dir = File(directory)
if (!dir.isDirectory || dir.name.startsWith('.')){
return emptyList()
}
val inside = dir.listFiles()
if (inside == null || inside.isEmpty()){
return emptyList()
}
inside.forEach {
if (it.isDirectory){
list.addAll(coroutineScope{ searchDir(name, it.path) })
}else if (it.isFile){
if (it.name.contains(name) && !it.name.startsWith('.') ){
list.add(it.path)
}
}
}
return list
}

View file

@ -10,6 +10,8 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.myapplication.compose.SearchActivity;
import com.example.myapplication.compose.ViewFileActivity;
import com.example.myapplication.fileSystem.DeleteHelper;
import java.io.File;
@ -41,6 +43,7 @@ public class main_page extends AppCompatActivity {
findViewById(R.id.MainPageRecordingButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageDCIMButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPagePicturesButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageSearchButton).setOnClickListener(buttonClickerHandler);
}
@Override protected void onDestroy() {
@ -124,6 +127,10 @@ public class main_page extends AppCompatActivity {
intent.putExtras(bundle);
startActivity(intent);
}
if (view.getId() == R.id.MainPageSearchButton) {
Intent intent = new Intent(context, SearchActivity.class);
startActivity(intent);
}
}
}
}

View file

@ -125,6 +125,26 @@ class AlertHelper {
.show()
}
fun showOnlyInfoNewAlert(
context: Context,
onInfo: () -> Unit,
) {
val builder = Builder(context)
builder.setTitle(context.getString(R.string.select_action))
.setItems(
arrayOf<CharSequence>(
context.getString(R.string.action_info)
)
) { _: DialogInterface?, which: Int ->
when (which) {
1 -> onInfo()
}
}
.setNegativeButton(context.getString(R.string.action_cancel))
{ dialog: DialogInterface, which: Int -> dialog.dismiss() }
.show()
}
fun showDeleteAlert(context: Context, file: String, onConfirm: (() -> Unit)?) {
val builder = Builder(context)
builder.setTitle(context.getString(R.string.confirm_to_delete))

View file

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#1A1B3F" android:viewportHeight="24" android:viewportWidth="24" android:width="40dp">
<path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
<path android:fillColor="@android:color/white" android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

View file

@ -1,7 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#000000" android:viewportHeight="23" android:viewportWidth="23" android:width="40dp">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:tint="#000000"
android:viewportWidth="23"
android:viewportHeight="23">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>

View file

@ -36,7 +36,7 @@
android:scrollbars="none"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/searchDocument"
app:layout_constraintTop_toBottomOf="@+id/MainPageSearchButton"
>
<LinearLayout
@ -329,19 +329,21 @@
</ScrollView>
<SearchView
android:id="@+id/searchDocument"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="15dp"
android:background="@drawable/search_border"
android:iconifiedByDefault="false"
android:queryHint="搜索文件"
android:textColor="@color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/MainPageTitleRow"
/>
<Button
android:id="@+id/MainPageSearchButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/ic_search"
android:gravity="center"
android:layout_marginHorizontal="60dp"
android:text="@string/search_file"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/MainPageTitleRow"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,42 +1,42 @@
<resources>
<string name="app_name">文件管理器</string>
<string name="hello">文件管理器</string>
<string name="search">浏览</string>
<string name="edit">编辑</string>
<string name="picture">图片</string>
<string name="video">视频</string>
<string name="music">音乐</string>
<string name="document">文件</string>
<string name="app">其他</string>
<string name="location">位置</string>
<string name="store">内部存储</string>
<string name="delete">最近删除</string>
<string name="download">下载与接收</string>
<string name="source">来源</string>
<string name="qq">QQ</string>
<string name="wechat">微信</string>
<string name="internet">浏览器</string>
<string name="radio">录音机</string>
<string name="installpackage">安装包</string>
<string name="Camera">Camera</string>
<string name="Screenshots">屏幕截图</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="Delete">删除</string>
<string name="copy">复制</string>
<string name="cut">剪切</string>
<string name="paste">粘贴</string>
<string name="tiktok">抖音</string>
<string name="alipay">支付宝</string>
<string name="taobao">淘宝</string>
<string name="little_red_book">小红书</string>
<string name="unload">卸载</string>
<string name="store_package">压缩包</string>
<string name="phone_information">通话与信息</string>
<string name="lorem_ipsum">
<string name="app_name">文件管理器</string>
<string name="hello">文件管理器</string>
<string name="search">浏览</string>
<string name="edit">编辑</string>
<string name="picture">图片</string>
<string name="video">视频</string>
<string name="music">音乐</string>
<string name="document">文件</string>
<string name="app">其他</string>
<string name="location">位置</string>
<string name="store">内部存储</string>
<string name="delete">最近删除</string>
<string name="download">下载与接收</string>
<string name="source">来源</string>
<string name="qq">QQ</string>
<string name="wechat">微信</string>
<string name="internet">浏览器</string>
<string name="radio">录音机</string>
<string name="installpackage">安装包</string>
<string name="Camera">Camera</string>
<string name="Screenshots">屏幕截图</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="Delete">删除</string>
<string name="copy">复制</string>
<string name="cut">剪切</string>
<string name="paste">粘贴</string>
<string name="tiktok">抖音</string>
<string name="alipay">支付宝</string>
<string name="taobao">淘宝</string>
<string name="little_red_book">小红书</string>
<string name="unload">卸载</string>
<string name="store_package">压缩包</string>
<string name="phone_information">通话与信息</string>
<string name="lorem_ipsum">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
@ -72,60 +72,69 @@
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
</string>
<string name="title_activity_require_permission">RequirePermissionActivity</string>
<string name="title_activity_require_permission">RequirePermissionActivity</string>
<string name="require_permission_readwrite">需要读取/写入存储权限以继续</string>
<string name="require_manage_storage">需要管理存储权限以继续</string>
<string name="give_permission">授权</string>
<string name="require_permission_readwrite">需要读取/写入存储权限以继续</string>
<string name="require_manage_storage">需要管理存储权限以继续</string>
<string name="give_permission">授权</string>
<string name="used_storage_percentage">%d%%</string>
<string name="used_storage">已使用\n%s / %s</string>
<string name="used_storage_percentage">%d%%</string>
<string name="used_storage">已使用\n%s / %s</string>
<string name="loading">加载中</string>
<string name="loading">加载中</string>
<!-- Actions -->
<string name="select_action">选择操作</string>
<string name="action_copy">复制</string>
<string name="action_paste">粘贴</string>
<string name="action_delete">删除</string>
<string name="action_cut">剪切</string>
<string name="action_info">信息</string>
<string name="action_new_file">新建文件</string>
<string name="action_new_folder">新建文件夹</string>
<string name="action_cancel">取消</string>
<!-- Actions -->
<string name="select_action">选择操作</string>
<string name="action_copy">复制</string>
<string name="action_paste">粘贴</string>
<string name="action_delete">删除</string>
<string name="action_cut">剪切</string>
<string name="action_info">信息</string>
<string name="action_new_file">新建文件</string>
<string name="action_new_folder">新建文件夹</string>
<string name="action_cancel">取消</string>
<!-- Confirmations -->
<string name="confirm">确认</string>
<string name="okay">好的</string>
<string name="cancel">取消</string>
<string name="confirm_to_delete">确认删除</string>
<string name="confirm_to_delete_file">确认删除文件: %s</string>
<!-- Confirmations -->
<string name="confirm">确认</string>
<string name="okay">好的</string>
<string name="cancel">取消</string>
<string name="confirm_to_delete">确认删除</string>
<string name="confirm_to_delete_file">确认删除文件: %s</string>
<string name="file_info">文件信息</string>
<string name="file_info_text">
<string name="file_info">文件信息</string>
<string name="file_info_text">
文件名:%s\n
路径:%s\n
大小:%s\n
最后修改时间:%s
</string>
<string name="advanced">查看什么</string>
<string name="settings">软件设置</string>
<string name="all_files">全部文件</string>
<string name="storage_info">内部存储信息</string>
<string name="advanced">查看什么</string>
<string name="settings">软件设置</string>
<string name="all_files">全部文件</string>
<string name="storage_info">内部存储信息</string>
<string name="camera">相机</string>
<string name="pictures">公用图片</string>
<string name="documents">公用文档</string>
<string name="prev_folder">上一级目录</string>
<string name="camera">相机</string>
<string name="pictures">公用图片</string>
<string name="documents">公用文档</string>
<string name="prev_folder">上一级目录</string>
<string name="input_name">请输入名称</string>
<string name="create_directory">将在 %s 创建文件夹</string>
<string name="create_file">将在 %s 创建文件</string>
<string name="input_name">请输入名称</string>
<string name="create_directory">将在 %s 创建文件夹</string>
<string name="create_file">将在 %s 创建文件</string>
<string name="error_nothing_to_paste">未找到可粘贴的内容</string>
<string name="error_need_input_name">请输入名称</string>
<string name="error_already_exist">已存在此文件(夹)</string>
<string name="title_activity_view_file">ViewFileActivity</string>
<string name="title_activity_search">SearchActivity</string>
<string name="error_nothing_to_paste">未找到可粘贴的内容</string>
<string name="error_need_input_name">请输入名称</string>
<string name="error_already_exist">已存在此文件(夹)</string>
<string name="title_activity_view_file">ViewFileActivity</string>
<string name="title_activity_search">SearchActivity</string>
<string name="search_result">搜索%s结果</string>
<string name="error_need_search_input">请输入要搜索的内容</string>
<string name="error_search_input_illegal">输入的搜索内容不合法</string>
<string name="search_file">搜索文件</string>
<string name="search_no_result">未找到任何结果</string>
<string name="search_some_result">找到%d个结果</string>
<string name="sort_by_size">已选择按大小排序</string>
<string name="sort_by_time">已选择按时间排序</string>
<string name="title_activity_setting">SettingActivity</string>
</resources>

View file

@ -1,9 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.MyApplication" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<!-- Base application theme. -->
<style name="Base.Theme.MyApplication" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.MyApplication" parent="Base.Theme.MyApplication" />
<style name="Theme.MyApplication" parent="Base.Theme.MyApplication" />
</resources>

View file

@ -15,6 +15,7 @@ lifecycleRuntimeKtx = "2.8.6"
activityCompose = "1.9.2"
composeBom = "2024.09.03"
commonsIO = "2.17.0"
datastorePreferences = "1.1.1"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@ -38,6 +39,7 @@ ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
material3 = { group = "androidx.compose.material3", name = "material3" }
commons-io = { group = "commons-io", name = "commons-io", version.ref = "commonsIO" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }