This commit is contained in:
Kagura 2024-10-10 12:46:38 +08:00
commit 3242c9f4a0
139 changed files with 5879 additions and 0 deletions

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name Normal file
View file

@ -0,0 +1 @@
My Application

6
.idea/compiler.xml Normal file
View file

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

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-10-09T05:47:14.530453572Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/kagura/.android/avd/Pixel_6_API_33.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

19
.idea/gradle.xml Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View file

@ -0,0 +1,53 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.20" />
</component>
</project>

10
.idea/migrations.xml Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
.idea/misc.xml Normal file
View file

@ -0,0 +1,10 @@
<?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">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

1
app/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

82
app/build.gradle.kts Normal file
View file

@ -0,0 +1,82 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.compose.compiler)
}
android {
namespace = "com.example.myapplication"
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapplication"
minSdk = 29
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
implementation(libs.core.ktx)
implementation(libs.navigation.fragment)
implementation(libs.navigation.ui)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.activity.compose)
implementation(platform(libs.compose.bom))
implementation(libs.ui)
implementation(libs.ui.graphics)
implementation(libs.ui.tooling.preview)
implementation(libs.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
androidTestImplementation(platform(libs.compose.bom))
androidTestImplementation(libs.ui.test.junit4)
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
implementation(libs.commons.io)
}

21
app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,26 @@
package com.example.myapplication;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.myapplication", appContext.getPackageName());
}
}

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<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"/>
<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.RequirePermissionActivity"
android:exported="true"
android:theme="@style/Theme.MyApplication">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".compose.ViewFileActivity"
android:exported="false" />
<activity
android:name=".store_page"
android:exported="false" />
<activity
android:name=".document_page_search"
android:exported="false" />
<activity
android:name=".document_page"
android:exported="false" />
<activity
android:name=".music_page_search"
android:exported="false" />
<activity
android:name=".music_page"
android:exported="false" />
<activity
android:name=".video_page_search"
android:exported="false" />
<activity
android:name=".picture_page_search"
android:exported="false" />
<activity
android:name=".main_page"
android:exported="false" />
<activity
android:name=".picture_page"
android:exported="false" />
<activity
android:name=".video_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>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,33 @@
package com.example.myapplication.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.example.myapplication.R
import java.io.File
class DocumentModel(document: File) {
val name: String = document.name
init {
if (!document.isFile) {
throw RuntimeException("No such document")
}
}
}
class DocumentAdapter(context: Context, list: ArrayList<DocumentModel>) :
ArrayAdapter<DocumentModel>(context, 0, list) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val listView = convertView ?: LayoutInflater.from(context).inflate(
R.layout.document_card_item, parent, false
)
val model = getItem(position) ?: throw RuntimeException()
val card = listView.findViewById<TextView>(R.id.iconButton)
card.text = model.name
return listView
}
}

View file

@ -0,0 +1,43 @@
package com.example.myapplication.adapters
import android.content.Context
import android.graphics.Bitmap
import android.media.ThumbnailUtils
import android.util.Size
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView.LayoutParams
import android.widget.ArrayAdapter
import android.widget.GridView
import android.widget.ImageView
import android.widget.TextView
import com.example.myapplication.R
import java.io.File
class ImageModel(image: File) {
val name: String = image.name
var thumbnail: Bitmap
init {
if (!image.isFile) {
throw RuntimeException("No such Image")
}
thumbnail = ThumbnailUtils.createImageThumbnail(image, Size(512, 512), null)
}
}
class ImageAdapter(context: Context, list: ArrayList<ImageModel>) :
ArrayAdapter<ImageModel>(context, 0, list) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val listView = convertView ?: LayoutInflater.from(context).inflate(
R.layout.picture_card_item, parent, false
)
val model = getItem(position) ?: throw RuntimeException()
listView.findViewById<ImageView>(R.id.pictureCardImage).setImageBitmap(model.thumbnail)
listView.findViewById<TextView>(R.id.pictureCardText).text = model.name
listView.setLayoutParams(LayoutParams(GridView.AUTO_FIT, 530))
return listView
}
}

View file

@ -0,0 +1,33 @@
package com.example.myapplication.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.example.myapplication.R
import java.io.File
class MusicModel(music: File) {
val name: String = music.nameWithoutExtension // 歌曲就不放扩展名了
init {
if (!music.isFile) {
throw RuntimeException("No such Video")
}
}
}
class MusicAdapter(context: Context, list: ArrayList<MusicModel>) :
ArrayAdapter<MusicModel>(context, 0, list) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val listView = convertView ?: LayoutInflater.from(context).inflate(
R.layout.music_card_item, parent, false
)
val model = getItem(position) ?: throw RuntimeException()
val card = listView.findViewById<TextView>(R.id.iconButton)
card.text = model.name
return listView
}
}

View file

@ -0,0 +1,46 @@
package com.example.myapplication.adapters
import android.content.Context
import android.graphics.Bitmap
import android.media.ThumbnailUtils
import android.util.Size
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView.LayoutParams
import android.widget.ArrayAdapter
import android.widget.GridView
import android.widget.ImageView
import android.widget.TextView
import com.example.myapplication.R
import java.io.File
class VideoModel(video: File) {
val name: String = video.name
var thumbnail: Bitmap
init {
if (!video.isFile) {
throw RuntimeException("No such Video")
}
thumbnail = ThumbnailUtils.createVideoThumbnail(
video, Size(854, 480) // 考虑到视频多16:9
, null
)
}
}
class VideoAdapter(context: Context, list: ArrayList<VideoModel>) :
ArrayAdapter<VideoModel>(context, 0, list) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val listView = convertView ?: LayoutInflater.from(context).inflate(
R.layout.picture_card_item, parent, false
)
val model = getItem(position) ?: throw RuntimeException()
listView.findViewById<ImageView>(R.id.pictureCardImage).setImageBitmap(model.thumbnail)
listView.findViewById<TextView>(R.id.pictureCardText).text = model.name
listView.setLayoutParams(LayoutParams(GridView.AUTO_FIT, 530))
return listView
}
}

View file

@ -0,0 +1,60 @@
package com.example.myapplication.compose
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.channels.FileChannel
class PasteHelper {
companion object{
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)
}
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)
copyDirectory(anItem, newDir)
} else {
// copy the file
val destFile = File(destDir, anItem.name)
copySingleFile(anItem, destFile)
}
}
}
}
private fun copySingleFile(sourceFile: File, destFile: File){
if (!destFile.exists()) {
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()
}
}
}
}

View file

@ -0,0 +1,145 @@
package com.example.myapplication.compose
import android.Manifest.permission
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.app.ActivityCompat
import com.example.myapplication.BuildConfig
import com.example.myapplication.R
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister
import com.example.myapplication.fileSystem.byTypeFileLister.ImageLister
import com.example.myapplication.fileSystem.byTypeFileLister.MusicLister
import com.example.myapplication.fileSystem.byTypeFileLister.VideoLister
import com.example.myapplication.main_page
class RequirePermissionActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
val activity = this
window.statusBarColor = getColor(R.color.WhiteSmoke)
setContent {
Column(
modifier = Modifier.statusBarsPadding()
.fillMaxHeight(0.9f)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = getString(
if (VERSION.SDK_INT >= VERSION_CODES.R) {
R.string.require_manage_storage
} else {
R.string.require_permission_readwrite
}
),
modifier = Modifier.padding(vertical = 10.dp),
fontSize = 30.sp
)
Button(
onClick = { // Ask for permission
if (VERSION.SDK_INT >= VERSION_CODES.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)
startActivity(intent)
}
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
val perm33 = arrayOf(permission.READ_MEDIA_AUDIO,permission.READ_MEDIA_VIDEO,permission.READ_MEDIA_IMAGES)
ActivityCompat.requestPermissions(
activity, perm33, 101
)
}
} else { // for legacy system
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
)
) {
Text(text = getString(R.string.give_permission))
}
}
}
}
override fun onResume() {
super.onResume()
if (checkPermissions(this)) {
val intent = Intent(this, main_page::class.java)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
initSystem()
finish()
}
}
}
fun checkPermissions(context: Context): Boolean {
// Check storage permission
if (VERSION.SDK_INT >= VERSION_CODES.R) {
// Check manage storage on R+
if (!Environment.isExternalStorageManager()) {
return false
}
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
val perm33 = arrayOf(permission.READ_MEDIA_AUDIO,permission.READ_MEDIA_VIDEO,permission.READ_MEDIA_IMAGES)
perm33.forEach {
if (context.checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED) {
return false
}
}
}
} else {
val permissions = arrayOf(permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE)
permissions.forEach {
if (context.checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED) {
return false
}
}
}
return true
}
fun initSystem(){
ImageLister.instance.initialize()
VideoLister.instance.initialize()
MusicLister.instance.initialize()
DocumentLister.instance.initialize()
}

View file

@ -0,0 +1,39 @@
package com.example.myapplication.compose
import android.os.Bundle
import android.os.Environment
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.FileColumn
import java.io.File
class ViewFileActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var path =
intent.extras?.getString("folder") ?: Environment.getExternalStorageDirectory().path
val file = File(path)
if (!file.isDirectory){
path = Environment.getExternalStorageDirectory().path
}
enableEdgeToEdge()
window.statusBarColor = getColor(R.color.WhiteSmoke)
setContent {
Surface(
modifier = Modifier
.fillMaxSize()
.background(Color(getColor(R.color.WhiteSmoke)))
){
FileColumn(this).Draw(path)
}
}
}
}

View file

