From c5556c7000e4eecaab6c8621616c170cada488c4 Mon Sep 17 00:00:00 2001 From: 1 <386029724@qq.com> Date: Mon, 21 Jul 2025 12:59:29 +0800 Subject: [PATCH] +js uploader --- .../donut/mixfile/server/CustomUploader.kt | 57 ++- .../com/donut/mixfile/server/FileService.kt | 2 - .../mixfile/server/JavaScriptUploader.kt | 39 ++ .../main/java/com/donut/mixfile/ui/nav/Nav.kt | 2 +- .../java/com/donut/mixfile/ui/nav/NavGraph.kt | 2 +- .../com/donut/mixfile/ui/routes/Settings.kt | 435 ------------------ .../mixfile/ui/routes/settings/Settings.kt | 200 ++++++++ .../donut/mixfile/ui/routes/settings/Utils.kt | 210 +++++++++ .../donut/mixfile/util/file/FileDataLog.kt | 2 +- .../com/donut/mixfile/util/file/FileDialog.kt | 4 +- gradle/libs.versions.toml | 2 +- 11 files changed, 508 insertions(+), 447 deletions(-) create mode 100644 app/src/main/java/com/donut/mixfile/server/JavaScriptUploader.kt delete mode 100644 app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt create mode 100644 app/src/main/java/com/donut/mixfile/ui/routes/settings/Settings.kt create mode 100644 app/src/main/java/com/donut/mixfile/ui/routes/settings/Utils.kt diff --git a/app/src/main/java/com/donut/mixfile/server/CustomUploader.kt b/app/src/main/java/com/donut/mixfile/server/CustomUploader.kt index 0a1ddba..b99d8bd 100644 --- a/app/src/main/java/com/donut/mixfile/server/CustomUploader.kt +++ b/app/src/main/java/com/donut/mixfile/server/CustomUploader.kt @@ -1,9 +1,15 @@ package com.donut.mixfile.server +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import com.donut.mixfile.server.core.Uploader import com.donut.mixfile.server.core.uploaders.A1Uploader import com.donut.mixfile.server.core.uploaders.A2Uploader import com.donut.mixfile.server.core.uploaders.A3Uploader +import com.donut.mixfile.ui.component.common.MixDialogBuilder import com.donut.mixfile.util.cachedMutableOf import io.ktor.client.HttpClient import io.ktor.client.request.get @@ -20,14 +26,15 @@ var CUSTOM_UPLOAD_URL by cachedMutableOf("", "CUSTOM_UPLOAD_URL") var CUSTOM_REFERER by cachedMutableOf("", "CUSTOM_REFERER") -val UPLOADERS = listOf(A1Uploader, A2Uploader, A3Uploader, CustomUploader) +// get()保证刷新js线路实例 +val UPLOADERS get() = listOf(A1Uploader, A2Uploader, A3Uploader, CustomUploader, JavaScriptUploader) val DEFAULT_UPLOADER = A2Uploader var currentUploader by cachedMutableOf(DEFAULT_UPLOADER.name, "current_uploader") -fun getCurrentUploader() = - UPLOADERS.firstOrNull { it.name.contentEquals(currentUploader) } ?: A1Uploader +fun getCurrentUploader(uploaders: List = UPLOADERS) = + uploaders.firstOrNull { it.name.contentEquals(currentUploader) } ?: A1Uploader object CustomUploader : Uploader("自定义") { @@ -47,7 +54,7 @@ object CustomUploader : Uploader("自定义") { override val referer: String get() = CUSTOM_REFERER - override suspend fun doUpload(fileData: ByteArray, client: HttpClient): String { + override suspend fun doUpload(fileData: ByteArray, client: HttpClient, headSize: Int): String { val response = client.put { url(CUSTOM_UPLOAD_URL) setBody(fileData) @@ -59,4 +66,46 @@ object CustomUploader : Uploader("自定义") { return resText } +} + +fun openCustomUploaderWindow() { + MixDialogBuilder("自定义线路设置").apply { + setContent { + OutlinedTextField( + value = CUSTOM_UPLOAD_URL, + onValueChange = { + CUSTOM_UPLOAD_URL = it.trim() + }, + maxLines = 1, + label = { + Text(text = "请求地址") + }, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = CUSTOM_REFERER, + maxLines = 1, + onValueChange = { + CUSTOM_REFERER = it.trim() + }, + label = { + Text(text = "referer") + }, + modifier = Modifier.fillMaxWidth() + ) + Text( + color = Color.Gray, + modifier = Modifier.fillMaxWidth(), + text = """ + 自定义线路请自行实现,app会使用PUT方式发送请求 + 请求体为图片二进制,成功请返回200状态码,内容直接返回URL + 失败返回403或500(会重试)状态码 + 另外需要实现GET方法返回填充图片,推荐gif格式 + 图片尺寸不宜过大,否则影响上传速度 + """.trimIndent(), + ) + } + setDefaultNegative("关闭") + show() + } } \ No newline at end of file diff --git a/app/src/main/java/com/donut/mixfile/server/FileService.kt b/app/src/main/java/com/donut/mixfile/server/FileService.kt index 7ac9cab..b4cca69 100644 --- a/app/src/main/java/com/donut/mixfile/server/FileService.kt +++ b/app/src/main/java/com/donut/mixfile/server/FileService.kt @@ -18,8 +18,6 @@ import com.donut.mixfile.kv import com.donut.mixfile.server.core.MixFileServer import com.donut.mixfile.server.core.Uploader import com.donut.mixfile.server.core.routes.api.webdav.objects.WebDavManager -import com.donut.mixfile.server.core.uploaders.js.JSUploader -import com.donut.mixfile.server.core.uploaders.js.runScript import com.donut.mixfile.server.core.utils.MixUploadTask import com.donut.mixfile.server.core.utils.extensions.kb import com.donut.mixfile.server.core.utils.ignoreError diff --git a/app/src/main/java/com/donut/mixfile/server/JavaScriptUploader.kt b/app/src/main/java/com/donut/mixfile/server/JavaScriptUploader.kt new file mode 100644 index 0000000..689e9cb --- /dev/null +++ b/app/src/main/java/com/donut/mixfile/server/JavaScriptUploader.kt @@ -0,0 +1,39 @@ +package com.donut.mixfile.server + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.ui.Modifier +import com.donut.mixfile.server.core.uploaders.js.JSUploader +import com.donut.mixfile.ui.component.common.MixDialogBuilder +import com.donut.mixfile.util.cachedMutableOf + + +var JAVASCRIPT_UPLOADER_CODE by cachedMutableOf("", "JAVASCRIPT_UPLOADER_CODE") + +val JavaScriptUploader + get() = JSUploader( + "JS自定义线路", + scriptCode = JAVASCRIPT_UPLOADER_CODE + ) + + +fun openJavaScriptUploaderWindow() { + MixDialogBuilder("JS自定义线路设置").apply { + setContent { + OutlinedTextField( + value = JAVASCRIPT_UPLOADER_CODE, + onValueChange = { + JAVASCRIPT_UPLOADER_CODE = it + }, + minLines = 10, + label = { + Text(text = "JavaScript代码") + }, + modifier = Modifier.fillMaxWidth() + ) + } + setDefaultNegative("关闭") + show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/donut/mixfile/ui/nav/Nav.kt b/app/src/main/java/com/donut/mixfile/ui/nav/Nav.kt index 1362cc2..a55e686 100644 --- a/app/src/main/java/com/donut/mixfile/ui/nav/Nav.kt +++ b/app/src/main/java/com/donut/mixfile/ui/nav/Nav.kt @@ -31,9 +31,9 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.donut.mixfile.ui.routes.About -import com.donut.mixfile.ui.routes.MixSettings import com.donut.mixfile.ui.routes.favorites.Favorites import com.donut.mixfile.ui.routes.home.Home +import com.donut.mixfile.ui.routes.settings.MixSettings import com.donut.mixfile.ui.routes.webdav.WebDAV import com.donut.mixfile.util.OnDispose import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/donut/mixfile/ui/nav/NavGraph.kt b/app/src/main/java/com/donut/mixfile/ui/nav/NavGraph.kt index 886d58d..3a60d40 100644 --- a/app/src/main/java/com/donut/mixfile/ui/nav/NavGraph.kt +++ b/app/src/main/java/com/donut/mixfile/ui/nav/NavGraph.kt @@ -12,9 +12,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost import com.donut.mixfile.ui.routes.About -import com.donut.mixfile.ui.routes.MixSettings import com.donut.mixfile.ui.routes.favorites.Favorites import com.donut.mixfile.ui.routes.home.Home +import com.donut.mixfile.ui.routes.settings.MixSettings import com.donut.mixfile.ui.routes.webdav.WebDAV @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt b/app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt deleted file mode 100644 index 2df5f03..0000000 --- a/app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt +++ /dev/null @@ -1,435 +0,0 @@ -package com.donut.mixfile.ui.routes - -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.os.PowerManager -import android.provider.Settings -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandIn -import androidx.compose.animation.shrinkOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutHorizontally -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ElevatedButton -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Slider -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf -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.graphics.Color -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.net.toUri -import com.donut.mixfile.app -import com.donut.mixfile.server.CUSTOM_REFERER -import com.donut.mixfile.server.CUSTOM_UPLOAD_URL -import com.donut.mixfile.server.CustomUploader -import com.donut.mixfile.server.DEFAULT_UPLOADER -import com.donut.mixfile.server.DOWNLOAD_TASK_COUNT -import com.donut.mixfile.server.MIXFILE_CHUNK_SIZE -import com.donut.mixfile.server.SERVER_PASSWORD -import com.donut.mixfile.server.UPLOADERS -import com.donut.mixfile.server.UPLOAD_RETRY_COUNT -import com.donut.mixfile.server.UPLOAD_TASK_COUNT -import com.donut.mixfile.server.core.Uploader -import com.donut.mixfile.server.currentUploader -import com.donut.mixfile.server.getCurrentUploader -import com.donut.mixfile.ui.component.common.CommonSwitch -import com.donut.mixfile.ui.component.common.MixDialogBuilder -import com.donut.mixfile.ui.component.common.SingleSelectItemList -import com.donut.mixfile.ui.nav.MixNavPage -import com.donut.mixfile.ui.theme.Theme -import com.donut.mixfile.ui.theme.currentTheme -import com.donut.mixfile.ui.theme.enableAutoDarkMode -import com.donut.mixfile.util.TipText -import com.donut.mixfile.util.cachedMutableOf -import com.donut.mixfile.util.file.filePreview -import com.donut.mixfile.util.file.multiUploadTaskCount -import com.donut.mixfile.util.file.uploadLogs -import com.donut.mixfile.util.showToast - - -var useShortCode by cachedMutableOf(true, "use_short_code") -var autoAddFavorite by cachedMutableOf(true, "auto_add_favorite") -var useSystemPlayer by cachedMutableOf(false, "use_system_player") - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun SettingButton( - text: String, - buttonText: String = "设置", - description: String = "", - onClick: () -> Unit -) { - Column(modifier = Modifier.fillMaxWidth()) { - HorizontalDivider() - FlowRow( - modifier = Modifier - .fillMaxWidth() - .padding(0.dp, 5.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - text = text, - modifier = Modifier.align(Alignment.CenterVertically) - ) - OutlinedButton(onClick = onClick) { - Text(text = buttonText) - } - } - if (description.isNotEmpty()) { - Text( - text = description, - modifier = Modifier - .fillMaxWidth(), -// .padding(10.dp, 0.dp), - color = Color(0xFF9E9E9E), - fontSize = 14.sp - ) - } - } -} - -fun isIgnoringBatteryOptimizations(context: Context = app): Boolean { - val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager - return powerManager.isIgnoringBatteryOptimizations(context.packageName) -} - -@SuppressLint("BatteryLife") -fun openBatteryOptimizationSettings(context: Context = app) { - val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { - data = "package:${context.packageName}".toUri() - } - context.startActivity(intent) -} - -@OptIn(ExperimentalMaterial3Api::class) -val MixSettings = MixNavPage( - gap = 10.dp, - horizontalAlignment = Alignment.CenterHorizontally -) { - Column { - Text( - modifier = Modifier.padding(10.dp, 0.dp), - text = "下载并发: $DOWNLOAD_TASK_COUNT", - color = MaterialTheme.colorScheme.primary - ) - Slider( - value = DOWNLOAD_TASK_COUNT.toFloat() / 10f, - steps = 10, - modifier = Modifier.fillMaxWidth(), - onValueChange = { - DOWNLOAD_TASK_COUNT = (it * 10).toLong().coerceAtLeast(1) - } - ) - - } - Column { - Text( - modifier = Modifier.padding(10.dp, 0.dp), - text = "上传并发: $UPLOAD_TASK_COUNT", - color = MaterialTheme.colorScheme.primary - ) - Slider( - value = UPLOAD_TASK_COUNT.toFloat() / 10f, - steps = 10, - modifier = Modifier.fillMaxWidth(), - onValueChange = { - UPLOAD_TASK_COUNT = (it * 10).toLong().coerceAtLeast(1) - } - ) - } - Column { - Text( - modifier = Modifier.padding(10.dp, 0.dp), - text = "批量上传并发: ${multiUploadTaskCount}", - color = MaterialTheme.colorScheme.primary - ) - Slider( - value = multiUploadTaskCount.toFloat() / 10f, - steps = 10, - modifier = Modifier.fillMaxWidth(), - onValueChange = { - multiUploadTaskCount = (it * 10).toLong().coerceAtLeast(1) - } - ) - } - Column { - Text( - modifier = Modifier.padding(10.dp, 0.dp), - text = "上传失败重试次数(单个分片): $UPLOAD_RETRY_COUNT", - color = MaterialTheme.colorScheme.primary - ) - Slider( - value = UPLOAD_RETRY_COUNT.toFloat() / 10f, - steps = 10, - modifier = Modifier.fillMaxWidth(), - onValueChange = { - UPLOAD_RETRY_COUNT = (it * 10).toLong().coerceAtLeast(0) - } - ) - } - TipText( - content = """ - 部分设置可能需要重启后才能生效 - """.trimIndent() - ) - CommonSwitch( - checked = useShortCode, - text = "使用短分享码(空白字符编码信息):" - ) { - useShortCode = it - } - CommonSwitch( - checked = autoAddFavorite, - text = "上传后自动添加文件到收藏:" - ) { - autoAddFavorite = it - } - CommonSwitch( - checked = useSystemPlayer, - text = "使用系统播放器:", - description = "播放视频是否使用系统调用其他播放器" - ) { - useSystemPlayer = it - } - - SettingButton(text = "网页端/WebDAV访问密码") { - setWebPassword() - } - - SettingButton(text = "文件分片大小", description = "默认1024kb,不建议修改") { - setUploadChunkSize() - } - - val uploader = remember(currentUploader) { getCurrentUploader() } - - SettingButton(text = "上传线路: ${uploader.name}") { - selectUploader() - } - uploader.SettingComponent() - SettingButton(text = "文件预览: $filePreview") { - selectFilePreview() - } - SettingButton(text = "颜色主题: ") { - MixDialogBuilder("颜色主题").apply { - setContent { - SingleSelectItemList( - items = Theme.entries, - getLabel = { it.label }, - currentOption = Theme.entries.firstOrNull { - it.name == currentTheme - } ?: Theme.DEFAULT - ) { option -> - currentTheme = option.name - closeDialog() - } - } - show() - } - } - CommonSwitch( - checked = enableAutoDarkMode, - text = "自动深色模式:", - "跟随系统自动切换深色模式", - ) { - enableAutoDarkMode = it - } - HorizontalDivider() - val batteryOptimization by remember { - mutableStateOf(isIgnoringBatteryOptimizations()) - } - if (!batteryOptimization) { - ElevatedButton(onClick = { - openBatteryOptimizationSettings() - }, modifier = Modifier.fillMaxWidth()) { - Text(text = "省电限制未设置!") - } - } - ElevatedButton(onClick = { - MixDialogBuilder("确定清除记录?").apply { - setContent { - Text(text = "确定清除所有上传历史记录?") - } - setPositiveButton("确定") { - closeDialog() - uploadLogs = emptyList() - showToast("清除成功!") - } - show() - } - }, modifier = Modifier.fillMaxWidth()) { - Text(text = "清除上传记录") - } -} - -fun setUploadChunkSize() { - MixDialogBuilder("设置分片大小").apply { - var chunkSize by mutableLongStateOf(MIXFILE_CHUNK_SIZE) - setContent { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - OutlinedTextField( - value = chunkSize.toString(), - onValueChange = { - chunkSize = it.toLongOrNull() ?: 0 - }, - maxLines = 1, - label = { Text(text = "文件分片大小(KB)") }, - modifier = Modifier.fillMaxWidth() - ) - Text( - text = "有上传大文件(>20GB)需求可适量增大,会增加上传失败概率,增加上传下载时占用,降低下载速度,同时并发数量也会根据分片大小自动调整,基于1mb,如果并发为10,分片为2mb,实际并发数量则会为5", - modifier = Modifier - .fillMaxWidth(), -// .padding(10.dp, 0.dp), - color = Color(0xFF9E9E9E), - fontSize = 14.sp - ) - } - } - setDefaultNegative() - setPositiveButton("确定") { - MIXFILE_CHUNK_SIZE = chunkSize.coerceAtLeast(1).coerceAtMost(20480) - showToast("设置成功") - closeDialog() - } - show() - } -} - -fun setWebPassword() { - MixDialogBuilder("设置密码").apply { - var pass by mutableStateOf(SERVER_PASSWORD) - setContent { - OutlinedTextField( - value = pass, - onValueChange = { - pass = it - }, - maxLines = 1, - label = { Text(text = "网页/WEBDAV访问密码(留空禁用密码)") }, - modifier = Modifier.fillMaxWidth() - ) - } - setDefaultNegative() - setPositiveButton("确定") { - SERVER_PASSWORD = pass.trim() - showToast("设置成功") - closeDialog() - } - show() - } -} - -fun selectFilePreview() { - MixDialogBuilder("文件预览").apply { - setContent { - SingleSelectItemList( - items = listOf("开启", "关闭", "仅Wifi"), - currentOption = filePreview - ) { option -> - filePreview = option - closeDialog() - } - } - show() - } -} - -fun openCustomUploaderWindow() { - MixDialogBuilder("自定义线路设置").apply { - setContent { - OutlinedTextField( - value = CUSTOM_UPLOAD_URL, - onValueChange = { - CUSTOM_UPLOAD_URL = it.trim() - }, - maxLines = 1, - label = { - Text(text = "请求地址") - }, - modifier = Modifier.fillMaxWidth() - ) - OutlinedTextField( - value = CUSTOM_REFERER, - maxLines = 1, - onValueChange = { - CUSTOM_REFERER = it.trim() - }, - label = { - Text(text = "referer") - }, - modifier = Modifier.fillMaxWidth() - ) - Text( - color = Color.Gray, - modifier = Modifier.fillMaxWidth(), - text = """ - 自定义线路请自行实现,app会使用PUT方式发送请求 - 请求体为图片二进制,成功请返回200状态码,内容直接返回URL - 失败返回403或500(会重试)状态码 - 另外需要实现GET方法返回填充图片,推荐gif格式 - 图片尺寸不宜过大,否则影响上传速度 - """.trimIndent(), - ) - } - setDefaultNegative("关闭") - show() - } -} - -@Composable -fun Uploader.SettingComponent() { - AnimatedVisibility( - this is CustomUploader, - enter = slideInVertically(), - exit = shrinkOut() - ) { - SettingButton(text = "自定义线路设置: ") { - openCustomUploaderWindow() - } - } -} - -fun selectUploader() { - MixDialogBuilder("上传线路").apply { - setContent { - SingleSelectItemList( - items = UPLOADERS, - getLabel = { it.name }, - currentOption = UPLOADERS.firstOrNull { - it.name.contentEquals(currentUploader) - } ?: DEFAULT_UPLOADER - ) { option -> - if (option.name.contentEquals(CustomUploader.name)){ - openCustomUploaderWindow() - } - currentUploader = option.name - closeDialog() - } - } - show() - } -} - diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/settings/Settings.kt b/app/src/main/java/com/donut/mixfile/ui/routes/settings/Settings.kt new file mode 100644 index 0000000..a934c25 --- /dev/null +++ b/app/src/main/java/com/donut/mixfile/ui/routes/settings/Settings.kt @@ -0,0 +1,200 @@ +package com.donut.mixfile.ui.routes.settings + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.donut.mixfile.server.DOWNLOAD_TASK_COUNT +import com.donut.mixfile.server.UPLOAD_RETRY_COUNT +import com.donut.mixfile.server.UPLOAD_TASK_COUNT +import com.donut.mixfile.server.currentUploader +import com.donut.mixfile.server.getCurrentUploader +import com.donut.mixfile.ui.component.common.CommonSwitch +import com.donut.mixfile.ui.component.common.MixDialogBuilder +import com.donut.mixfile.ui.component.common.SingleSelectItemList +import com.donut.mixfile.ui.nav.MixNavPage +import com.donut.mixfile.ui.theme.Theme +import com.donut.mixfile.ui.theme.currentTheme +import com.donut.mixfile.ui.theme.enableAutoDarkMode +import com.donut.mixfile.util.TipText +import com.donut.mixfile.util.cachedMutableOf +import com.donut.mixfile.util.file.filePreview +import com.donut.mixfile.util.file.multiUploadTaskCount +import com.donut.mixfile.util.file.uploadLogs +import com.donut.mixfile.util.showToast + + +var useShortCode by cachedMutableOf(true, "use_short_code") +var autoAddFavorite by cachedMutableOf(true, "auto_add_favorite") +var useSystemPlayer by cachedMutableOf(false, "use_system_player") + +@OptIn(ExperimentalMaterial3Api::class) +val MixSettings = MixNavPage( + gap = 10.dp, + horizontalAlignment = Alignment.CenterHorizontally +) { + Column { + Text( + modifier = Modifier.padding(10.dp, 0.dp), + text = "下载并发: $DOWNLOAD_TASK_COUNT", + color = MaterialTheme.colorScheme.primary + ) + Slider( + value = DOWNLOAD_TASK_COUNT.toFloat() / 10f, + steps = 10, + modifier = Modifier.fillMaxWidth(), + onValueChange = { + DOWNLOAD_TASK_COUNT = (it * 10).toLong().coerceAtLeast(1) + } + ) + + } + Column { + Text( + modifier = Modifier.padding(10.dp, 0.dp), + text = "上传并发: $UPLOAD_TASK_COUNT", + color = MaterialTheme.colorScheme.primary + ) + Slider( + value = UPLOAD_TASK_COUNT.toFloat() / 10f, + steps = 10, + modifier = Modifier.fillMaxWidth(), + onValueChange = { + UPLOAD_TASK_COUNT = (it * 10).toLong().coerceAtLeast(1) + } + ) + } + Column { + Text( + modifier = Modifier.padding(10.dp, 0.dp), + text = "批量上传并发: ${multiUploadTaskCount}", + color = MaterialTheme.colorScheme.primary + ) + Slider( + value = multiUploadTaskCount.toFloat() / 10f, + steps = 10, + modifier = Modifier.fillMaxWidth(), + onValueChange = { + multiUploadTaskCount = (it * 10).toLong().coerceAtLeast(1) + } + ) + } + Column { + Text( + modifier = Modifier.padding(10.dp, 0.dp), + text = "上传失败重试次数(单个分片): $UPLOAD_RETRY_COUNT", + color = MaterialTheme.colorScheme.primary + ) + Slider( + value = UPLOAD_RETRY_COUNT.toFloat() / 10f, + steps = 10, + modifier = Modifier.fillMaxWidth(), + onValueChange = { + UPLOAD_RETRY_COUNT = (it * 10).toLong().coerceAtLeast(0) + } + ) + } + TipText( + content = """ + 部分设置可能需要重启后才能生效 + """.trimIndent() + ) + CommonSwitch( + checked = useShortCode, + text = "使用短分享码(空白字符编码信息):" + ) { + useShortCode = it + } + CommonSwitch( + checked = autoAddFavorite, + text = "上传后自动添加文件到收藏:" + ) { + autoAddFavorite = it + } + CommonSwitch( + checked = useSystemPlayer, + text = "使用系统播放器:", + description = "播放视频是否使用系统调用其他播放器" + ) { + useSystemPlayer = it + } + + SettingButton(text = "网页端/WebDAV访问密码") { + setWebPassword() + } + + SettingButton(text = "文件分片大小", description = "默认1024kb,不建议修改") { + setUploadChunkSize() + } + + val uploader = remember(currentUploader) { getCurrentUploader() } + + SettingButton(text = "上传线路: ${uploader.name}") { + selectUploader() + } + uploader.SettingComponent() + SettingButton(text = "文件预览: $filePreview") { + selectFilePreview() + } + SettingButton(text = "颜色主题: ") { + MixDialogBuilder("颜色主题").apply { + setContent { + SingleSelectItemList( + items = Theme.entries, + getLabel = { it.label }, + currentOption = Theme.entries.firstOrNull { + it.name == currentTheme + } ?: Theme.DEFAULT + ) { option -> + currentTheme = option.name + closeDialog() + } + } + show() + } + } + CommonSwitch( + checked = enableAutoDarkMode, + text = "自动深色模式:", + "跟随系统自动切换深色模式", + ) { + enableAutoDarkMode = it + } + HorizontalDivider() + val batteryOptimization by remember { + mutableStateOf(isIgnoringBatteryOptimizations()) + } + if (!batteryOptimization) { + ElevatedButton(onClick = { + openBatteryOptimizationSettings() + }, modifier = Modifier.fillMaxWidth()) { + Text(text = "省电限制未设置!") + } + } + ElevatedButton(onClick = { + MixDialogBuilder("确定清除记录?").apply { + setContent { + Text(text = "确定清除所有上传历史记录?") + } + setPositiveButton("确定") { + closeDialog() + uploadLogs = emptyList() + showToast("清除成功!") + } + show() + } + }, modifier = Modifier.fillMaxWidth()) { + Text(text = "清除上传记录") + } +} diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/settings/Utils.kt b/app/src/main/java/com/donut/mixfile/ui/routes/settings/Utils.kt new file mode 100644 index 0000000..fc020b7 --- /dev/null +++ b/app/src/main/java/com/donut/mixfile/ui/routes/settings/Utils.kt @@ -0,0 +1,210 @@ +package com.donut.mixfile.ui.routes.settings + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.os.PowerManager +import android.provider.Settings +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.shrinkOut +import androidx.compose.animation.slideInVertically +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +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.net.toUri +import com.donut.mixfile.app +import com.donut.mixfile.server.CustomUploader +import com.donut.mixfile.server.JavaScriptUploader +import com.donut.mixfile.server.MIXFILE_CHUNK_SIZE +import com.donut.mixfile.server.SERVER_PASSWORD +import com.donut.mixfile.server.UPLOADERS +import com.donut.mixfile.server.core.Uploader +import com.donut.mixfile.server.currentUploader +import com.donut.mixfile.server.openCustomUploaderWindow +import com.donut.mixfile.server.openJavaScriptUploaderWindow +import com.donut.mixfile.ui.component.common.MixDialogBuilder +import com.donut.mixfile.ui.component.common.SingleSelectItemList +import com.donut.mixfile.util.file.filePreview +import com.donut.mixfile.util.showToast + +fun setUploadChunkSize() { + MixDialogBuilder("设置分片大小").apply { + var chunkSize by mutableLongStateOf(MIXFILE_CHUNK_SIZE) + setContent { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + OutlinedTextField( + value = chunkSize.toString(), + onValueChange = { + chunkSize = it.toLongOrNull() ?: 0 + }, + maxLines = 1, + label = { Text(text = "文件分片大小(KB)") }, + modifier = Modifier.fillMaxWidth() + ) + Text( + text = "有上传大文件(>20GB)需求可适量增大,会增加上传失败概率,增加上传下载时占用,降低下载速度,同时并发数量也会根据分片大小自动调整,基于1mb,如果并发为10,分片为2mb,实际并发数量则会为5", + modifier = Modifier + .fillMaxWidth(), +// .padding(10.dp, 0.dp), + color = Color(0xFF9E9E9E), + fontSize = 14.sp + ) + } + } + setDefaultNegative() + setPositiveButton("确定") { + MIXFILE_CHUNK_SIZE = chunkSize.coerceAtLeast(1).coerceAtMost(20480) + showToast("设置成功") + closeDialog() + } + show() + } +} + +fun setWebPassword() { + MixDialogBuilder("设置密码").apply { + var pass by mutableStateOf(SERVER_PASSWORD) + setContent { + OutlinedTextField( + value = pass, + onValueChange = { + pass = it + }, + maxLines = 1, + label = { Text(text = "网页/WEBDAV访问密码(留空禁用密码)") }, + modifier = Modifier.fillMaxWidth() + ) + } + setDefaultNegative() + setPositiveButton("确定") { + SERVER_PASSWORD = pass.trim() + showToast("设置成功") + closeDialog() + } + show() + } +} + +fun selectFilePreview() { + MixDialogBuilder("文件预览").apply { + setContent { + SingleSelectItemList( + items = listOf("开启", "关闭", "仅Wifi"), + currentOption = filePreview + ) { option -> + filePreview = option + closeDialog() + } + } + show() + } +} + +@Composable +fun Uploader.SettingComponent() { + AnimatedVisibility( + this is CustomUploader, + enter = slideInVertically(), + exit = shrinkOut() + ) { + SettingButton(text = "自定义线路设置: ") { + openCustomUploaderWindow() + } + } + AnimatedVisibility( + this.name.contentEquals(JavaScriptUploader.name), + enter = slideInVertically(), + exit = shrinkOut() + ) { + SettingButton(text = "JS自定义线路设置: ") { + openJavaScriptUploaderWindow() + } + } +} + +fun selectUploader() { + MixDialogBuilder("上传线路").apply { + setContent { + SingleSelectItemList( + items = UPLOADERS.map { it.name }, + getLabel = { it }, + currentOption = currentUploader + ) { option -> + currentUploader = option + closeDialog() + } + } + show() + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun SettingButton( + text: String, + buttonText: String = "设置", + description: String = "", + onClick: () -> Unit +) { + Column(modifier = Modifier.fillMaxWidth()) { + HorizontalDivider() + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(0.dp, 5.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = text, + modifier = Modifier.align(Alignment.CenterVertically) + ) + OutlinedButton(onClick = onClick) { + Text(text = buttonText) + } + } + if (description.isNotEmpty()) { + Text( + text = description, + modifier = Modifier + .fillMaxWidth(), +// .padding(10.dp, 0.dp), + color = Color(0xFF9E9E9E), + fontSize = 14.sp + ) + } + } +} + +fun isIgnoringBatteryOptimizations(context: Context = app): Boolean { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + return powerManager.isIgnoringBatteryOptimizations(context.packageName) +} + +@SuppressLint("BatteryLife") +fun openBatteryOptimizationSettings(context: Context = app) { + val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { + data = "package:${context.packageName}".toUri() + } + context.startActivity(intent) +} diff --git a/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt b/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt index 8c1f9d6..f8a9bc0 100644 --- a/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt +++ b/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt @@ -12,10 +12,10 @@ import com.donut.mixfile.server.core.objects.MixShareInfo import com.donut.mixfile.server.core.utils.resolveMixShareInfo import com.donut.mixfile.server.core.utils.sanitizeFileName import com.donut.mixfile.ui.component.common.MixDialogBuilder -import com.donut.mixfile.ui.routes.autoAddFavorite import com.donut.mixfile.ui.routes.favorites.currentCategory import com.donut.mixfile.ui.routes.home.getLocalServerAddress import com.donut.mixfile.ui.routes.home.serverAddress +import com.donut.mixfile.ui.routes.settings.autoAddFavorite import com.donut.mixfile.util.cachedMutableOf import com.donut.mixfile.util.getFileAccessUrl import com.donut.mixfile.util.showToast diff --git a/app/src/main/java/com/donut/mixfile/util/file/FileDialog.kt b/app/src/main/java/com/donut/mixfile/util/file/FileDialog.kt index bea5b26..1a24f85 100644 --- a/app/src/main/java/com/donut/mixfile/util/file/FileDialog.kt +++ b/app/src/main/java/com/donut/mixfile/util/file/FileDialog.kt @@ -31,8 +31,8 @@ import com.donut.mixfile.ui.component.common.MixDialogBuilder import com.donut.mixfile.ui.routes.favorites.openCategorySelect import com.donut.mixfile.ui.routes.home.DownloadTask import com.donut.mixfile.ui.routes.home.showDownloadTaskWindow -import com.donut.mixfile.ui.routes.useShortCode -import com.donut.mixfile.ui.routes.useSystemPlayer +import com.donut.mixfile.ui.routes.settings.useShortCode +import com.donut.mixfile.ui.routes.settings.useSystemPlayer import com.donut.mixfile.ui.theme.colorScheme import com.donut.mixfile.util.CachedDelegate import com.donut.mixfile.util.copyToClipboard diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4c863f..063783d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ lz4Java = "1.8.0" materialIconsExtended = "1.7.8" media3Exoplayer = "1.7.1" media3Session = "1.7.1" -mixfileCore = "42e98c7a09" +mixfileCore = "856a85cc25" #noinspection GradleDependency mmkv = "1.3.14" navigationCompose = "2.9.2"