+js uploader

This commit is contained in:
1
2025-07-21 12:59:29 +08:00
parent 6d98e3cc31
commit c5556c7000
11 changed files with 508 additions and 447 deletions

View File

@@ -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<Uploader> = 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()
}
}

View File

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

View File

@@ -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()
}
}

View File

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

View File

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

View File

@@ -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()
}
}

View File

@@ -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 = "清除上传记录")
}
}

View File

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

View File

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

View File

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

View File

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