@ -0,0 +1,693 @@
package com.example.myapplication.compose.ui
import android.content.ClipData
import android.content.ClipDescription
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.draganddrop.dragAndDropSource
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.detectTapGestures
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.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draganddrop.DragAndDropEvent
import androidx.compose.ui.draganddrop.DragAndDropTarget
import androidx.compose.ui.draganddrop.DragAndDropTransferData
import androidx.compose.ui.draganddrop.mimeTypes
import androidx.compose.ui.draganddrop.toAndroidDragEvent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
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.compose.PasteHelper
import com.example.myapplication.fileSystem.CutHelper
import com.example.myapplication.fileSystem.WrappedFile
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 org.apache.commons.io.IOUtils
import java.io.File
import java.io.FileNotFoundException
class FileColumn(val context: Context) {
private val fileList = mutableStateListOf<WrappedFile>()
@Composable
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) {
isOkay = false
fileList.clear()
cwd.listFiles()?.forEach { f ->
fileList.add(WrappedFile(f))
}
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 = path,
fontSize = 24.sp,
modifier = Modifier.padding(start = 16.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
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, path, update = { shouldUpdate = !shouldUpdate }) {
if (it == "/storage/emulated") {
return@DrawColumns
}
val file = File(it)
if (file.isDirectory) {
path = it
} else 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)
}
}
}
}
}
private companion object{
var dropTarget: String? = null
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun DrawColumns(
fileList: List<WrappedFile>,
cwd: String,
parent: String? = null,
update: (() -> Unit)? = null,
onItemClick: ((String) -> Unit)? = null
) {
val dragAndDropCallBack = remember {
object : DragAndDropTarget {
override fun onDrop(event: DragAndDropEvent): Boolean {
val target = File(dropTarget ?: return false)
if (!target.exists()){
return false.also {
dropTarget = null
}
}
val data = event.toAndroidDragEvent()
.clipData.getItemAt(0).text
if (!data.startsWith("Drag:")) {
return false.also {
dropTarget = null
}
}
val source = File(data.split(':').last())
if (!source.exists()) {
return false.also {
dropTarget = null
}
}
if (source.path == target.path){
dropTarget = null
return false
}
if (target.isFile) {
if (source.isFile) {
val dir = source.parent ?: return false
val f = File("$dir/合并文件夹")
if (!f.exists()){
f.mkdir()
}
ClipHelper.getInstance(context).copy(source,context)
val sourceUri = ClipHelper.getInstance(context).paste() ?: return false
val inputStream = try {
context.contentResolver.openInputStream(sourceUri)
}catch (e: FileNotFoundException) {
return false.also {
dropTarget = null
}
}
if (inputStream != null) {
val actualFile = File(f, source.name)
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
}
ClipHelper.getInstance(context).copy(target,context)
val targetUri = ClipHelper.getInstance(context).paste() ?: return false
val inputStream2 = context.contentResolver.openInputStream(targetUri)
if (inputStream2 != null) {
val actualFile = File(f, target.name)
actualFile.writeBytes(IOUtils.toByteArray(inputStream2))
inputStream2.close()
}
Toast.makeText(
context,
"已放入 $dir/合并文件夹",
Toast.LENGTH_SHORT
).show()
} else if (source.isDirectory) {
ClipHelper.getInstance(context).copy(target,context)
val sourceUri = ClipHelper.getInstance(context).paste() ?: return false.also {
dropTarget = null
}
val inputStream = try {
context.contentResolver.openInputStream(sourceUri)
}catch (e: FileNotFoundException) {
return false.also {
dropTarget = null
}
}
if (inputStream != null) {
val actualFile = File(source, target.name)
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
}
Toast.makeText(
context,
"已放入 ${source.path}",
Toast.LENGTH_SHORT
).show()
}
} else if (target.isDirectory) {
if (source.isFile) {
ClipHelper.getInstance(context).copy(source,context)
val sourceUri = ClipHelper.getInstance(context).paste() ?: return false.also {
dropTarget = null
}
val inputStream = try {
context.contentResolver.openInputStream(sourceUri)
}catch (e: FileNotFoundException) {
return false.also {
dropTarget = null
}
}
if (inputStream != null) {
val actualFile = File(target, source.name)
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
}
Toast.makeText(
context,
"已放入 ${target.path}",
Toast.LENGTH_SHORT
).show()
}
}
update?.invoke()
dropTarget = null
return true
}
}
}
LazyColumn(
modifier = Modifier.padding(vertical = 5.dp)
) {
// 最顶上那个
if (parent == null) {
val parts = cwd.split('/')
if (parts.lastIndex != 0) {
val prevDir = StringBuilder()
for (i in 0..<parts.lastIndex) {
prevDir.append("/${parts[i]}")
}
val prev = File(prevDir.toString())
if (prev.isDirectory) {
item {
FileSingleView(
WrappedFile(prev),
cwd,
ImageVector.vectorResource(R.drawable.outline_arrow_upward_32),
context.getString(R.string.prev_folder),
prev.path,
update
) {
onItemClick?.invoke(it)
}
}
}
}
} else {
item {
FileSingleView(
WrappedFile(File(parent)),
cwd,
ImageVector.vectorResource(R.drawable.outline_arrow_upward_32),
context.getString(R.string.prev_folder),
parent,
update
) {
onItemClick?.invoke(it)
}
}
}
// 下面的内容
items(fileList) { file ->
FileSingleView(
file, cwd,
update = update,
dragAndDrop = Modifier
.dragAndDropSource {
detectTapGestures(onLongPress = {
startTransfer(
DragAndDropTransferData(
ClipData.newPlainText(
"Drag", "Drag:${file.path}"
)
)
)
})
}
,
dragAndDropCallBack = dragAndDropCallBack,
onItemClick = onItemClick
)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun FileSingleView(
file: WrappedFile,
cwd: String,
forceIcon: ImageVector? = null,
forceName: String? = null,
forceParent: String? = null,
update: (() -> Unit)? = null,
dragAndDrop: Modifier = Modifier,
dragAndDropCallBack: DragAndDropTarget? = null,
onItemClick: ((String) -> Unit)? = null
) {
val openFileDialog = remember { mutableIntStateOf(3) }
when (openFileDialog.intValue) {
0 -> AskForName(
onDismissRequest = { openFileDialog.intValue = 3 },
onConfirmation = {
if (it.isEmpty()) {
Toast.makeText(
context,
context.getString(R.string.error_need_input_name),
Toast.LENGTH_SHORT
).show()
} else {
val f = File("$cwd/$it")
if (f.exists()) {
Toast.makeText(
context,
context.getString(R.string.error_already_exist),
Toast.LENGTH_SHORT
).show()
} else {
f.mkdir()
}
}
update?.invoke()
openFileDialog.intValue = 3
},
dir = cwd,
isDirectory = true
)
1 -> AskForName(
onDismissRequest = { openFileDialog.intValue = 3 },
onConfirmation = {
if (it.isEmpty()) {
Toast.makeText(
context,
context.getString(R.string.error_need_input_name),
Toast.LENGTH_SHORT
).show()
} else {
val f = File("$cwd/$it")
if (f.exists()) {
Toast.makeText(
context,
context.getString(R.string.error_already_exist),
Toast.LENGTH_SHORT
).show()
} else {
f.createNewFile()
}
}
update?.invoke()
openFileDialog.intValue = 3
},
dir = cwd,
isDirectory = false
)
else -> {}
}
Row(
modifier = Modifier
.fillMaxWidth()
.border(
width = Dp.Hairline,
color = Color.Gray,
shape = RectangleShape
)
.padding(vertical = 3.dp)
.clickable {
onItemClick?.invoke(file.path)
}.then(
if (dragAndDropCallBack != null) {
Modifier.dragAndDropTarget(
shouldStartDragAndDrop = { event ->
val result = event
.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
if (result) {
dropTarget = file.path
}
result
}, target = dragAndDropCallBack
)
}else{
Modifier
}
)
,
verticalAlignment = Alignment.CenterVertically,
) {
Image(
forceIcon ?: 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)
.then(dragAndDrop)
)
Column(
modifier = Modifier
.padding(horizontal = 15.dp)
.fillMaxWidth(0.8f)
) {
Text(
text = forceName ?: file.name,
fontSize = 24.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = forceParent ?: file.getModifiedTimeString(context),
fontSize = 15.sp,
color = Color.Gray,
maxLines = 1
)
}
Spacer(Modifier.weight(1f))
if (forceName == null) {
IconButton(
onClick = {
if (file.type == Type.FILE) { // 普通文件
AlertHelper.showNoPasteAlert(context,
onCopy = {
val f = File(file.path)
if (f.isFile) {
ClipHelper.getInstance(context).copy(f, context)
}
},
onPaste = {
},
onDelete = {
AlertHelper.showDeleteAlert(context, file.path) {
update?.invoke()
}
},
onCut = {
CutHelper.cut(context, File(file.path))
update?.invoke()
},
onInfo = {
AlertHelper.showFileInfoAlert(context, file.path)
}
)
} else { // 普通文件夹
AlertHelper.showNoPasteAlert(context,
onCopy = {
val f = File(file.path)
if (f.isDirectory) {
ClipHelper.getInstance(context).copyFolder(f.path)
}
},
onPaste = {
},
onDelete = {
AlertHelper.showDeleteAlert(context, file.path) {
update?.invoke()
}
},
onCut = {
val f = File(file.path)
if (f.isDirectory) {
CutHelper.cutFolder(context, f)
}
update?.invoke()
},
onInfo = {
AlertHelper.showFileInfoAlert(context, file.path)
}
)
}
},
modifier = Modifier.padding(horizontal = 10.dp)
) {
Image(
ImageVector.vectorResource(R.drawable.outline_info_24), "info"
)
}
} else { // 最上面那个按钮
IconButton(
onClick = {
AlertHelper.showOnlyPasteInfoNewAlert(context,
onPaste = {
val uri = ClipHelper.getInstance(context).paste()
if (uri != null) {
val name = uri.path?.split('/')?.last() ?: "somePastedItem"
val ext = name.split('.').last()
var actualFile = File(cwd, name)
while (actualFile.exists()) {
actualFile = File("${actualFile.path}_paste.$ext")
}
val inputStream = context.contentResolver.openInputStream(uri)
if (inputStream != null) {
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
// 刷新
update?.invoke()
}
} else {
val pasteDir = ClipHelper.getInstance(context).pasteFolder()
if (pasteDir != null) {
val sourceDir = File(pasteDir)
if (sourceDir.isDirectory) {
var destDir = File("$cwd/${sourceDir.name}")
while (destDir.exists()) {
destDir = File("${destDir.path}_paste")
}
destDir.mkdir()
PasteHelper.copyDirectory(sourceDir, destDir)
}
update?.invoke()
} else {
Toast.makeText(
context,
context.getString(R.string.error_nothing_to_paste),
Toast.LENGTH_SHORT
)
.show()
}
}
},
onInfo = {
AlertHelper.showFileInfoAlert(context, cwd)
},
onNewFile = {
openFileDialog.intValue = 1
},
onNewFolder = {
openFileDialog.intValue = 0
}
)
},
modifier = Modifier.padding(horizontal = 10.dp)
) {
Image(
ImageVector.vectorResource(R.drawable.outline_info_i_24), "info"
)
}
}
}
}
@Composable
fun AskForName(
onDismissRequest: () -> Unit,
onConfirmation: (String) -> Unit,
dir: String,
isDirectory: Boolean
) {
var input by remember { mutableStateOf("") }
AlertDialog(
icon = {
Icon(
ImageVector.vectorResource(R.drawable.baseline_question_mark_24),
contentDescription = "Ask"
)
},
title = {
Text(text = context.getString(R.string.input_name))
},
text = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = context.getString(
if (isDirectory) {
R.string.create_directory
} else {
R.string.create_file
}, dir
),
modifier = Modifier.padding(vertical = 5.dp)
)
TextField(
value = input,
onValueChange = { input = it },
maxLines = 1
)
}
},
onDismissRequest = {
onDismissRequest()
},
confirmButton = {
TextButton(
onClick = {
onConfirmation(input)
}
) {
Text(context.getString(R.string.confirm))
}
},
dismissButton = {
TextButton(
onClick = {
onDismissRequest()
}
) {
Text(context.getString(R.string.cancel))
}
}
)
}
}

View file

@ -0,0 +1,284 @@
package com.example.myapplication
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.GridView
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog.Builder
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type
import com.example.myapplication.adapters.DocumentAdapter
import com.example.myapplication.adapters.DocumentModel
import com.example.myapplication.fileSystem.CutHelper
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister.Companion.instance
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister.Companion.regex
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
class document_page : AppCompatActivity() {
private var documentList = listOf<String>()
private val pasteDir = "${Environment.getExternalStorageDirectory().path}/Documents/pasted"
private var listOrderType = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
setContentView(R.layout.document_page)
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v: View, insets: WindowInsetsCompat ->
val systemBars =
insets.getInsets(Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
// 设置排序按钮的点击事件
val sortImageView = findViewById<ImageView>(R.id.sortDocumentView)
sortImageView.setOnClickListener { v: View? -> showSortOptions() }
// 设置左箭头的点击事件,返回上一级页面
val leftArrowImageView = findViewById<ImageView>(R.id.leftArrowImageView)
leftArrowImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@document_page,
main_page::class.java
)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
finish()
}
val searchImageView = findViewById<ImageView>(R.id.searchDocumentView)
searchImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@document_page,
document_page_search::class.java
)
startActivity(intent) // 跳转到搜索页面
}
findViewById<ImageView>(R.id.refreshData).setOnClickListener { _ ->
update()
}
val documentGrid: GridView = findViewById(R.id.DocumentGrid)
documentGrid.onItemClickListener =
AdapterView.OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
val file = File(documentList[position])
if (file.isFile) {
val uri = FileProvider.getUriForFile(
this,
applicationContext.packageName + ".provider",
file
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, when(file.extension){
"xls" -> "application/vnd.ms-excel"
"xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
"doc" -> "application/msword"
"docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
"ppt" -> "application/vnd.ms-powerpoint"
"pptx" -> "application/vnd.openxmlformats-officedocument.presentationml.presentation"
"txt" -> "text/plain"
"htm","html" -> "text/html"
"pdf" -> "application/pdf"
else -> "application/*"
})
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try { startActivity(intent) }
catch (e: ActivityNotFoundException){
Toast.makeText(baseContext,"没有安装可以打开此类型文件的应用",Toast.LENGTH_SHORT).show()
}
}
}
documentGrid.onItemLongClickListener =
AdapterView.OnItemLongClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
showDocumentOperation(
position
)
true
}
// 最后在后台刷新
CoroutineScope(Dispatchers.Default).launch {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
val defaultText = loadingTextView.text
launch { loadingText(loadingTextView,defaultText) }
documentList = instance.dateOrderedList()
val models = ArrayList<DocumentModel>()
for (path in documentList) {
models.add(DocumentModel(File(path)))
}
runOnUiThread {
val adapter = DocumentAdapter(this@document_page, models)
val grid = findViewById<GridView>(R.id.DocumentGrid)
grid.setAdapter(adapter)
findViewById<TextView>(R.id.LoadingBlankText).visibility = View.GONE
}
}
}
private fun showDocumentOperation(position: Int) {
AlertHelper.showItemAlert(this,
onCopy = {
val file = File(documentList[position])
if (file.isFile) {
ClipHelper.getInstance(this).copy(file, this)
}
},
onPaste = {
val uri = ClipHelper.getInstance(this).paste()
if (uri != null) {
val dir = File(pasteDir)
if (!dir.exists()) {
dir.mkdir()
}
val name = uri.path?.split('/')?.last() ?: "somePastedItem"
val ext = name.split('.').last()
if (!"$ext.".matches(regex)){
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
return@showItemAlert
}
val actualFile = File(dir, "${name}_paste.$ext")
val inputStream = contentResolver.openInputStream(uri)
if (inputStream != null) {
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
// 刷新
update()
}
} else {
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
}
},
onDelete = {
AlertHelper.showDeleteAlert(this, documentList[position]) {
update()
}
},
onCut = {
CutHelper.cut(this, File(documentList[position]))
update()
},
onInfo = {
AlertHelper.showFileInfoAlert(this, documentList[position])
}
)
}
private fun showSortOptions() {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
// 创建对话框构建者
val builder = Builder(this@document_page)
builder.setTitle("选择排序方式")
.setItems(
arrayOf<CharSequence>("按时间排序(默认)", "按大小排序")
) { dialog: DialogInterface?, which: Int ->
when (which) {
0 -> {
listOrderType = 0
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@document_page,
"已选择按时间排序",
Toast.LENGTH_SHORT
).show()
}
}
}
1 -> {
listOrderType = 1
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@document_page,
"已选择按大小排序",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
.setNegativeButton(
"取消"
) { dialog: DialogInterface, which: Int -> dialog.dismiss() }
.show()
}
private fun update(runSomethingMore: (()->Unit)? = null) {
instance.initialize {
documentList = when (listOrderType) {
0 -> instance.dateOrderedList()
1 -> instance.sizeOrderedList()
else -> listOf()
}
val models = ArrayList<DocumentModel>()
for (path in documentList) {
models.add(DocumentModel(File(path)))
}
val adapter = DocumentAdapter(this, models)
runOnUiThread {
val grid = findViewById<GridView>(R.id.DocumentGrid)
grid.setAdapter(adapter)
}
runSomethingMore?.invoke()
}
}
private fun loadingText(
loadingTextView: TextView,
defaultText: CharSequence,
dots: String = ""
) {
Thread.sleep(500)
val next = if (dots.length > 3) {
""
} else {
"$dots."
}
runOnUiThread {
loadingTextView.text = "$defaultText$next"
}
if (loadingTextView.visibility != View.GONE) {
loadingText(loadingTextView, defaultText, next)
}
}
}

