增加批量操作,下载功能

增加批量操作,下载功能
This commit is contained in:
1
2024-11-30 21:31:59 +08:00
parent 6250e7c91f
commit cc4264d80b
14 changed files with 582 additions and 119 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId = "com.donut.mixfile"
minSdk = 24
targetSdk = 34
versionCode = 59
versionName = "1.7.0"
versionCode = 60
versionName = "1.8.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@@ -8,7 +8,6 @@ import io.ktor.client.request.put
import io.ktor.client.request.setBody
import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsText
import io.ktor.client.statement.readBytes
import io.ktor.client.statement.readRawBytes
import io.ktor.http.isSuccess
import kotlinx.coroutines.Dispatchers
@@ -22,15 +21,15 @@ object CustomUploader : Uploader("自定义") {
override suspend fun genHead(): ByteArray {
return uploadClient.get {
url(CUSTOM_UPLOAD_URL)
}.also {
val referer = it.headers["referer"]
if (!referer.isNullOrEmpty()) {
withContext(Dispatchers.Main) {
CUSTOM_REFERER = referer
}
url(CUSTOM_UPLOAD_URL)
}.also {
val referer = it.headers["referer"]
if (!referer.isNullOrEmpty()) {
withContext(Dispatchers.Main) {
CUSTOM_REFERER = referer
}
}.readRawBytes()
}
}.readRawBytes()
}
override val referer: String

View File

@@ -168,7 +168,7 @@ fun SingleSelectItemList(
@Composable
fun <T> SingleSelectItemList(
items: List<T>,
currentOption: T?,
currentOption: T? = null,
getLabel: (option: T) -> String,
onSelect: (option: T) -> Unit,
) {

View File

@@ -103,14 +103,14 @@ val About = MixNavPage(
}
Text(
color = colorScheme.primary,
text = "项目地址: https://gitlab.com/ivgeek/MixFile",
text = "项目地址: https://github.com/InvertGeek/MixMessage",
modifier = Modifier.clickable {
MixDialogBuilder("确定打开?").apply {
setPositiveButton("确定") {
val intent =
Intent(
Intent.ACTION_VIEW,
Uri.parse("https://gitlab.com/ivgeek/MixFile")
Uri.parse("https://github.com/InvertGeek/MixMessage")
).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}

View File

@@ -19,13 +19,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.donut.mixfile.ui.component.common.MixDialogBuilder
import com.donut.mixfile.ui.routes.home.TaskCard
import com.donut.mixfile.ui.routes.home.UploadTaskCard
import com.donut.mixfile.ui.routes.home.uploadTasks
import com.donut.mixfile.ui.theme.colorScheme
import com.donut.mixfile.util.file.cancelAllMultiUpload
import com.donut.mixfile.util.file.successFileCount
import com.donut.mixfile.util.file.totalFileCount
import com.donut.mixfile.util.file.totalUploadFileCount
import com.donut.mixfile.util.file.uploadQueue
import com.donut.mixfile.util.file.uploadSuccessFileCount
import com.donut.mixfile.util.objects.AnimatedLoadingBar
import com.donut.mixfile.util.showConfirmDialog
import com.donut.mixfile.util.showToast
@@ -41,11 +41,11 @@ fun showUploadTaskWindow() {
Column(
modifier = Modifier.fillMaxSize(),
) {
if (totalFileCount > 1) {
val progress = successFileCount.toFloat() / totalFileCount
if (totalUploadFileCount > 1) {
val progress = uploadSuccessFileCount.toFloat() / totalUploadFileCount
AnimatedLoadingBar(
progress = progress,
label = "总进度: ${successFileCount}/${totalFileCount} " +
label = "总进度: ${uploadSuccessFileCount}/${totalUploadFileCount} " +
"正在上传: ${uploadTasks.filter { it.uploading }.size} " +
"排队中: $uploadQueue"
)
@@ -54,8 +54,8 @@ fun showUploadTaskWindow() {
verticalArrangement = Arrangement.spacedBy(0.dp),
modifier = Modifier.padding(0.dp)
) {
uploadTasks.forEach {
TaskCard(uploadTask = it) {
uploadTasks.take(10).forEach {
UploadTaskCard(uploadTask = it) {
it.delete()
}
}

View File

@@ -1,19 +1,23 @@
package com.donut.mixfile.ui.routes.favorites
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
@@ -30,18 +34,25 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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.routes.UploadDialogCard
import com.donut.mixfile.ui.routes.home.DownloadDialogCard
import com.donut.mixfile.ui.routes.home.showDownloadTaskWindow
import com.donut.mixfile.ui.routes.home.tryResolveFile
import com.donut.mixfile.ui.theme.colorScheme
import com.donut.mixfile.util.cachedMutableOf
import com.donut.mixfile.util.file.FileCardList
import com.donut.mixfile.util.file.deleteFavoriteLog
import com.donut.mixfile.util.file.exportFileList
import com.donut.mixfile.util.file.FileDataLog
import com.donut.mixfile.util.file.downloadFile
import com.donut.mixfile.util.file.favorites
import com.donut.mixfile.util.file.resolveMixShareInfo
import com.donut.mixfile.util.file.selectAndUploadFile
import com.donut.mixfile.util.file.showExportFileListDialog
import com.donut.mixfile.util.formatFileSize
import com.donut.mixfile.util.getCurrentTime
import com.donut.mixfile.util.parseSortNum
import com.donut.mixfile.util.showConfirmDialog
import com.donut.mixfile.util.showToast
import com.donut.mixfile.util.truncate
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -52,6 +63,7 @@ var currentCategory: String by mutableStateOf("")
private var favoriteSort by cachedMutableOf("最新", "mix_favorite_sort")
@OptIn(ExperimentalFoundationApi::class)
val Favorites = MixNavPage(
gap = 10.dp,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -116,9 +128,9 @@ val Favorites = MixNavPage(
result = if (searchVal.trim().isNotEmpty()) {
favorites.filter {
it.name.contains(searchVal)
}.reversed()
}.asReversed()
} else {
favorites.reversed()
favorites.asReversed()
}
result = result.filter {
currentCategory.isEmpty() || it.category == currentCategory
@@ -159,28 +171,7 @@ val Favorites = MixNavPage(
}
Button(
onClick = {
MixDialogBuilder("确定导出?").apply {
var listName by mutableStateOf("文件列表-${getCurrentTime()}")
setContent {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
OutlinedTextField(value = listName, onValueChange = {
listName = it
}, modifier = Modifier.fillMaxWidth(), label = {
Text(text = "列表名称")
})
Text(text = "将会导出当前筛选的文件列表上传为一键分享链接")
}
}
setDefaultNegative()
setPositiveButton("确定") {
exportFileList(result, listName)
closeDialog()
}
show()
}
showExportFileListDialog(result)
},
modifier = Modifier
.weight(1.0f)
@@ -190,6 +181,7 @@ val Favorites = MixNavPage(
}
}
UploadDialogCard()
DownloadDialogCard()
if (result.isEmpty()) {
Text(
text = "没有搜索到文件",
@@ -201,6 +193,100 @@ val Favorites = MixNavPage(
return@MixNavPage
}
var multiSelect by remember {
mutableStateOf(false)
}
var selected by remember {
mutableStateOf(setOf<FileDataLog>())
}
LaunchedEffect(multiSelect) {
if (!multiSelect) {
selected = setOf()
}
}
LaunchedEffect(selected) {
if (selected.isEmpty()) {
multiSelect = false
}
}
AnimatedVisibility(visible = multiSelect) {
ElevatedCard(
modifier = Modifier
.fillMaxWidth(),
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.combinedClickable(onLongClick = {
selected = emptySet()
}) {
showFileListActionDialog(
listOf(
Pair("删除") {
showConfirmDialog("确定删除?") {
favorites = favorites - selected
showToast("删除成功")
selected = emptySet()
}
},
Pair("全选") {
selected += result
},
Pair("取消选择") {
selected = emptySet()
},
Pair("导出文件") {
showExportFileListDialog(selected)
selected = emptySet()
},
Pair("全部下载") {
selected.forEach {
val shareInfo = resolveMixShareInfo(it.shareInfoData)
if (shareInfo != null) {
downloadFile(shareInfo)
}
}
showDownloadTaskWindow()
selected = emptySet()
},
Pair("移动分类") {
openCategorySelect(selected.first().category) { category ->
favorites = favorites.map {
if (selected.contains(it))
it.copy(category = category)
else
it
}
selected = emptySet()
}
},
),
)
}
.fillMaxSize()
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "已选择 ${selected.size} 个文件",
modifier = Modifier,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = colorScheme.primary
)
Icon(
Icons.Filled.Edit,
contentDescription = "selected",
tint = colorScheme.primary
)
}
}
}
ElevatedCard(
modifier = Modifier.fillMaxSize(),
) {
@@ -218,8 +304,40 @@ val Favorites = MixNavPage(
fontWeight = FontWeight.Bold,
color = colorScheme.primary,
)
FileCardList(cardList = result) {
deleteFavoriteLog(it)
HorizontalDivider()
FileCardList(
cardList = result,
selected = selected,
onClick = {
if (!multiSelect) {
tryResolveFile(it.shareInfoData)
return@FileCardList
}
if (!selected.contains(it)) {
selected += it
return@FileCardList
}
selected -= it
}) {
multiSelect = true
selected += it
}
}
}
fun showFileListActionDialog(options: List<Pair<String, () -> Unit>>) {
MixDialogBuilder("编辑文件").apply {
setContent {
SingleSelectItemList(
items = options,
getLabel = {
it.first
},
) { option ->
option.second()
closeDialog()
}
}
show()
}
}

View File

@@ -0,0 +1,303 @@
package com.donut.mixfile.ui.routes.home
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
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.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.donut.mixfile.appScope
import com.donut.mixfile.ui.component.common.MixDialogBuilder
import com.donut.mixfile.ui.theme.colorScheme
import com.donut.mixfile.util.file.InfoText
import com.donut.mixfile.util.file.saveFileToStorage
import com.donut.mixfile.util.formatFileSize
import com.donut.mixfile.util.objects.AnimatedLoadingBar
import com.donut.mixfile.util.objects.ProgressContent
import com.donut.mixfile.util.showConfirmDialog
import com.donut.mixfile.util.showErrorDialog
import com.donut.mixfile.util.showToast
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@Composable
fun DownloadTaskCard(
downloadTask: DownloadTask,
longClick: () -> Unit = {},
) {
HorizontalDivider()
Card(
colors = CardDefaults.cardColors(
containerColor = Color(107, 218, 246, 0),
),
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
onLongClick = {
longClick()
}
) {
downloadTask.cancel()
}
) {
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Text(
text = downloadTask.fileName,
color = colorScheme.primary,
fontSize = 16.sp,
)
FlowRow(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
InfoText(key = "大小: ", value = formatFileSize(downloadTask.fileSize))
downloadTask.State()
}
if (downloadTask.started && !downloadTask.stopped) {
downloadTask.progress.LoadingContent()
}
}
}
}
var downloadTasks by mutableStateOf(listOf<DownloadTask>())
var downloadSemaphore = Semaphore(3)
class DownloadTask(
val fileName: String,
val fileSize: Long,
val url: String,
) {
var progress = ProgressContent("下载中", 14.sp, colorScheme.secondary, false)
var job: Job? = null
var stopped by mutableStateOf(false)
var started by mutableStateOf(false)
var error: Throwable? by mutableStateOf(null)
init {
appScope.launch {
downloadTasks += this@DownloadTask
}
totalDownloadFileCount++
progress.contentLength = fileSize
}
@Composable
fun State() {
if (stopped) {
if (error is CancellationException) {
return Text(text = "下载取消", color = colorScheme.error)
}
if (error != null) {
return Text(text = "下载失败", color = colorScheme.error)
}
return Text(text = "下载成功", color = colorScheme.primary)
}
if (started) {
return Text(text = "下载中", color = colorScheme.primary)
}
return Text(text = "等待中", color = colorScheme.primary)
}
fun delete() {
MixDialogBuilder("删除记录?").apply {
setContent {
Text(text = "文件: ${fileName}")
}
val currentError = error
if (currentError != null) {
setNegativeButton("查看错误信息") {
showErrorDialog(currentError, "错误信息")
}
}
setPositiveButton("确定") {
stop()
downloadTasks -= this@DownloadTask
closeDialog()
}
show()
}
}
fun stop() {
if (stopped) {
return
}
job?.cancel("下载取消")
stopped = true
}
fun start() {
downloadQueue++
job = appScope.launch(Dispatchers.IO) {
downloadSemaphore.acquire()
withContext(Dispatchers.Main) {
downloadQueue--
}
if (stopped) {
return@launch
}
started = true
saveFileToStorage(
url,
displayName = fileName,
progress = progress
)
downloadSuccessFileCount++
}
job?.invokeOnCompletion {
error = it
stopped = true
downloadSemaphore.release()
}
}
fun cancel() {
if (stopped) {
delete()
return
}
MixDialogBuilder("取消下载?").apply {
setContent {
Text(text = "文件: ${fileName}")
}
setPositiveButton("确定") {
stop()
closeDialog()
showToast("下载已取消")
}
show()
}
}
}
var downloadQueue by mutableIntStateOf(0)
var totalDownloadFileCount by mutableIntStateOf(0)
var downloadSuccessFileCount by mutableIntStateOf(0)
fun cancelAllDownloads() {
downloadQueue = 0
downloadTasks.forEach { it.stop() }
totalDownloadFileCount = 0
downloadSuccessFileCount = 0
}
fun showDownloadTaskWindow() {
MixDialogBuilder("下载中的文件").apply {
setContent {
if (downloadTasks.isEmpty()) {
Text(text = "没有下载中的文件")
return@setContent
}
Column(
modifier = Modifier.fillMaxSize(),
) {
if (totalDownloadFileCount > 1) {
val progress = downloadSuccessFileCount.toFloat() / totalDownloadFileCount
AnimatedLoadingBar(
progress = progress,
label = "总进度: $downloadSuccessFileCount/$totalDownloadFileCount " +
"正在下载: ${downloadTasks.filter { it.started && !it.stopped }.size} " +
"排队中: $downloadQueue"
)
}
Column(
verticalArrangement = Arrangement.spacedBy(0.dp),
modifier = Modifier.padding(0.dp)
) {
downloadTasks.sortedBy {
if (it.started && !it.stopped) 0 else 1
}.take(5).forEach {
DownloadTaskCard(it) {
it.delete()
}
}
}
}
}
setPositiveButton("清除已完成任务") {
downloadTasks = downloadTasks.filter { !it.stopped }
showToast("清除成功")
}
setNegativeButton("全部取消") {
showConfirmDialog("确定取消全部下载任务?") {
cancelAllDownloads()
showToast("取消成功")
}
}
show()
}
}
@Composable
fun DownloadDialogCard() {
val downloading = downloadTasks.filter { !it.stopped }
AnimatedVisibility(visible = downloading.isNotEmpty()) {
ElevatedCard(
modifier = Modifier
.fillMaxWidth(),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.clickable {
showDownloadTaskWindow()
}
.fillMaxSize()
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (downloading.isNotEmpty()) {
Text(
text = "${downloading.size} 个文件正在下载中",
modifier = Modifier,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = colorScheme.primary
)
CircularProgressIndicator(modifier = Modifier.size(20.dp))
}
}
}
}
}

View File

@@ -14,6 +14,7 @@ import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
@@ -132,6 +133,7 @@ val Home = MixNavPage(
}
}
UploadDialogCard()
DownloadDialogCard()
if (uploadLogs.isNotEmpty()) {
ElevatedCard(
modifier = Modifier.fillMaxSize(),
@@ -145,7 +147,8 @@ val Home = MixNavPage(
fontWeight = FontWeight.Bold,
color = colorScheme.primary
)
FileCardList(cardList = uploadLogs.reversed()) {
HorizontalDivider()
FileCardList(cardList = uploadLogs.asReversed()) {
deleteUploadLog(it)
}
}

View File

@@ -37,11 +37,10 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Date
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@Composable
fun TaskCard(
fun UploadTaskCard(
uploadTask: UploadTask,
longClick: () -> Unit = {},
) {
@@ -95,7 +94,6 @@ class UploadTask(
val fileName: String,
val fileSize: Long,
val add: Boolean = true,
val time: Date = Date(),
) {
var progress = ProgressContent("上传中", 14.sp, colorScheme.secondary, false)

View File

@@ -351,7 +351,7 @@ inline fun errorDialog(title: String, block: () -> Unit) {
when (e) {
is CancellationException,
is EOFException,
-> return
-> return
}
appScope.launch(Dispatchers.Main) {
showErrorDialog(e, title)

View File

@@ -1,11 +1,14 @@
package com.donut.mixfile.util.file
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -14,13 +17,16 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Card
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
@@ -119,7 +125,14 @@ fun PreviewCard(
}
@Composable
fun FileCardList(cardList: List<FileDataLog>, longClick: (FileDataLog) -> Unit = {}) {
fun FileCardList(
cardList: List<FileDataLog>,
selected: Set<FileDataLog> = setOf(),
onClick: (FileDataLog) -> Unit = {
tryResolveFile(it.shareInfoData)
},
longClick: (FileDataLog) -> Unit = {},
) {
if (filePreview.contentEquals("开启") ||
(filePreview.contentEquals("仅Wifi") && NetworkChangeReceiver.isWifi)
@@ -149,7 +162,16 @@ fun FileCardList(cardList: List<FileDataLog>, longClick: (FileDataLog) -> Unit =
verticalArrangement = Arrangement.spacedBy(0.dp)
) {
items(cardList.size) { index ->
FileCard(cardList[index], longClick = longClick)
val log = cardList[index]
if (index > 0) {
HorizontalDivider()
}
FileCard(
log,
longClick = longClick,
selected = selected.contains(log),
onClick = onClick
)
}
}
}
@@ -160,35 +182,52 @@ fun FileCardList(cardList: List<FileDataLog>, longClick: (FileDataLog) -> Unit =
fun FileCard(
fileDataLog: FileDataLog,
showDate: Boolean = true,
onClick: (FileDataLog) -> Unit,
selected: Boolean = false,
longClick: (FileDataLog) -> Unit = {},
) {
LaunchedEffect(favorites) {
}
HorizontalDivider()
Card(
colors = CardDefaults.cardColors(
containerColor = Color(107, 218, 246, 0),
),
val color = remember(selected) {
if (selected)
Color(107, 184, 242, 84)
else
Color(107, 218, 246, 0)
}
Box(
modifier = Modifier
.fillMaxWidth()
.background(color)
.combinedClickable(
onLongClick = {
longClick(fileDataLog)
}
) {
tryResolveFile(fileDataLog.shareInfoData)
onClick(fileDataLog)
}
) {
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Text(
text = fileDataLog.name.trim(),
color = colorScheme.primary,
fontSize = 16.sp,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
if (selected) {
Icon(
Icons.Filled.CheckCircle,
contentDescription = "selected",
tint = colorScheme.primary
)
}
Text(
text = fileDataLog.name.trim(),
color = colorScheme.primary,
fontSize = 16.sp,
)
}
FlowRow(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()

View File

@@ -16,7 +16,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties
import com.donut.mixfile.activity.VideoActivity
import com.donut.mixfile.app
import com.donut.mixfile.currentActivity
@@ -24,13 +23,11 @@ import com.donut.mixfile.server.utils.bean.MixShareInfo
import com.donut.mixfile.ui.component.common.MixDialogBuilder
import com.donut.mixfile.ui.routes.favorites.importFileList
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.theme.colorScheme
import com.donut.mixfile.util.UseEffect
import com.donut.mixfile.util.copyToClipboard
import com.donut.mixfile.util.errorDialog
import com.donut.mixfile.util.formatFileSize
import com.donut.mixfile.util.objects.ProgressContent
import com.donut.mixfile.util.showToast
@OptIn(ExperimentalLayoutApi::class)
fun showFileInfoDialog(shareInfo: MixShareInfo, onDismiss: () -> Unit = {}) {
@@ -126,6 +123,8 @@ fun showFileInfoDialog(shareInfo: MixShareInfo, onDismiss: () -> Unit = {}) {
}
setPositiveButton("下载文件") {
downloadFile(shareInfo)
closeDialog()
showDownloadTaskWindow()
}
show()
}
@@ -146,38 +145,6 @@ fun InfoText(key: String, value: String) {
}
fun downloadFile(shareInfo: MixShareInfo) {
MixDialogBuilder(
"文件下载中", properties = DialogProperties(
dismissOnClickOutside = false,
dismissOnBackPress = false
)
).apply {
setContent {
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
val progressContent = remember {
ProgressContent()
}
progressContent.LoadingContent()
UseEffect {
errorDialog("下载失败") {
saveFileToStorage(
shareInfo.downloadUrl,
displayName = shareInfo.fileName,
progress = progressContent
)
showToast("文件已保存到下载目录")
}
closeDialog()
}
}
}
setNegativeButton("取消") {
showToast("下载已取消")
closeDialog()
}
show()
}
val task = DownloadTask(shareInfo.fileName, shareInfo.fileSize, shareInfo.downloadUrl)
task.start()
}

View File

@@ -1,10 +1,21 @@
package com.donut.mixfile.util.file
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.donut.mixfile.server.localClient
import com.donut.mixfile.ui.component.common.MixDialogBuilder
import com.donut.mixfile.util.compressGzip
import com.donut.mixfile.util.decompressGzip
import com.donut.mixfile.util.formatFileSize
import com.donut.mixfile.util.getCurrentTime
import com.donut.mixfile.util.objects.ProgressContent
import com.donut.mixfile.util.showErrorDialog
import com.donut.mixfile.util.showToast
@@ -23,7 +34,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.readByteArray
fun exportFileList(fileList: List<FileDataLog>, name: String) {
fun exportFileList(fileList: Collection<FileDataLog>, name: String) {
val strData = fileList.toJsonString()
val compressedData = compressGzip(strData)
doUploadFile(
@@ -33,6 +44,31 @@ fun exportFileList(fileList: List<FileDataLog>, name: String) {
)
}
fun showExportFileListDialog(fileList: Collection<FileDataLog>) {
MixDialogBuilder("确定导出?").apply {
var listName by mutableStateOf("文件列表-${getCurrentTime()}")
setContent {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
OutlinedTextField(value = listName, onValueChange = {
listName = it
}, modifier = Modifier.fillMaxWidth(), label = {
Text(text = "列表名称")
})
Text(text = "将会导出当前筛选的文件列表上传为一键分享链接")
}
}
setDefaultNegative()
setPositiveButton("确定") {
exportFileList(fileList, listName)
closeDialog()
}
show()
}
}
fun showFileList(fileList: List<FileDataLog>) {
val fileTotalSize = fileList.sumOf { it.size }
MixDialogBuilder(

View File

@@ -139,8 +139,8 @@ var multiUploadTaskCount by cachedMutableOf(5, "mix_file_multi_upload_task_count
val uploadSemaphore = Semaphore(multiUploadTaskCount.toInt())
var uploadQueue by mutableIntStateOf(0)
var totalFileCount by mutableIntStateOf(0)
var successFileCount by mutableIntStateOf(0)
var totalUploadFileCount by mutableIntStateOf(0)
var uploadSuccessFileCount by mutableIntStateOf(0)
private val multiUploadJobs = mutableListOf<Job>()
fun cancelAllMultiUpload() {
@@ -148,8 +148,8 @@ fun cancelAllMultiUpload() {
multiUploadJobs.forEach { it.cancel() }
multiUploadJobs.clear()
uploadTasks.forEach { it.stop() }
totalFileCount = 0
successFileCount = 0
totalUploadFileCount = 0
uploadSuccessFileCount = 0
}
inline fun uploadUri(uri: Uri, uploader: (StreamContent, String) -> Unit) {
@@ -170,7 +170,7 @@ fun selectAndUploadFile() {
MainActivity.mixFileSelector.openSelect { uriList ->
val taskList = mutableListOf<suspend () -> Unit>()
uploadQueue += uriList.size
totalFileCount += uriList.size
totalUploadFileCount += uriList.size
uriList.forEach { uri ->
taskList.add {
uploadUri(uri) { stream, name ->
@@ -191,7 +191,7 @@ fun selectAndUploadFile() {
deferredList.add(async {
catchError {
task()
successFileCount++
uploadSuccessFileCount++
}
uploadSemaphore.release()
})
@@ -199,8 +199,8 @@ fun selectAndUploadFile() {
deferredList.awaitAll()
withContext(Dispatchers.Main) {
if (uploadQueue == 0) {
totalFileCount = 0
successFileCount = 0
totalUploadFileCount = 0
uploadSuccessFileCount = 0
}
}
}