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,6 +2,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<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" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@ -12,6 +21,11 @@
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"
@ -30,15 +44,12 @@
<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" />
@ -63,14 +74,4 @@
</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" />
</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"
<Button
android:id="@+id/MainPageSearchButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="15dp"
android:background="@drawable/search_border"
android:iconifiedByDefault="false"
android:queryHint="搜索文件"
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

@ -128,4 +128,13 @@
<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

@ -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" }