View file

@ -0,0 +1,44 @@
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
public class document_page_search extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.document_page_search);
// 设置左箭头的点击事件返回上一级页面
ImageView leftArrowImageView = findViewById(R.id.leftArrowImageView);
leftArrowImageView.setOnClickListener(v -> {
Intent intent = new Intent(document_page_search.this, document_page.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
});
SearchView searchView = findViewById(R.id.searchDocument); // 确保使用正确的 ID
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
// 处理搜索提交
Toast.makeText(document_page_search.this, "搜索: " + query, Toast.LENGTH_SHORT).show();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// 处理搜索文本变化
// 可以在这里添加过滤逻辑
return false;
}
});
}
}

View file

@ -0,0 +1,53 @@
package com.example.myapplication.fileSystem
import android.content.Context
import android.os.Environment
import com.example.myapplication.compose.PasteHelper
import com.example.myapplication.utils.ClipHelper
import java.io.File
class DeleteHelper {
companion object{
fun delete(path: String){
val file = File(path)
if (file.isFile){
file.delete()
}else if (file.isDirectory){
file.deleteRecursively()
}
}
}
}
class CutHelper{
companion object {
fun cut(context: Context, file: File){
val cacheDir = File("${Environment.getExternalStorageDirectory()}/.copy")
if (!cacheDir.exists()){
cacheDir.mkdir()
}
if (file.exists()) {
val tempFile = File("${Environment.getExternalStorageDirectory()}/.copy", file.name)
val bytes = file.readBytes()
tempFile.writeBytes(bytes)
ClipHelper.getInstance(context).copy(tempFile, context)
DeleteHelper.delete(file.path)
}
}
fun cutFolder(context: Context,folder: File){
val cacheDir = File("${Environment.getExternalStorageDirectory()}/.copy")
if (!cacheDir.exists()){
cacheDir.mkdir()
}
if (folder.exists()) {
val tempFolder = File("${Environment.getExternalStorageDirectory()}/.copy", folder.name)
PasteHelper.copyDirectory(folder,tempFolder)
ClipHelper.getInstance(context).copyFolder(tempFolder.path)
DeleteHelper.delete(folder.path)
}
}
}
}

View file

@ -0,0 +1,39 @@
package com.example.myapplication.fileSystem
import android.app.usage.StorageStatsManager
import android.content.Context
import android.os.Environment
import android.os.storage.StorageManager
import java.io.IOException
class SystemStorageInfo(private val context: Context) {
private val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
private val storageStatsManager =
context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager
private var totalStorage = 1uL
private var freeStorage = 0uL
fun getTotalStorageSize(): ULong {
try {
val uuid = storageManager.getUuidForPath(Environment.getDataDirectory())
totalStorage = storageStatsManager.getTotalBytes(uuid).toULong()
return totalStorage
} catch (e: IOException) {
return 1uL
}
}
fun getFreeStorageSize(): ULong {
try {
val uuid = storageManager.getUuidForPath(Environment.getDataDirectory())
freeStorage = storageStatsManager.getFreeBytes(uuid).toULong()
return freeStorage
} catch (e: IOException) {
return 0uL
}
}
fun getUsedPercentage(): Int = 100 - Math.round(freeStorage.toDouble() * 100 / totalStorage.toDouble()).toInt()
}

View file

@ -0,0 +1,155 @@
package com.example.myapplication.fileSystem
import android.content.Context
import android.icu.text.DecimalFormat
import android.text.format.DateFormat
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister
import com.example.myapplication.fileSystem.byTypeFileLister.ImageLister
import com.example.myapplication.fileSystem.byTypeFileLister.MusicLister
import com.example.myapplication.fileSystem.byTypeFileLister.VideoLister
import java.io.File
import java.net.URLConnection
import java.nio.file.Files
import java.nio.file.attribute.BasicFileAttributes
import java.sql.Date
import java.sql.Timestamp
import java.time.Instant
class WrappedFile(private val f: File, skipCalculateDirectorySize: Boolean = false) {
companion object {
fun getSizeString(size: ULong): String {
var sizeFirst = size
var sizeLast = 0
var unit = SizeUnit.B
while (sizeFirst > 1024u && unit != SizeUnit.GB) {
sizeLast = size.mod(1024u).toInt()
sizeFirst /= 1024u
unit = when (unit) {
SizeUnit.B -> SizeUnit.KB
SizeUnit.KB -> SizeUnit.MB
SizeUnit.MB -> SizeUnit.GB
else -> SizeUnit.B
}
}
val s: Double = sizeFirst.toDouble() + sizeLast / 1000.0
return "${DecimalFormat("#.##").format(s)} ${getUnit(unit)}"
}
private fun getUnit(unit: SizeUnit): String = when (unit) {
SizeUnit.B -> "B"
SizeUnit.KB -> "KB"
SizeUnit.MB -> "MB"
SizeUnit.GB -> "GB"
}
fun guessMime(ext: String): String {
// Known ext
val dotExt = ".$ext"
if (dotExt.matches(ImageLister.regex)) {
return "image/*"
} else if (dotExt.matches(VideoLister.regex)) {
return "video/*"
} else if (dotExt.matches(MusicLister.regex)) {
return "audio/*"
} else if (dotExt.matches(DocumentLister.regex)) {
return when (ext) {
"xls" -> "application/vnd.ms-excel"
"xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
"doc" -> "application/msword"
"docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
"ppt" -> "application/vnd.ms-powerpoint"
"pptx" -> "application/vnd.openxmlformats-officedocument.presentationml.presentation"
"txt" -> "text/plain"
"htm", "html" -> "text/html"
"pdf" -> "application/pdf"
else -> "application/*"
}
} else return URLConnection.guessContentTypeFromName("f.$ext") ?: "text/plain"
}
}
enum class Type {
FILE,
DIRECTORY
}
private enum class SizeUnit {
B,
KB,
MB,
GB
}
val name: String
val nameWithoutExt: String
val path: String
val type: Type
val lastModifiedTime: Instant
val mime: String
private var isSizeCalculated = true
var size: Long
private set
init {
if (!f.exists()) {
throw RuntimeException("Cannot find file")
}
name = f.name
nameWithoutExt = f.nameWithoutExtension
path = f.path
type = when (f.isDirectory) {
true -> Type.DIRECTORY
false -> Type.FILE
}
size = if (type == Type.FILE) {
f.length()
} else {
if (skipCalculateDirectorySize) {
isSizeCalculated = false
0
} else {
getFolderSize(f)
}
}
val attr: BasicFileAttributes = Files.readAttributes(
f.toPath(),
BasicFileAttributes::class.java
)
lastModifiedTime = attr.lastModifiedTime().toInstant()
mime = if (f.isDirectory){
"dir"
}else {
guessMime(f.extension)
}
}
fun getSizeString(): String {
if (size == 0L) {
if (type == Type.DIRECTORY) {
if (!isSizeCalculated){
// Calculate Size
size = getFolderSize(f)
isSizeCalculated = true
}else{
return "0B"
}
} else {
return "未知"
}
}
return getSizeString(size.toULong())
}
fun getModifiedTimeString(context: Context): String {
val ts = Timestamp.from(lastModifiedTime)
val df = DateFormat.getDateFormat(context)
val tf = DateFormat.getTimeFormat(context)
val date = Date(ts.time)
return df.format(date) + " " + tf.format(date)
}
}

View file

@ -0,0 +1,59 @@
package com.example.myapplication.fileSystem.byTypeFileLister
import android.os.Environment
import com.example.myapplication.fileSystem.WrappedFile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class DocumentLister : Lister() {
companion object {
val instance by lazy { DocumentLister() }
val directories = listOf("Documents", "Download")
val regex = "\\.((xls|doc|ppt)(x|)|txt|htm(l|)|pdf)".toRegex()
}
val documentList = mutableListOf<String>()
fun initialize(onFinished: (() -> Unit)? = null) {
documentList.clear()
CoroutineScope(Dispatchers.IO).launch {
directories.forEach { dir ->
walkDir(
File("${Environment.getExternalStorageDirectory().path}/${dir}"),
documentList,
regex
)
}
onFinished?.invoke()
}
return
}
fun dateOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
documentList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.lastModifiedTime }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun sizeOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
documentList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.size }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun getFullSize(): ULong {
var size = 0UL
val wrappedFileList = mutableListOf<WrappedFile>()
documentList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.forEach { size += it.size.toUInt() }
return size
}
}

View file

@ -0,0 +1,59 @@
package com.example.myapplication.fileSystem.byTypeFileLister
import android.os.Environment
import com.example.myapplication.fileSystem.WrappedFile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class ImageLister private constructor() : Lister() {
companion object {
val instance by lazy { ImageLister() }
val directories = listOf("DCIM", "Pictures", "Download")
val regex = "\\.(jpg|png|jpeg|webp)".toRegex()
}
val imageList = mutableListOf<String>()
fun initialize(onFinished: (() -> Unit)? = null) {
imageList.clear()
CoroutineScope(Dispatchers.IO).launch {
directories.forEach { imageDirectory ->
walkDir(
File("${Environment.getExternalStorageDirectory().path}/${imageDirectory}"),
imageList,
regex
)
}
onFinished?.invoke()
}
return
}
fun dateOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
imageList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.lastModifiedTime }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun sizeOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
imageList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.size }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun getFullSize(): ULong {
var size = 0UL
val wrappedFileList = mutableListOf<WrappedFile>()
imageList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.forEach { size += it.size.toUInt() }
return size
}
}

View file

@ -0,0 +1,4 @@
package com.example.myapplication.fileSystem.byTypeFileLister
abstract class Lister {
}

View file

@ -0,0 +1,59 @@
package com.example.myapplication.fileSystem.byTypeFileLister
import android.os.Environment
import com.example.myapplication.fileSystem.WrappedFile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class MusicLister : Lister() {
companion object {
val instance by lazy { MusicLister() }
val directories = listOf("Recordings", "Download", "Audiobooks", "Music", "Podcasts", "Ringtones")
val regex = "\\.(mp3|ogg|aac|wav)".toRegex()
}
val musicList = mutableListOf<String>()
fun initialize(onFinished: (() -> Unit)? = null) {
musicList.clear()
CoroutineScope(Dispatchers.IO).launch {
directories.forEach { dir ->
walkDir(
File("${Environment.getExternalStorageDirectory().path}/${dir}"),
musicList,
regex
)
}
onFinished?.invoke()
}
return
}
fun dateOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
musicList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.lastModifiedTime }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun sizeOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
musicList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.size }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun getFullSize(): ULong {
var size = 0UL
val wrappedFileList = mutableListOf<WrappedFile>()
musicList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.forEach { size += it.size.toUInt() }
return size
}
}

View file

@ -0,0 +1,59 @@
package com.example.myapplication.fileSystem.byTypeFileLister
import android.os.Environment
import com.example.myapplication.fileSystem.WrappedFile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class VideoLister : Lister() {
companion object {
val instance by lazy { VideoLister() }
val directories = listOf("DCIM", "Download", "Movies")
val regex = "\\.(mp4|avi|video|webm)".toRegex()
}
val videoList = mutableListOf<String>()
fun initialize(onFinished: (() -> Unit)? = null) {
videoList.clear()
CoroutineScope(Dispatchers.IO).launch {
directories.forEach { dir ->
walkDir(
File("${Environment.getExternalStorageDirectory().path}/${dir}"),
videoList,
regex
)
}
onFinished?.invoke()
}
return
}
fun dateOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
videoList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.lastModifiedTime }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun sizeOrderedList(): List<String> {
val wrappedFileList = mutableListOf<WrappedFile>()
videoList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.sortBy { it.size }
val result = mutableListOf<String>()
wrappedFileList.forEach { result.add(it.path) }
return result
}
fun getFullSize(): ULong {
var size = 0UL
val wrappedFileList = mutableListOf<WrappedFile>()
videoList.forEach { wrappedFileList.add(WrappedFile(File(it))) }
wrappedFileList.forEach { size += it.size.toUInt() }
return size
}
}

View file

@ -0,0 +1,20 @@
package com.example.myapplication.fileSystem.byTypeFileLister
import java.io.File
fun Lister.walkDir(directory: File,list: MutableList<String>,pattern: Regex ,ignoreDotFile: Boolean = true){
if (!directory.exists() || !directory.isDirectory){
return
}
directory.listFiles()?.forEach {
if (ignoreDotFile && !it.name.startsWith(".")) {
if (it.isDirectory) {
walkDir(it, list, pattern)
} else if (it.isFile) {
if (it.name.contains(pattern)) {
list.add(it.path)
}
}
}
}
}

View file

@ -0,0 +1,33 @@
package com.example.myapplication.fileSystem
import java.io.File
fun getFolderSize(folder: File, level: Int = 0): Long {
if (!folder.exists() || !folder.isDirectory) {
return 0
}
if (level >= 5) { // 限制递归层数防止过度递归
return 1.shl(63)
}
var size = 0L
val file = folder.listFiles()
folder.listFiles()?.forEach { content ->
size += if (content.isFile) {
content.length()
} else if (content.isDirectory) {
getFolderSize(content, level + 1).let {
if (size > 0 || it > 0) {
it
} else {
-it
}
}
} else {
0
}
}
return size
}

View file

@ -0,0 +1,129 @@
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import androidx.activity.EdgeToEdge;
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.ViewFileActivity;
import com.example.myapplication.fileSystem.DeleteHelper;
import java.io.File;
public class main_page extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.main_page);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
ButtonClickerHandler buttonClickerHandler = new ButtonClickerHandler(this);
findViewById(R.id.MainPagePictureButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageVideoButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageMusicButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageDocumentButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageStorageButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageDownloadButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageAllFilesButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageDownloadButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageDocumentsButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageRecordingButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPageDCIMButton).setOnClickListener(buttonClickerHandler);
findViewById(R.id.MainPagePicturesButton).setOnClickListener(buttonClickerHandler);
}
@Override protected void onDestroy() {
// 把剪切的缓存文件删掉
File cacheDir = new File(Environment.getExternalStorageDirectory() + "/.copy");
if (cacheDir.exists()) {
DeleteHelper.Companion.delete(cacheDir.getPath());
}
super.onDestroy();
}
public class ButtonClickerHandler implements View.OnClickListener {
private final Context context;
ButtonClickerHandler(Context context) {
this.context = context;
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.MainPagePictureButton) {
Intent intent = new Intent(context, picture_page.class);
startActivity(intent);
}
if (view.getId() == R.id.MainPageVideoButton) {
Intent intent = new Intent(context, video_page.class);
startActivity(intent);
}
if (view.getId() == R.id.MainPageMusicButton) {
Intent intent = new Intent(context, music_page.class);
startActivity(intent);
}
if (view.getId() == R.id.MainPageDocumentButton) {
Intent intent = new Intent(context, document_page.class);
startActivity(intent);
}
if (view.getId() == R.id.MainPageStorageButton) {
Intent intent = new Intent(context, store_page.class);
startActivity(intent);
}
if (view.getId() == R.id.MainPageAllFilesButton) {
Intent intent = new Intent(context, ViewFileActivity.class);
startActivity(intent);
}
if (view.getId() == R.id.MainPageDCIMButton) {
Intent intent = new Intent(context, ViewFileActivity.class);
Bundle bundle = new Bundle();
bundle.putString("folder", Environment.getExternalStorageDirectory().getPath() + "/DCIM");
intent.putExtras(bundle);
startActivity(intent);
}
if (view.getId() == R.id.MainPageDownloadButton) {
Intent intent = new Intent(context, ViewFileActivity.class);
Bundle bundle = new Bundle();
bundle.putString("folder",
Environment.getExternalStorageDirectory().getPath() + "/Download");
intent.putExtras(bundle);
startActivity(intent);
}
if (view.getId() == R.id.MainPageDocumentsButton) {
Intent intent = new Intent(context, ViewFileActivity.class);
Bundle bundle = new Bundle();
bundle.putString("folder",
Environment.getExternalStorageDirectory().getPath() + "/Documents");
intent.putExtras(bundle);
startActivity(intent);
}
if (view.getId() == R.id.MainPageRecordingButton) {
Intent intent = new Intent(context, ViewFileActivity.class);
Bundle bundle = new Bundle();
bundle.putString("folder",
Environment.getExternalStorageDirectory().getPath() + "/Recordings");
intent.putExtras(bundle);
startActivity(intent);
}
if (view.getId() == R.id.MainPagePicturesButton) {
Intent intent = new Intent(context, ViewFileActivity.class);
Bundle bundle = new Bundle();
bundle.putString("folder",
Environment.getExternalStorageDirectory().getPath() + "/Pictures");
intent.putExtras(bundle);
startActivity(intent);
}
}
}
}

View file

@ -0,0 +1,269 @@
package com.example.myapplication
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.GridView
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog.Builder
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type
import com.example.myapplication.adapters.MusicAdapter
import com.example.myapplication.adapters.MusicModel
import com.example.myapplication.fileSystem.CutHelper
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister.Companion
import com.example.myapplication.fileSystem.byTypeFileLister.MusicLister.Companion.instance
import com.example.myapplication.fileSystem.byTypeFileLister.MusicLister.Companion.regex
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
class music_page : AppCompatActivity() {
private var musicList = listOf<String>()
private val pasteDir = "${Environment.getExternalStorageDirectory().path}/Music/pasted"
private var listOrderType = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
setContentView(R.layout.music_page)
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v: View, insets: WindowInsetsCompat ->
val systemBars =
insets.getInsets(Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
// 设置排序按钮的点击事件
val sortImageView = findViewById<ImageView>(R.id.sortMusicView)
sortImageView.setOnClickListener { v: View? -> showSortOptions() }
// 设置左箭头的点击事件,返回上一级页面
val leftArrowImageView = findViewById<ImageView>(R.id.leftArrowImageView)
leftArrowImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@music_page,
main_page::class.java
)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
}
val searchImageView = findViewById<ImageView>(R.id.searchMusicView)
searchImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@music_page,
music_page_search::class.java
)
startActivity(intent) // 跳转到搜索页面
}
findViewById<ImageView>(R.id.refreshData).setOnClickListener { _ ->
update()
}
val musicGrid: GridView = findViewById(R.id.MusicGrid)
musicGrid.onItemClickListener =
AdapterView.OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
val file = File(musicList[position])
if (file.isFile) {
val uri = FileProvider.getUriForFile(
this,
applicationContext.packageName + ".provider",
file
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "audio/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
}
musicGrid.onItemLongClickListener =
AdapterView.OnItemLongClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
showMusicOperation(
position
)
true
}
// 最后在后台刷新
CoroutineScope(Dispatchers.Default).launch {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
val defaultText = loadingTextView.text
launch { loadingText(loadingTextView,defaultText) }
musicList = instance.dateOrderedList()
val models = ArrayList<MusicModel>()
for (path in musicList) {
models.add(MusicModel(File(path)))
}
runOnUiThread {
val adapter = MusicAdapter(this@music_page, models)
val grid = findViewById<GridView>(R.id.MusicGrid)
grid.setAdapter(adapter)
findViewById<TextView>(R.id.LoadingBlankText).visibility = View.GONE
}
}
}
private fun showMusicOperation(position: Int) {
AlertHelper.showItemAlert(this,
onCopy = {
val file = File(musicList[position])
if (file.isFile) {
ClipHelper.getInstance(this).copy(file, this)
}
},
onPaste = {
val uri = ClipHelper.getInstance(this).paste()
if (uri != null) {
val dir = File(pasteDir)
if (!dir.exists()) {
dir.mkdir()
}
val name = uri.path?.split('/')?.last() ?: "somePastedItem"
val ext = name.split('.').last()
if (!"$ext.".matches(DocumentLister.regex)){
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
return@showItemAlert
}
val actualFile = File(dir, "${name}_paste.$ext")
val inputStream = contentResolver.openInputStream(uri)
if (inputStream != null) {
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
// 刷新
update()
}
} else {
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
}
},
onDelete = {
AlertHelper.showDeleteAlert(this, musicList[position]) {
update()
}
},
onCut = {
CutHelper.cut(this, File(musicList[position]))
update()
},
onInfo = {
AlertHelper.showFileInfoAlert(this, musicList[position])
}
)
}
private fun showSortOptions() {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
// 创建对话框构建者
val builder = Builder(this@music_page)
builder.setTitle("选择排序方式")
.setItems(
arrayOf<CharSequence>("按时间排序(默认)", "按大小排序")
) { dialog: DialogInterface?, which: Int ->
when (which) {
0 -> {
listOrderType = 0
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@music_page,
"已选择按时间排序",
Toast.LENGTH_SHORT
).show()
}
}
}
1 -> {
listOrderType = 1
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@music_page,
"已选择按大小排序",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
.setNegativeButton(
"取消"
) { dialog: DialogInterface, which: Int -> dialog.dismiss() }
.show()
}
private fun update(runSomethingMore: (()->Unit)? = null) {
instance.initialize {
musicList = when (listOrderType) {
0 -> instance.dateOrderedList()
1 -> instance.sizeOrderedList()
else -> listOf()
}
val models = ArrayList<MusicModel>()
for (path in musicList) {
models.add(MusicModel(File(path)))
}
val adapter = MusicAdapter(this, models)
runOnUiThread {
val grid = findViewById<GridView>(R.id.MusicGrid)
grid.setAdapter(adapter)
}
runSomethingMore?.invoke()
}
}
private fun loadingText(
loadingTextView: TextView,
defaultText: CharSequence,
dots: String = ""
) {
Thread.sleep(500)
val next = if (dots.length > 3) {
""
} else {
"$dots."
}
runOnUiThread {
loadingTextView.text = "$defaultText$next"
}
if (loadingTextView.visibility != View.GONE) {
loadingText(loadingTextView, defaultText, next)
}
}
}

View file

@ -0,0 +1,43 @@
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
public class music_page_search extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.music_page_search);
// 设置左箭头的点击事件返回上一级页面
ImageView leftArrowImageView = findViewById(R.id.leftArrowImageView);
leftArrowImageView.setOnClickListener(v -> {
Intent intent = new Intent(music_page_search.this, music_page.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
});
SearchView searchView = findViewById(R.id.searchMusic); // 确保使用正确的 ID
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
// 处理搜索提交
Toast.makeText(music_page_search.this, "搜索: " + query, Toast.LENGTH_SHORT).show();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// 处理搜索文本变化
// 可以在这里添加过滤逻辑
return false;
}
});
}
}

View file

@ -0,0 +1,270 @@
package com.example.myapplication
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.view.View
import android.widget.AdapterView
import android.widget.GridView
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog.Builder
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type
import com.example.myapplication.adapters.ImageAdapter
import com.example.myapplication.adapters.ImageModel
import com.example.myapplication.fileSystem.CutHelper
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister.Companion
import com.example.myapplication.fileSystem.byTypeFileLister.ImageLister.Companion.instance
import com.example.myapplication.fileSystem.byTypeFileLister.ImageLister.Companion.regex
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
class picture_page : AppCompatActivity() {
private var imageList = listOf<String>()
private val pasteDir = "${Environment.getExternalStorageDirectory().path}/Pictures/pasted"
private var imageListOrderType = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
setContentView(R.layout.picture_page)
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v: View, insets: WindowInsetsCompat ->
val systemBars =
insets.getInsets(Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
// 设置排序按钮的点击事件
val sortImageView = findViewById<ImageView>(R.id.sortImageView)
sortImageView.setOnClickListener { v: View? -> showSortOptions() }
// 设置左箭头的点击事件,返回上一级页面
val leftArrowImageView = findViewById<ImageView>(R.id.leftArrowImageView)
leftArrowImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@picture_page,
main_page::class.java
)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
finish()
}
// 设置搜索按钮的点击事件,跳转到 picture_page_search 页面
val searchImageView = findViewById<ImageView>(R.id.searchImageView)
searchImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@picture_page,
picture_page_search::class.java
)
startActivity(intent) // 跳转到搜索页面
}
findViewById<ImageView>(R.id.refreshData).setOnClickListener { _ ->
update()
}
val pictureGrid: GridView = findViewById(R.id.PicturePageGrid)
pictureGrid.onItemClickListener =
AdapterView.OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
val file = File(imageList[position])
if (file.isFile) {
val uri = FileProvider.getUriForFile(
this,
applicationContext.packageName + ".provider",
file
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "image/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
}
pictureGrid.onItemLongClickListener =
AdapterView.OnItemLongClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
showImageOperation(
position
)
true
}
// 最后在后台刷新
CoroutineScope(Dispatchers.Default).launch {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
val defaultText = loadingTextView.text
launch { loadingText(loadingTextView,defaultText) }
imageList = instance.dateOrderedList()
val imageModels = ArrayList<ImageModel>()
for (path in imageList) {
imageModels.add(ImageModel(File(path)))
}
runOnUiThread {
val adapter = ImageAdapter(this@picture_page, imageModels)
val grid = findViewById<GridView>(R.id.PicturePageGrid)
grid.setAdapter(adapter)
findViewById<TextView>(R.id.LoadingBlankText).visibility = View.GONE
}
}
}
private fun showImageOperation(position: Int) {
AlertHelper.showItemAlert(this,
onCopy = {
val file = File(imageList[position])
if (file.isFile) {
ClipHelper.getInstance(this).copy(file, this)
}
},
onPaste = {
val uri = ClipHelper.getInstance(this).paste()
if (uri != null) {
val dir = File(pasteDir)
if (!dir.exists()) {
dir.mkdir()
}
val name = uri.path?.split('/')?.last() ?: "somePastedItem"
val ext = name.split('.').last()
if (!"$ext.".matches(DocumentLister.regex)){
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
return@showItemAlert
}
val actualFile = File(dir, "${name}_paste.$ext")
val inputStream = contentResolver.openInputStream(uri)
if (inputStream != null) {
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
// 刷新
update()
}
} else {
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
}
},
onDelete = {
AlertHelper.showDeleteAlert(this, imageList[position]) {
update()
}
},
onCut = {
CutHelper.cut(this, File(imageList[position]))
update()
},
onInfo = {
AlertHelper.showFileInfoAlert(this, imageList[position])
}
)
}
private fun showSortOptions() {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
// 创建对话框构建者
val builder = Builder(this@picture_page)
builder.setTitle("选择排序方式")
.setItems(
arrayOf<CharSequence>("按时间排序(默认)", "按大小排序")
) { dialog: DialogInterface?, which: Int ->
when (which) {
0 -> {
imageListOrderType = 0
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@picture_page,
"已选择按时间排序",
Toast.LENGTH_SHORT
).show()
}
}
}
1 -> {
imageListOrderType = 1
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@picture_page,
"已选择按大小排序",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
.setNegativeButton(
"取消"
) { dialog: DialogInterface, which: Int -> dialog.dismiss() }
.show()
}
private fun update(runSomethingMore: (()->Unit)? = null) {
instance.initialize {
imageList = when (imageListOrderType) {
0 -> instance.dateOrderedList()
1 -> instance.sizeOrderedList()
else -> listOf()
}
val imageModels = ArrayList<ImageModel>()
for (path in imageList) {
imageModels.add(ImageModel(File(path)))
}
val adapter = ImageAdapter(this, imageModels)
runOnUiThread {
val grid = findViewById<GridView>(R.id.PicturePageGrid)
grid.setAdapter(adapter)
}
runSomethingMore?.invoke()
}
}
private fun loadingText(
loadingTextView: TextView,
defaultText: CharSequence,
dots: String = ""
) {
Thread.sleep(500)
val next = if (dots.length > 3) {
""
} else {
"$dots."
}
runOnUiThread {
loadingTextView.text = "$defaultText$next"
}
if (loadingTextView.visibility != View.GONE) {
loadingText(loadingTextView, defaultText, next)
}
}
}

View file

@ -0,0 +1,43 @@
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
public class picture_page_search extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.picture_page_search);
// 设置左箭头的点击事件返回上一级页面
ImageView leftArrowImageView = findViewById(R.id.leftArrowImageView);
leftArrowImageView.setOnClickListener(v -> {
Intent intent = new Intent(picture_page_search.this, picture_page.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
});
SearchView searchView = findViewById(R.id.searchPicture); // 确保使用正确的 ID
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
// 处理搜索提交
Toast.makeText(picture_page_search.this, "搜索: " + query, Toast.LENGTH_SHORT).show();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// 处理搜索文本变化
// 可以在这里添加过滤逻辑
return false;
}
});
}
}

View file

@ -0,0 +1,72 @@
package com.example.myapplication
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.example.myapplication.fileSystem.SystemStorageInfo
import com.example.myapplication.fileSystem.WrappedFile
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister
import com.example.myapplication.fileSystem.byTypeFileLister.ImageLister
import com.example.myapplication.fileSystem.byTypeFileLister.MusicLister
import com.example.myapplication.fileSystem.byTypeFileLister.VideoLister
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class store_page : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
setContentView(R.layout.store_page)
val leftArrowImageView = findViewById<ImageView>(R.id.leftArrowImageView)
leftArrowImageView.setOnClickListener { v: View? ->
val intent = Intent(
this@store_page,
main_page::class.java
)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
finish()
}
val systemStorageInfo = SystemStorageInfo(this)
val tv = findViewById<TextView>(R.id.storageText)
tv.text = getString(
R.string.used_storage,
WrappedFile.getSizeString(systemStorageInfo.getTotalStorageSize() - systemStorageInfo.getFreeStorageSize()),
WrappedFile.getSizeString(systemStorageInfo.getTotalStorageSize())
)
(findViewById<View>(R.id.progressText) as TextView).text =
getString(
R.string.used_storage_percentage,
systemStorageInfo.getUsedPercentage()
)
(findViewById<View>(R.id.progressBar) as ProgressBar).progress =
systemStorageInfo.getUsedPercentage()
CoroutineScope(Dispatchers.Main).launch {
val imageSize = ImageLister.instance.getFullSize()
val imageSizeString = WrappedFile.getSizeString(imageSize)
val videoSize = VideoLister.instance.getFullSize()
val videoSizeString = WrappedFile.getSizeString(videoSize)
val musicSize = MusicLister.instance.getFullSize()
val musicSizeString = WrappedFile.getSizeString(musicSize)
val documentSize = DocumentLister.instance.getFullSize()
val documentSizeString = WrappedFile.getSizeString(documentSize)
val otherSize = systemStorageInfo.getTotalStorageSize() - systemStorageInfo.getFreeStorageSize() - imageSize - musicSize - documentSize
val otherSizeString = WrappedFile.getSizeString(otherSize)
runOnUiThread{
findViewById<TextView>(R.id.pictureStorage).text = imageSizeString
findViewById<TextView>(R.id.videoStorage).text = videoSizeString
findViewById<TextView>(R.id.audioStorage).text = musicSizeString
findViewById<TextView>(R.id.documentStorage).text = documentSizeString
findViewById<TextView>(R.id.appStorage).text = otherSizeString
}
}
}
}

View file

@ -0,0 +1,167 @@
package com.example.myapplication.utils
import android.content.Context
import android.content.DialogInterface
import androidx.appcompat.app.AlertDialog.Builder
import com.example.myapplication.R
import com.example.myapplication.fileSystem.DeleteHelper
import com.example.myapplication.fileSystem.WrappedFile
import java.io.File
class AlertHelper {
companion object {
fun showItemAlert(
context: Context,
onCopy: () -> Unit,
onPaste: () -> Unit,
onDelete: () -> Unit,
onCut: () -> Unit,
onInfo: () -> Unit
) {
val builder = Builder(context)
builder.setTitle(context.getString(R.string.select_action))
.setItems(
arrayOf<CharSequence>(
context.getString(R.string.action_copy),
context.getString(R.string.action_paste),
context.getString(R.string.action_delete),
context.getString(R.string.action_cut),
context.getString(R.string.action_info)
)
) { _: DialogInterface?, which: Int ->
when (which) {
0 -> {
onCopy()
}
1 -> {
onPaste()
}
2 -> {
onDelete()
}
3 -> {
onCut()
}
4 -> {
onInfo()
}
}
}
.setNegativeButton(context.getString(R.string.action_cancel))
{ dialog: DialogInterface, which: Int -> dialog.dismiss() }
.show()
}
fun showNoPasteAlert(
context: Context,
onCopy: () -> Unit,
onPaste: () -> Unit,
onDelete: () -> Unit,
onCut: () -> Unit,
onInfo: () -> Unit
) {
val builder = Builder(context)
builder.setTitle(context.getString(R.string.select_action))
.setItems(
arrayOf<CharSequence>(
context.getString(R.string.action_copy),
context.getString(R.string.action_delete),
context.getString(R.string.action_cut),
context.getString(R.string.action_info)
)
) { _: DialogInterface?, which: Int ->
when (which) {
0 -> {
onCopy()
}
1 -> {
onDelete()
}
2 -> {
onCut()
}
3 -> {
onInfo()
}
}
}
.setNegativeButton(context.getString(R.string.action_cancel))
{ dialog: DialogInterface, which: Int -> dialog.dismiss() }
.show()
}
fun showOnlyPasteInfoNewAlert(
context: Context,
onPaste: () -> Unit,
onInfo: () -> Unit,
onNewFile: () -> Unit,
onNewFolder: () -> Unit
) {
val builder = Builder(context)
builder.setTitle(context.getString(R.string.select_action))
.setItems(
arrayOf<CharSequence>(
context.getString(R.string.action_paste),
context.getString(R.string.action_info),
context.getString(R.string.action_new_file),
context.getString(R.string.action_new_folder)
)
) { _: DialogInterface?, which: Int ->
when (which) {
0 -> onPaste()
1 -> onInfo()
2-> onNewFile()
3-> onNewFolder()
}
}
.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))
.setMessage(context.getString(R.string.confirm_to_delete_file, file))
.setPositiveButton(context.getString(R.string.confirm)) { _, _ ->
DeleteHelper.delete(file)
onConfirm?.invoke()
}
.setNegativeButton(context.getString(R.string.cancel)) { dialog, _ ->
dialog.dismiss()
}
.show()
}
fun showFileInfoAlert(context: Context, file: String) {
val f = File(file)
if (!f.exists()) {
return
}
val wrappedFile = WrappedFile(f)
val builder = Builder(context)
builder.setTitle(context.getString(R.string.file_info))
.setMessage(
context.getString(
R.string.file_info_text,
wrappedFile.name,
wrappedFile.path,
wrappedFile.getSizeString(),
wrappedFile.getModifiedTimeString(context)
)
)
.setNegativeButton(context.getString(R.string.okay)) { dialog, _ ->
dialog.dismiss()
}
.show()
}
}
}

View file

@ -0,0 +1,68 @@
package com.example.myapplication.utils
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import androidx.core.content.FileProvider
import com.example.myapplication.BuildConfig
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.internal.synchronized
import java.io.File
class ClipHelper private constructor(context: Context) {
companion object {
private const val label = "${BuildConfig.APPLICATION_ID}\$ClipHelper"
private const val ENCODE_LABEL = "ClipHelper"
private var instance: ClipHelper? = null
@OptIn(InternalCoroutinesApi::class)
fun getInstance(context: Context): ClipHelper =
instance ?: synchronized(this) {
instance ?: ClipHelper(context).also { instance = it }
}
}
private val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
private val contentResolver: ContentResolver = context.contentResolver
fun copy(file: File, context: Context) {
val uri = FileProvider.getUriForFile(
context,
context.applicationContext.packageName + ".provider",
file
)
val clip = ClipData.newUri(contentResolver, label, uri)
clipboard.setPrimaryClip(clip)
}
fun paste(): Uri? {
val clip = clipboard.primaryClip
clip?.run {
val item: ClipData.Item = getItemAt(0)
return item.uri
}
return null
}
fun copyFolder(folder: String){
val clip = ClipData.newPlainText("SingleFolderCopy","$ENCODE_LABEL:${folder}")
clipboard.setPrimaryClip(clip)
}
fun pasteFolder(): String? {
val clip = clipboard.primaryClip
val content = clip?.run {
val item: ClipData.Item = getItemAt(0)
item.text
}
if (content != null){
if (content.startsWith(ENCODE_LABEL)){
return content.split(':').last()
}
}
return null
}
}

View file

@ -0,0 +1,271 @@
package com.example.myapplication
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.view.View
import android.widget.AdapterView
import android.widget.GridView
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog.Builder
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type
import com.example.myapplication.adapters.ImageAdapter
import com.example.myapplication.adapters.ImageModel
import com.example.myapplication.adapters.VideoAdapter
import com.example.myapplication.adapters.VideoModel
import com.example.myapplication.fileSystem.CutHelper
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister
import com.example.myapplication.fileSystem.byTypeFileLister.DocumentLister.Companion
import com.example.myapplication.fileSystem.byTypeFileLister.VideoLister.Companion.regex
import com.example.myapplication.fileSystem.byTypeFileLister.VideoLister.Companion.instance
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
class video_page : AppCompatActivity() {
private var videoList = listOf<String>()
private val pasteDir = "${Environment.getExternalStorageDirectory().path}/Movies/pasted"
private var videoListOrderType = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.enableEdgeToEdge()
setContentView(R.layout.video_page)
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v: View, insets: WindowInsetsCompat ->
val systemBars =
insets.getInsets(Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
// 设置排序按钮的点击事件
val sortImageView = findViewById<ImageView>(R.id.sortVideoView)
sortImageView.setOnClickListener { v: View? -> showSortOptions() }
// 设置左箭头的点击事件,返回上一级页面
val leftArrowImageView = findViewById<ImageView>(R.id.leftArrowImageView)
leftArrowImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@video_page,
main_page::class.java
)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
finish()
}
val searchImageView = findViewById<ImageView>(R.id.searchVideoView)
searchImageView.setOnClickListener { v: View? ->
val intent =
Intent(
this@video_page,
video_page_search::class.java
)
startActivity(intent) // 跳转到搜索页面
}
findViewById<ImageView>(R.id.refreshData).setOnClickListener { _ ->
update()
}
val videoGrid: GridView = findViewById(R.id.VideoGrid)
videoGrid.onItemClickListener =
AdapterView.OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
val file = File(videoList[position])
if (file.isFile) {
val uri = FileProvider.getUriForFile(
this,
applicationContext.packageName + ".provider",
file
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "video/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
}
videoGrid.onItemLongClickListener =
AdapterView.OnItemLongClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
showVideoOperation(
position
)
true
}
// 最后在后台刷新
CoroutineScope(Dispatchers.Default).launch {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
val defaultText = loadingTextView.text
launch { loadingText(loadingTextView,defaultText) }
videoList = instance.dateOrderedList()
val videoModels = ArrayList<VideoModel>()
for (path in videoList) {
videoModels.add(VideoModel(File(path)))
}
runOnUiThread {
val adapter = VideoAdapter(this@video_page, videoModels)
val grid = findViewById<GridView>(R.id.VideoGrid)
grid.setAdapter(adapter)
findViewById<TextView>(R.id.LoadingBlankText).visibility = View.GONE
}
}
}
private fun showVideoOperation(position: Int) {
AlertHelper.showItemAlert(this,
onCopy = {
val file = File(videoList[position])
if (file.isFile) {
ClipHelper.getInstance(this).copy(file, this)
}
},
onPaste = {
val uri = ClipHelper.getInstance(this).paste()
if (uri != null) {
val dir = File(pasteDir)
if (!dir.exists()) {
dir.mkdir()
}
val name = uri.path?.split('/')?.last() ?: "somePastedItem"
val ext = name.split('.').last()
if (!"$ext.".matches(DocumentLister.regex)){
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
return@showItemAlert
}
val actualFile = File(dir, "${name}_paste.$ext")
val inputStream = contentResolver.openInputStream(uri)
if (inputStream != null) {
actualFile.writeBytes(IOUtils.toByteArray(inputStream))
inputStream.close()
// 刷新
update()
}
} else {
Toast.makeText(this, getString(R.string.error_nothing_to_paste), Toast.LENGTH_SHORT)
.show()
}
},
onDelete = {
AlertHelper.showDeleteAlert(this, videoList[position]) {
update()
}
},
onCut = {
CutHelper.cut(this, File(videoList[position]))
update()
},
onInfo = {
AlertHelper.showFileInfoAlert(this, videoList[position])
}
)
}
private fun showSortOptions() {
val loadingTextView = findViewById<TextView>(R.id.LoadingBlankText)
// 创建对话框构建者
val builder = Builder(this@video_page)
builder.setTitle("选择排序方式")
.setItems(
arrayOf<CharSequence>("按时间排序(默认)", "按大小排序")
) { dialog: DialogInterface?, which: Int ->
when (which) {
0 -> {
videoListOrderType = 0
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@video_page,
"已选择按时间排序",
Toast.LENGTH_SHORT
).show()
}
}
}
1 -> {
videoListOrderType = 1
runOnUiThread{
loadingTextView.visibility = View.VISIBLE
}
CoroutineScope(Dispatchers.IO).launch { loadingText(loadingTextView,loadingTextView.text) }
update {
runOnUiThread{
loadingTextView.visibility = View.GONE
Toast.makeText(
this@video_page,
"已选择按大小排序",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
.setNegativeButton(
"取消"
) { dialog: DialogInterface, which: Int -> dialog.dismiss() }
.show()
}
private fun update(runSomethingMore: (()->Unit)? = null) {
instance.initialize {
videoList = when (videoListOrderType) {
0 -> instance.dateOrderedList()
1 -> instance.sizeOrderedList()
else -> listOf()
}
val videoModels = ArrayList<VideoModel>()
for (path in videoList) {
videoModels.add(VideoModel(File(path)))
}
val adapter = VideoAdapter(this, videoModels)
runOnUiThread {
val grid = findViewById<GridView>(R.id.VideoGrid)
grid.setAdapter(adapter)
}
runSomethingMore?.invoke()
}
}
private fun loadingText(
loadingTextView: TextView,
defaultText: CharSequence,
dots: String = ""
) {
Thread.sleep(500)
val next = if (dots.length > 3) {
""
} else {
"$dots."
}
runOnUiThread {
loadingTextView.text = "$defaultText$next"
}
if (loadingTextView.visibility != View.GONE) {
loadingText(loadingTextView, defaultText, next)
}
}
}

View file

@ -0,0 +1,43 @@
package com.example.myapplication;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
public class video_page_search extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.video_page_search);
// 设置左箭头的点击事件返回上一级页面
ImageView leftArrowImageView = findViewById(R.id.leftArrowImageView);
leftArrowImageView.setOnClickListener(v -> {
Intent intent = new Intent(video_page_search.this, video_page.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
});
SearchView searchView = findViewById(R.id.searchVideo); // 确保使用正确的 ID
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
// 处理搜索提交
Toast.makeText(video_page_search.this, "搜索: " + query, Toast.LENGTH_SHORT).show();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// 处理搜索文本变化
// 可以在这里添加过滤逻辑
return false;
}
});
}
}

View file

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#514A3A" android:viewportHeight="24" android:viewportWidth="24" android:width="40dp">
<path android:fillColor="@android:color/white" android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="@android:color/white" android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="40dp"
android:tint="#BAC8B8"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="40dp">
<path
android:fillColor="@android:color/white"
android:pathData="M22,16L22,4c0,-1.1 -0.9,-2 -2,-2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zM11,12l2.03,2.71L16,11l4,5L8,16l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6L2,6z" />
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#DF7F7F" android:viewportHeight="24" android:viewportWidth="24" android:width="40dp">
<path android:fillColor="@android:color/white" android:pathData="M11.07,12.85c0.77,-1.39 2.25,-2.21 3.11,-3.44c0.91,-1.29 0.4,-3.7 -2.18,-3.7c-1.69,0 -2.52,1.28 -2.87,2.34L6.54,6.96C7.25,4.83 9.18,3 11.99,3c2.35,0 3.96,1.07 4.78,2.41c0.7,1.15 1.11,3.3 0.03,4.9c-1.2,1.77 -2.35,2.31 -2.97,3.45c-0.25,0.46 -0.35,0.76 -0.35,2.24h-2.89C10.58,15.22 10.46,13.95 11.07,12.85zM14,20c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2S14,18.9 14,20z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="40dp"
android:tint="#000000"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="40dp">
<path
android:fillColor="@android:color/white"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="40dp"
android:tint="#006064"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="40dp">
<path
android:fillColor="@android:color/white"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
</vector>

View file

@ -0,0 +1,5 @@
<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="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
</vector>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF"/>
<stroke android:width="3dp" android:color="@color/WhiteSmoke" />
<corners android:radius="255dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View file

@ -0,0 +1,29 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 背景圆环 -->
<item android:id="@android:id/background">
<shape
android:shape="ring"
android:innerRadiusRatio="3"
android:thicknessRatio="15"
android:useLevel="false">
<solid android:color="#e0e0e0" />
</shape>
</item>
<!-- 进度条圆环 -->
<item android:id="@android:id/progress">
<rotate
android:fromDegrees="-90"
android:toDegrees="270">
<shape
android:shape="ring"
android:innerRadiusRatio="3"
android:thicknessRatio="15"
android:useLevel="true">
<solid android:color="#66cc33" />
</shape>
</rotate>
</item>
</layer-list>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="62.5dp" android:tint="#FF8C00" android:viewportHeight="24" android:viewportWidth="24" android:width="62.5dp">
<path android:fillColor="@android:color/white" android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="40dp">
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="5dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="5dp">
<path android:fillColor="#1296db" android:pathData="M256,512c0,70.7 25,131.1 75,181A246.6,246.6 0,0 0,512 768c70.7,0 131,-25 181,-75C743,643.1 768,582.7 768,512c0,-70.7 -25,-131.1 -75,-181A246.6,246.6 0,0 0,512 256c-70.7,0 -131,25 -181,75C281,380.9 256,441.3 256,512z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="5dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="5dp">
<path android:fillColor="@color/lightgrey" android:pathData="M256,512c0,70.7 25,131.1 75,181A246.6,246.6 0,0 0,512 768c70.7,0 131,-25 181,-75C743,643.1 768,582.7 768,512c0,-70.7 -25,-131.1 -75,-181A246.6,246.6 0,0 0,512 256c-70.7,0 -131,25 -181,75C281,380.9 256,441.3 256,512z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="5dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="5dp">
<path android:fillColor="#FF8000" android:pathData="M256,512c0,70.7 25,131.1 75,181A246.6,246.6 0,0 0,512 768c70.7,0 131,-25 181,-75C743,643.1 768,582.7 768,512c0,-70.7 -25,-131.1 -75,-181A246.6,246.6 0,0 0,512 256c-70.7,0 -131,25 -181,75C281,380.9 256,441.3 256,512z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="5dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="5dp">
<path android:fillColor="#d81e06" android:pathData="M256,512c0,70.7 25,131.1 75,181A246.6,246.6 0,0 0,512 768c70.7,0 131,-25 181,-75C743,643.1 768,582.7 768,512c0,-70.7 -25,-131.1 -75,-181A246.6,246.6 0,0 0,512 256c-70.7,0 -131,25 -181,75C281,380.9 256,441.3 256,512z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="5dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="5dp">
<path android:fillColor="#f4ea2a" android:pathData="M256,512c0,70.7 25,131.1 75,181A246.6,246.6 0,0 0,512 768c70.7,0 131,-25 181,-75C743,643.1 768,582.7 768,512c0,-70.7 -25,-131.1 -75,-181A246.6,246.6 0,0 0,512 256c-70.7,0 -131,25 -181,75C281,380.9 256,441.3 256,512z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="62.5dp" android:tint="#FFD700" android:viewportHeight="24" android:viewportWidth="24" android:width="62.5dp">
<path android:fillColor="@android:color/white" android:pathData="M7,3H4v3H2V1h5V3zM22,6V1h-5v2h3v3H22zM7,21H4v-3H2v5h5V21zM20,18v3h-3v2h5v-5H20zM19,18c0,1.1 -0.9,2 -2,2H7c-1.1,0 -2,-0.9 -2,-2V6c0,-1.1 0.9,-2 2,-2h10c1.1,0 2,0.9 2,2V18zM15,8H9v2h6V8zM15,11H9v2h6V11zM15,14H9v2h6V14z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#FF00FF" android:viewportHeight="24" android:viewportWidth="24" android:width="40dp">
<path android:fillColor="@color/white" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:viewportHeight="512" android:viewportWidth="512" android:width="40dp">
<path android:fillColor="#94bd31" android:fillType="nonZero" android:pathData="m512,256c0,141.38 -114.62,256 -256,256 -141.38,0 -256,-114.62 -256,-256 0,-141.38 114.62,-256 256,-256 141.38,0 256,114.62 256,256z" android:strokeColor="#00000000"/>
<path android:fillColor="#ffffff" android:pathData="m348.11,339.48l0,-116.67 0,-13.99l0,0l0,-15.82l0.04,0 -184.08,0 -0.18,0l0,16.24 0,13.57 0,116.67c0,11.66 8.21,20.74 19.87,20.74l15.28,0c-0.53,1.42 0.28,4.03 0.28,5.95l0,1.13 0,6.67 0,34.5c0,11.05 8.08,20.03 19.13,20.03 11.05,0 19.13,-8.98 19.13,-20.03l0,-34.5 0,-6.67 0,-1.13c0,-1.91 0.43,-4.54 -0.08,-5.95l37.21,0c-0.53,1.42 -0.28,4.03 -0.28,5.95l0,1.13 0,6.67 0,34.5c0,11.05 8.08,20.03 19.13,20.03 11.07,0 19.13,-8.98 19.13,-20.03l0,-34.5 0,-6.67 0,-1.13c0,-1.91 0.99,-4.54 0.49,-5.95l15.26,0c11.68,0 19.68,-9.08 19.68,-20.74z"/>
<path android:fillColor="#ffffff" android:pathData="m363.7,212.61l0,85.68c0,11.07 8.07,20.03 19.15,20.03 11.06,0 19.12,-8.96 19.12,-20.03l0,-85.68c0,-11.07 -8.06,-20.03 -19.12,-20.03 -11.07,0 -19.15,8.96 -19.15,20.03z"/>
<path android:fillColor="#ffffff" android:pathData="m129.16,318.32c11.06,0 19.14,-8.96 19.14,-20.03l0,-85.68c0,-11.07 -8.08,-20.03 -19.14,-20.03 -11.06,0 -19.12,8.96 -19.12,20.03l0,85.68c0,11.07 8.06,20.03 19.12,20.03z"/>
<path android:fillColor="#ffffff" android:pathData="m309.54,101.18 l8.32,-12.49c1.03,-1.54 0.6,-3.61 -0.91,-4.63 -1.55,-1.03 -3.62,-0.61 -4.64,0.93l-8.93,13.4 -3.75,5.63 -3.81,5.71c-12.04,-4.68 -25.52,-7.3 -39.73,-7.3 -14.18,0 -27.64,2.62 -39.71,7.3l-3.81,-5.71 -3.73,-5.63 -8.93,-13.4c-1.02,-1.54 -3.1,-1.94 -4.64,-0.93 -1.54,1.01 -1.94,3.09 -0.93,4.63l8.32,12.49 3.73,5.57 3.75,5.46c-28.34,13.19 -47.51,38.28 -47.51,66.62l186.95,0c0,-28.34 -19.17,-53.44 -47.52,-66.62l3.77,-5.56 3.71,-5.47zM216.05,152.51c-5.53,0 -10.02,-4.49 -10.02,-10.02 0,-5.53 4.49,-10.02 10.02,-10.02 5.53,0 10.02,4.49 10.02,10.02 0,5.53 -4.49,10.02 -10.02,10.02zM306.19,142.49c0,5.53 -4.49,10.02 -10.02,10.02 -5.53,0 -10.02,-4.49 -10.02,-10.02 0,-5.53 4.49,-10.02 10.02,-10.02 5.53,0 10.02,4.49 10.02,10.02z"/>
</vector>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="M0,0h108v108h-108z"/>
</vector>

View file

@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1026"
android:viewportHeight="1024">
<group android:scaleY="0.998004"
android:translateY="1.0219561">
<path
android:pathData="M853.3,874.7H128c-46.9,0 -85.3,-38.4 -85.3,-85.3V234.7c0,-46.9 38.4,-85.3 85.3,-85.3l337.1,-2.1L597.3,42.7h256c46.9,0 85.3,38.4 85.3,85.3v661.3c0,46.9 -38.4,85.3 -85.3,85.3z"
android:fillColor="#FFFFFF"/>
<path
android:fillColor="#FF000000"
android:pathData="M853.3,896H128c-59.7,0 -106.7,-46.9 -106.7,-106.7V234.7c0,-59.7 46.9,-106.7 106.7,-106.7l330.7,-2.1L588.8,21.3H853.3c59.7,0 106.7,46.9 106.7,106.7v661.3c0,57.6 -49.1,106.7 -106.7,106.7zM603.7,64l-132.3,104.5L128,170.7c-36.3,0 -64,27.7 -64,64v554.7c0,36.3 27.7,64 64,64h725.3c36.3,0 64,-27.7 64,-64V128c0,-36.3 -27.7,-64 -64,-64H603.7z"/>
<path
android:pathData="M213.3,234.7h554.7c46.9,0 85.3,38.4 85.3,85.3v469.3H128V320c0,-49.1 38.4,-85.3 85.3,-85.3z"
android:fillColor="#FFBF80"/>
<path
android:fillColor="#FF000000"
android:pathData="M874.7,810.7H106.7V320c0,-59.7 46.9,-106.7 106.7,-106.7h554.7c59.7,0 106.7,46.9 106.7,106.7v490.7zM149.3,768h682.7V320c0,-36.3 -27.7,-64 -64,-64H213.3c-36.3,0 -64,27.7 -64,64v448z"/>
<path
android:pathData="M42.7,320h896v512c0,46.9 -38.4,85.3 -85.3,85.3H128c-46.9,0 -85.3,-38.4 -85.3,-85.3V320z"
android:fillColor="#3399FF"/>
<path
android:fillColor="#FF000000"
android:pathData="M853.3,938.7H128c-59.7,0 -106.7,-46.9 -106.7,-106.7V298.7h938.7v533.3c0,57.6 -49.1,106.7 -106.7,106.7zM64,341.3v490.7c0,36.3 27.7,64 64,64h725.3c36.3,0 64,-27.7 64,-64V341.3H64z"/>
<path
android:pathData="M789.3,979.2l-215.5,-117.3v-234.7L789.3,512l213.3,117.3v234.7l-213.3,115.2zM789.3,661.3c-46.9,0 -85.3,38.4 -85.3,85.3s38.4,85.3 85.3,85.3 85.3,-38.4 85.3,-85.3 -38.4,-85.3 -85.3,-85.3z"
android:fillColor="#FFFFFF"/>
<path
android:fillColor="#FF000000"
android:pathData="M789.3,1002.7l-236.8,-128L552.5,616.5l236.8,-128 236.8,128L1026.1,874.7L789.3,1002.7zM595.2,849.1l194.1,104.5 194.1,-104.5L983.5,640L789.3,535.5 595.2,640v209.1zM789.3,853.3c-59.7,0 -106.7,-46.9 -106.7,-106.7s46.9,-106.7 106.7,-106.7 106.7,46.9 106.7,106.7 -49.1,106.7 -106.7,106.7zM789.3,682.7c-36.3,0 -64,27.7 -64,64s27.7,64 64,64 64,-27.7 64,-64 -29.9,-64 -64,-64z"/>
</group>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="40dp" android:tint="#000000" android:viewportHeight="23" android:viewportWidth="25" android:width="40dp">
<path
android:fillColor="#000000"
android:strokeColor="@android:color/white"
android:strokeWidth="0.005"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="62.5dp"
android:tint="#BA55D3"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="62.5dp">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z" />
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="10dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="10dp">
<path android:fillColor="#E9EBED" android:pathData="M870.6,240.8L467.2,240.8c0,-49.5 -40.1,-89.6 -89.6,-89.6L153.5,151.1c-49.5,0 -89.7,40.1 -89.7,89.6v627.5c0,49.5 40.1,89.7 89.7,89.7h717.2c49.5,0 89.6,-40.1 89.6,-89.7L960.3,330.4c-0,-49.5 -40.1,-89.7 -89.7,-89.7zM870.6,823.5c0,24.8 -20.1,44.8 -44.8,44.8L198.3,868.3c-24.8,0 -44.8,-20.1 -44.8,-44.8L153.5,509.7h717.2v313.8zM825.8,420.1L153.5,420.1v-179.3c0.2,0.2 20.1,0 44.8,0h134.5c24.8,0 44.8,0.1 44.8,0v89.7L825.8,330.4c24.8,0 44.8,20.1 44.8,44.8 -0,24.8 -20.1,44.8 -44.8,44.8z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="62.5dp" android:tint="#1E90FF" android:viewportHeight="24" android:viewportWidth="24" android:width="62.5dp">
<path android:fillColor="#FFD700" android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="70dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="70dp">
<path android:fillColor="@android:color/white" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="40dp">
<path android:fillColor="@android:color/white" android:pathData="M21,6h-7.59l3.29,-3.29L16,2l-4,4 -4,-4 -0.71,0.71L10.59,6L3,6c-1.1,0 -2,0.89 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.11 -0.9,-2 -2,-2zM21,20L3,20L3,8h18v12zM9,10v8l7,-4z"/>
</vector>

View file

@ -0,0 +1,7 @@
<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">
<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"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#8B4513" android:viewportHeight="24" android:viewportWidth="24" android:width="40dp">
<path android:fillColor="@android:color/white" android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM12,8h-2L10,4h2v4zM15,8h-2L13,4h2v4zM18,8h-2L16,4h2v4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="62.5dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="62.5dp">
<path android:fillColor="@android:color/white" android:pathData="M21,3L3,3c-1.11,0 -2,0.89 -2,2v12c0,1.1 0.89,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.11 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12zM16,11l-7,4L9,7z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="45dp" android:tint="#CD2990" android:viewportHeight="24" android:viewportWidth="24" android:width="45dp">
<path android:fillColor="@android:color/white" android:pathData="M15,6H3v2h12V6zM15,10H3v2h12V10zM3,16h8v-2H3V16zM17,6v8.18C16.69,14.07 16.35,14 16,14c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3V8h3V6H17z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="32dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="32dp">
<path android:fillColor="@android:color/white" android:pathData="M440,800L440,313L216,537L160,480L480,160L800,480L744,537L520,313L520,800L440,800Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="32dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="32dp">
<path android:fillColor="@android:color/white" android:pathData="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="32dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="32dp">
<path android:fillColor="@android:color/white" android:pathData="M480,280Q447,280 423.5,256.5Q400,233 400,200Q400,167 423.5,143.5Q447,120 480,120Q513,120 536.5,143.5Q560,167 560,200Q560,233 536.5,256.5Q513,280 480,280ZM420,840L420,360L540,360L540,840L420,840Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="40dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="40dp">
<path android:fillColor="@android:color/white" android:pathData="M360,480Q343,480 331.5,468.5Q320,457 320,440Q320,423 331.5,411.5Q343,400 360,400L600,400Q617,400 628.5,411.5Q640,423 640,440Q640,457 628.5,468.5Q617,480 600,480L360,480ZM360,320Q343,320 331.5,308.5Q320,297 320,280Q320,263 331.5,251.5Q343,240 360,240L600,240Q617,240 628.5,251.5Q640,263 640,280Q640,297 628.5,308.5Q617,320 600,320L360,320ZM240,560L540,560Q569,560 594,572.5Q619,585 636,608L720,718L720,160Q720,160 720,160Q720,160 720,160L240,160Q240,160 240,160Q240,160 240,160L240,560ZM240,800L682,800L573,657Q567,649 558.5,644.5Q550,640 540,640L240,640L240,800Q240,800 240,800Q240,800 240,800ZM720,880L240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,800Q800,833 776.5,856.5Q753,880 720,880ZM240,800L240,800L240,160Q240,160 240,160Q240,160 240,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM240,640L240,560L240,560L240,640Z"/>
</vector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<stroke
android:width="2dp"
android:color="@android:color/black" />
<corners android:radius="8dp" />
</shape>

View file

@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/> <!-- 按钮背景色 -->
<stroke
android:width="2dp"
android:color="@color/white"/>
<corners android:radius="30dp"/>
</shape>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="32dp"
android:tint="#000000"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="32dp">
<path
android:fillColor="@android:color/white"
android:pathData="M430,760Q468,760 494,734Q520,708 520,670L520,520L640,520L640,440L480,440L480,595Q469,587 456.5,583.5Q444,580 430,580Q392,580 366,606Q340,632 340,670Q340,708 366,734Q392,760 430,760ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L520,360ZM240,160L240,160L240,360L240,360L240,160L240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="32dp"
android:tint="#000000"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="32dp">
<path
android:fillColor="@android:color/white"
android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L400,160L480,240L800,240Q833,240 856.5,263.5Q880,287 880,320L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320Q800,320 800,320Q800,320 800,320L447,320L367,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720ZM160,720Q160,720 160,720Q160,720 160,720L160,240Q160,240 160,240Q160,240 160,240L160,240L160,320L160,320Q160,320 160,320Q160,320 160,320L160,720Q160,720 160,720Q160,720 160,720Z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="32dp"
android:tint="#000000"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="32dp">
<path
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L520,360Z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="32dp"
android:tint="#000000"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="32dp">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM240,680L720,680L570,480L450,640L360,520L240,680ZM200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760Z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="32dp"
android:tint="#000000"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="32dp">
<path
android:fillColor="@android:color/white"
android:pathData="M360,720L520,720Q537,720 548.5,708.5Q560,697 560,680L560,640L640,682L640,518L560,560L560,520Q560,503 548.5,491.5Q537,480 520,480L360,480Q343,480 331.5,491.5Q320,503 320,520L320,680Q320,697 331.5,708.5Q343,720 360,720ZM240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L560,80L800,320L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM520,360L520,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800L720,800Q720,800 720,800Q720,800 720,800L720,360L520,360ZM240,160L240,160L240,360L240,360L240,160L240,360L240,360L240,800Q240,800 240,800Q240,800 240,800L240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160Z" />
</vector>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/iconButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="80sp"
android:background="@drawable/bg_roundcorner"
android:textColor="@color/black"
android:textSize="30sp"
android:textAlignment="center"
android:gravity="center"
app:drawableStartCompat="@drawable/ic_document"
android:paddingHorizontal="20dp"
/>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingHorizontal="5dp"
android:id="@+id/main">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/DocumentPageTopBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingStart="5dp"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/leftArrowImageView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:contentDescription="left_arrow"
android:src="@drawable/ic_left_arrow" />
<TextView
android:id="@+id/titleDescription"
app:layout_constraintStart_toEndOf="@+id/leftArrowImageView"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/document"
android:textColor="@color/black"
android:textSize="30sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/sortDocumentView"
app:layout_constraintStart_toEndOf="@+id/titleDescription"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="0dp"
android:contentDescription="arrow_drop_down"
android:src="@drawable/ic_arrow_drop_down" />
<ImageView
android:id="@+id/refreshData"
app:layout_constraintEnd_toStartOf="@+id/searchDocumentView"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="20dp"
android:contentDescription="refresh"
android:src="@drawable/baseline_refresh_24" />
<ImageView
android:id="@+id/searchDocumentView"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="search"
android:src="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<GridView
android:id="@+id/DocumentGrid"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingHorizontal="10dp"
android:paddingVertical="15dp"
app:layout_constraintTop_toBottomOf="@+id/DocumentPageTopBar"
app:layout_constraintBottom_toBottomOf="parent"
android:horizontalSpacing="8dp"
android:verticalSpacing="10dp"
android:numColumns="1"
android:scrollbars="vertical"
android:clipToPadding="false"
android:scrollbarSize="0dp"
/>
<TextView
android:id="@+id/LoadingBlankText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAlignment="center"
android:gravity="center"
android:text="@string/loading"
android:textSize="78sp"
android:background="@color/WhiteSmoke"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/leftArrowImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="left_arrow"
android:layout_marginTop="45dp"
android:src="@drawable/ic_left_arrow" />
<SearchView
android:id="@+id/searchDocument"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginStart="20dp"
android:background="@drawable/search_border"
android:iconifiedByDefault="false"
android:padding="10dp"
android:queryHint="搜索文件"
android:textColor="@color/black" />
</LinearLayout>

View file

@ -0,0 +1,347 @@
<?xml version="1.0" encoding="utf-8"?><!--本页面为主页面-->
<!--该页面顶部的搜索文件的功能没有实现,如果没有搜索到,就显示没有文件,已经提供相关的图标:@drawable/ic_no_document-->
<!--图片的具体情况见picture_page.xml-->
<!--视频的具体情况见video_page.xml-->
<!--音乐的具体情况见music_page.xml-->
<!--文件的具体情况见document_page.xml-->
<!--应用的具体情况见app_page.xml-->
<!--内部存储的具体情况见store_page.xml-->
<!--最近删除的具体情况见delete_page.xml-->
<!--下载与接收的具体情况见download_page.xml-->
<!--浏览器的具体情况见internet_page.xml-->
<!--录音机的具体情况见radio_page.xml-->
<!--安装包的具体情况见install_package_page.xml-->
<!--压缩包的具体情况见store_package_page.xml-->
<!--通话与信息的具体情况见phone_information_page.xml-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/WhiteSmoke"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="10dp"
android:layout_marginVertical="5dp"
android:orientation="vertical"
>
<!--设置”浏览“-->
<HorizontalScrollView
android:id="@+id/MainPageTypeSelector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:scrollbars="none"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/searchDocument"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:orientation="horizontal"
android:padding="10dp"
>
<!--设置“图片”按钮-->
<Button
android:id="@+id/MainPagePictureButton"
android:layout_width="101dp"
android:layout_height="108dp"
android:layout_gravity="start"
android:layout_marginEnd="5dp"
android:backgroundTint="@color/design_default_color_background"
android:drawableTop="@drawable/ic_photo"
android:gravity="center"
android:text="@string/picture"
android:textSize="11sp"
android:textColor="@color/black"
/>
<!--设置“视频”按钮-->
<Button
android:id="@+id/MainPageMusicButton"
android:layout_width="101dp"
android:layout_height="108dp"
android:layout_gravity="start"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:backgroundTint="@color/design_default_color_background"
android:drawableTop="@drawable/ic_music"
android:gravity="center"
android:text="@string/music"
android:textColor="@color/black"
android:textSize="11sp"
/>
<Button
android:id="@+id/MainPageVideoButton"
android:layout_width="101dp"
android:layout_height="108dp"
android:layout_gravity="start"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:backgroundTint="@color/design_default_color_background"
android:drawableTop="@drawable/ic_video"
android:gravity="center"
android:text="@string/video"
android:textSize="11sp"
android:textColor="@color/black"
/>
<!--设置“音乐”按钮-->
<!--设置“文档”按钮-->
<Button
android:id="@+id/MainPageDocumentButton"
android:layout_width="101dp"
android:layout_height="108dp"
android:layout_gravity="start"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:backgroundTint="@color/design_default_color_background"
android:drawableTop="@drawable/ic_document"
android:gravity="center"
android:text="@string/document"
android:textSize="11sp"
android:textColor="@color/black"
/>
<!-- &lt;!&ndash;设置“应用”按钮&ndash;&gt;-->
<!-- <Button-->
<!-- android:id="@+id/MainPageAppButton"-->
<!-- android:layout_width="101dp"-->
<!-- android:layout_height="108dp"-->
<!-- android:layout_gravity="start"-->
<!-- android:layout_marginEnd="5dp"-->
<!-- android:layout_marginStart="5dp"-->
<!-- android:backgroundTint="@color/design_default_color_background"-->
<!-- android:drawableTop="@drawable/ic_app"-->
<!-- android:gravity="center"-->
<!-- android:text="@string/app"-->
<!-- android:textSize="11sp"-->
<!-- android:textColor="@color/black"-->
<!-- />-->
</LinearLayout>
</HorizontalScrollView>
<!--设置搜索框-->
<LinearLayout
android:id="@+id/MainPageTitleRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search"
android:textColor="@color/black"
android:textSize="35sp"
android:textStyle="bold"
/>
<!-- &lt;!&ndash;设置”编辑“&ndash;&gt;-->
<!-- <Button-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="200dp"-->
<!-- android:backgroundTint="@color/design_default_color_background"-->
<!-- android:text="@string/edit"-->
<!-- android:textColor="@color/black"-->
<!-- android:textSize="20sp"-->
<!-- android:textStyle="bold" />-->
</LinearLayout>
<!--设置水平滚动-->
<LinearLayout
android:id="@+id/MainPageLocationRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/MainPageTypeSelector"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="20dp"
android:text="@string/advanced"
android:textSize="20sp"
/>
<Button
android:id="@+id/MainPageAllFilesButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/baseline_storage_24"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/all_files"
android:textColor="@color/black"
android:textSize="20sp"
/>
<!--设置“内部存储”按钮-->
<Button
android:id="@+id/MainPageStorageButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/ic_storage"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/storage_info"
android:textColor="@color/black"
android:textSize="20sp"
/>
<Button
android:id="@+id/MainPageSettingsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/baseline_settings_24"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/settings"
android:textColor="@color/black"
android:textSize="20sp"
/>
<!--设置“来源”的文本-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="20dp"
android:text="@string/source"
android:textSize="20sp"
/>
</LinearLayout>
<!--设置垂直滚动视图-->
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/WhiteSmoke"
android:scrollbars="none"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/MainPageLocationRow"
app:layout_constraintBottom_toBottomOf="parent"
android:fillViewport="true"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
>
<!--设置“下载与接收”按钮-->
<Button
android:id="@+id/MainPageDownloadButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/ic_download"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/download"
android:textColor="@color/black"
android:textSize="20sp"
/>
<!--设置“录音机”按钮-->
<Button
android:id="@+id/MainPageRecordingButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/ic_radio"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/radio"
android:textColor="@color/black"
android:textSize="20sp"
/>
<Button
android:id="@+id/MainPageDCIMButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/baseline_camera_alt_24"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/camera"
android:textColor="@color/black"
android:textSize="20sp"
/>
<Button
android:id="@+id/MainPagePicturesButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/baseline_photo_library_24"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/pictures"
android:textColor="@color/black"
android:textSize="20sp"
/>
<Button
android:id="@+id/MainPageDocumentsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/white"
android:drawableLeft="@drawable/rounded_lab_profile_24"
android:gravity="center"
android:paddingEnd="40dp"
android:paddingStart="40dp"
android:text="@string/documents"
android:textColor="@color/black"
android:textSize="20sp"
/>
</LinearLayout>
</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"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/iconButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="60sp"
android:background="@drawable/bg_roundcorner"
android:textColor="@color/black"
android:textSize="30sp"
android:textAlignment="center"
android:gravity="center"
app:drawableStartCompat="@drawable/music_icon"
android:paddingHorizontal="20dp"
/>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingHorizontal="5dp"
android:id="@+id/main">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/MusicPageTopBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingStart="5dp"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/leftArrowImageView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:contentDescription="left_arrow"
android:src="@drawable/ic_left_arrow" />
<TextView
android:id="@+id/titleDescription"
app:layout_constraintStart_toEndOf="@+id/leftArrowImageView"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/music"
android:textColor="@color/black"
android:textSize="30sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/sortMusicView"
app:layout_constraintStart_toEndOf="@+id/titleDescription"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="0dp"
android:contentDescription="arrow_drop_down"
android:src="@drawable/ic_arrow_drop_down" />
<ImageView
android:id="@+id/refreshData"
app:layout_constraintEnd_toStartOf="@+id/searchMusicView"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="20dp"
android:contentDescription="refresh"
android:src="@drawable/baseline_refresh_24" />
<ImageView
android:id="@+id/searchMusicView"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="search"
android:src="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<GridView
android:id="@+id/MusicGrid"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingHorizontal="10dp"
android:paddingVertical="15dp"
app:layout_constraintTop_toBottomOf="@+id/MusicPageTopBar"
app:layout_constraintBottom_toBottomOf="parent"
android:horizontalSpacing="8dp"
android:verticalSpacing="10dp"
android:numColumns="1"
android:scrollbars="vertical"
android:clipToPadding="false"
android:scrollbarSize="0dp"
/>
<TextView
android:id="@+id/LoadingBlankText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAlignment="center"
android:gravity="center"
android:text="@string/loading"
android:textSize="78sp"
android:background="@color/WhiteSmoke"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/leftArrowImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="left_arrow"
android:layout_marginTop="45dp"
android:src="@drawable/ic_left_arrow" />
<SearchView
android:id="@+id/searchMusic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginStart="20dp"
android:background="@drawable/search_border"
android:iconifiedByDefault="false"
android:padding="10dp"
android:queryHint="搜索音乐"
android:textColor="@color/black" />
</LinearLayout>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/pictureCardImage"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
<TextView
android:id="@+id/pictureCardText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:lines="2"
android:ellipsize="end"
/>
</LinearLayout>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?><!--本页面为图片页面-->
<!--长按图片,只是提供了复制,删除,剪切,粘贴的按键,并未实现功能,按键的设计在menu下的function.xml文件中,相关的java文件见picture_page.java。且只是固定提供了9张图片不能动态增加图片所以粘贴功能没有实现-->
<!--要能同时删除多个图片-->
<!--点击下箭头,是“以时间排序(默认)”和“以大小排序”也没有实现功能具体设计在picture_page.java的showSortOption函数里面-->
<!--点击搜索图标是搜索图片功能未实现搜索图片的功能具体设计在picture_page_search的xml和java文件中,如果没有搜索到,就显示没有文件,已经提供相关的图标:@drawable/ic_no_document-->
<!--单击图片能够显示图片但不能左右滑动到下一张图片要按exc键才能回到缩略图而不是点击图片的任意位置就能回到缩略图,且单击之后不能放大缩小图片-->
<!--picture_page.xml是主页面点击“音乐”之后进入的页面-->
<!--picture_page_search.xml是picture_page.xml页面右上角的搜索页面-->
<!--picture_full.xml是在picture_page.xml页面点击某张图片的缩略图后放大图片的页面-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingHorizontal="5dp"
android:id="@+id/main">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/PicturePageTopBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingStart="5dp"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/leftArrowImageView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:contentDescription="left_arrow"
android:src="@drawable/ic_left_arrow" />
<TextView
android:id="@+id/titleDescription"
app:layout_constraintStart_toEndOf="@+id/leftArrowImageView"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/picture"
android:textColor="@color/black"
android:textSize="30sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/sortImageView"
app:layout_constraintStart_toEndOf="@+id/titleDescription"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="0dp"
android:contentDescription="arrow_drop_down"
android:src="@drawable/ic_arrow_drop_down" />
<ImageView
android:id="@+id/refreshData"
app:layout_constraintEnd_toStartOf="@+id/searchImageView"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="20dp"
android:contentDescription="refresh"
android:src="@drawable/baseline_refresh_24" />
<ImageView
android:id="@+id/searchImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="search"
android:src="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<GridView
android:id="@+id/PicturePageGrid"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingHorizontal="10dp"
android:paddingVertical="20dp"
app:layout_constraintTop_toBottomOf="@+id/PicturePageTopBar"
app:layout_constraintBottom_toBottomOf="parent"
android:horizontalSpacing="8dp"
android:verticalSpacing="10dp"
android:numColumns="3"
android:scrollbars="vertical"
android:clipToPadding="false"
android:scrollbarSize="0dp"
/>
<TextView
android:id="@+id/LoadingBlankText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAlignment="center"
android:gravity="center"
android:text="@string/loading"
android:textSize="78sp"
android:background="@color/WhiteSmoke"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Some files were not shown because too many files have changed in this diff Show more