mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
perf: upload dialog, move log to AdvancedPage
Some checks are pending
Build-Apk / build (push) Waiting to run
Some checks are pending
Build-Apk / build (push) Waiting to run
This commit is contained in:
parent
eebcf242de
commit
ab8afcefae
|
@ -15,12 +15,15 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Upload
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
|
@ -46,6 +49,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
@ -82,12 +86,16 @@ import li.songe.gkd.util.LocalLauncher
|
|||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.ProfileTransitions
|
||||
import li.songe.gkd.util.appInfoCacheFlow
|
||||
import li.songe.gkd.util.buildLogFile
|
||||
import li.songe.gkd.util.json
|
||||
import li.songe.gkd.util.launchAsFn
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.openApp
|
||||
import li.songe.gkd.util.openUri
|
||||
import li.songe.gkd.util.saveFileToDownloads
|
||||
import li.songe.gkd.util.shareFile
|
||||
import li.songe.gkd.util.storeFlow
|
||||
import li.songe.gkd.util.throttle
|
||||
import li.songe.gkd.util.toast
|
||||
import rikka.shizuku.Shizuku
|
||||
|
||||
|
@ -103,10 +111,62 @@ fun AdvancedPage() {
|
|||
val snapshotCount by vm.snapshotCountFlow.collectAsState()
|
||||
|
||||
ShizukuErrorDialog(vm.shizukuErrorFlow)
|
||||
vm.uploadOptions.ShowDialog()
|
||||
|
||||
var showPortDlg by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var showShareLogDlg by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
if (showShareLogDlg) {
|
||||
Dialog(onDismissRequest = { showShareLogDlg = false }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
Text(
|
||||
text = "分享到其他应用", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.shareFile(logZipFile, "分享日志文件")
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "保存到下载", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.saveFileToDownloads(logZipFile)
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "生成链接(需科学上网)",
|
||||
modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
vm.uploadOptions.startTask(logZipFile)
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||
Scaffold(
|
||||
|
@ -324,7 +384,7 @@ fun AdvancedPage() {
|
|||
}
|
||||
|
||||
TextSwitch(
|
||||
name = "隐藏快照状态栏",
|
||||
name = "隐藏状态栏",
|
||||
desc = "当保存快照时,隐藏截图里的顶部状态栏高度区域",
|
||||
checked = store.hideSnapshotStatusBar
|
||||
) {
|
||||
|
@ -334,7 +394,7 @@ fun AdvancedPage() {
|
|||
}
|
||||
|
||||
TextSwitch(
|
||||
name = "保存快照提示",
|
||||
name = "保存提示",
|
||||
desc = "保存快照时是否提示\"正在保存快照\"",
|
||||
checked = store.showSaveSnapshotToast
|
||||
) {
|
||||
|
@ -343,6 +403,37 @@ fun AdvancedPage() {
|
|||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "日志",
|
||||
modifier = Modifier.titleItemPadding(),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
|
||||
TextSwitch(name = "保存日志",
|
||||
desc = "保存7天日志,帮助定位BUG",
|
||||
checked = store.log2FileSwitch,
|
||||
onCheckedChange = {
|
||||
storeFlow.value = store.copy(
|
||||
log2FileSwitch = it
|
||||
)
|
||||
if (!it) {
|
||||
context.mainVm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logFiles = LogUtils.getLogFiles()
|
||||
if (logFiles.isNotEmpty()) {
|
||||
logFiles.forEach { f ->
|
||||
f.delete()
|
||||
}
|
||||
toast("已删除全部日志")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
SettingItem(title = "导出日志", imageVector = Icons.Default.Upload, onClick = {
|
||||
showShareLogDlg = true
|
||||
})
|
||||
|
||||
Text(
|
||||
text = "其它",
|
||||
modifier = Modifier.titleItemPadding(),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package li.songe.gkd.ui
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -8,12 +7,15 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import li.songe.gkd.db.DbSet
|
||||
import li.songe.gkd.ui.component.UploadOptions
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AdvancedVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel() {
|
||||
class AdvancedVm @Inject constructor() : ViewModel() {
|
||||
val snapshotCountFlow =
|
||||
DbSet.snapshotDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0)
|
||||
|
||||
val shizukuErrorFlow = MutableStateFlow(false)
|
||||
|
||||
val uploadOptions = UploadOptions(viewModelScope)
|
||||
}
|
|
@ -15,17 +15,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -49,7 +46,6 @@ import com.blankj.utilcode.util.UriUtils
|
|||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import com.ramcosta.composedestinations.navigation.navigate
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import li.songe.gkd.MainActivity
|
||||
|
@ -63,7 +59,6 @@ import li.songe.gkd.ui.component.waitResult
|
|||
import li.songe.gkd.ui.destinations.ImagePreviewPageDestination
|
||||
import li.songe.gkd.ui.style.EmptyHeight
|
||||
import li.songe.gkd.util.IMPORT_BASE_URL
|
||||
import li.songe.gkd.util.LoadStatus
|
||||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.LocalPickContentLauncher
|
||||
import li.songe.gkd.util.ProfileTransitions
|
||||
|
@ -86,7 +81,8 @@ fun SnapshotPage() {
|
|||
|
||||
val vm = hiltViewModel<SnapshotVm>()
|
||||
val snapshots by vm.snapshotsState.collectAsState()
|
||||
val uploadStatus by vm.uploadStatusFlow.collectAsState()
|
||||
|
||||
vm.uploadOptions.ShowDialog()
|
||||
|
||||
var selectedSnapshot by remember {
|
||||
mutableStateOf<Snapshot?>(null)
|
||||
|
@ -253,9 +249,9 @@ fun SnapshotPage() {
|
|||
} else {
|
||||
Text(
|
||||
text = "生成链接(需科学上网)", modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
.clickable(onClick = vm.viewModelScope.launchAsFn(Dispatchers.IO) {
|
||||
selectedSnapshot = null
|
||||
vm.uploadZip(snapshotVal)
|
||||
vm.uploadOptions.startTask(SnapshotExt.getSnapshotZipFile(snapshotVal.id))
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
|
@ -323,69 +319,6 @@ fun SnapshotPage() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (val uploadStatusVal = uploadStatus) {
|
||||
is LoadStatus.Failure -> {
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传失败") },
|
||||
text = {
|
||||
Text(text = uploadStatusVal.exception.let {
|
||||
it.message ?: it.toString()
|
||||
})
|
||||
},
|
||||
onDismissRequest = { vm.uploadStatusFlow.value = null },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "关闭")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Loading -> {
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传文件中") },
|
||||
text = {
|
||||
LinearProgressIndicator(
|
||||
progress = { uploadStatusVal.progress },
|
||||
)
|
||||
},
|
||||
onDismissRequest = { },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadJob?.cancel(CancellationException("终止上传"))
|
||||
vm.uploadJob = null
|
||||
}) {
|
||||
Text(text = "终止上传")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Success -> {
|
||||
AlertDialog(title = { Text(text = "上传完成") }, text = {
|
||||
Text(text = IMPORT_BASE_URL + uploadStatusVal.result.id)
|
||||
}, onDismissRequest = {}, dismissButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "关闭")
|
||||
}
|
||||
}, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
ClipboardUtils.copyText(IMPORT_BASE_URL + uploadStatusVal.result.id)
|
||||
toast("复制成功")
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "复制")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,27 +3,10 @@ package li.songe.gkd.ui
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.onUpload
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import li.songe.gkd.data.GithubPoliciesAsset
|
||||
import li.songe.gkd.data.RpcError
|
||||
import li.songe.gkd.data.Snapshot
|
||||
import li.songe.gkd.db.DbSet
|
||||
import li.songe.gkd.debug.SnapshotExt.getSnapshotZipFile
|
||||
import li.songe.gkd.util.FILE_UPLOAD_URL
|
||||
import li.songe.gkd.util.LoadStatus
|
||||
import li.songe.gkd.util.client
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.ui.component.UploadOptions
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
@ -32,40 +15,6 @@ class SnapshotVm @Inject constructor() : ViewModel() {
|
|||
val snapshotsState = DbSet.snapshotDao.query()
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
|
||||
|
||||
val uploadStatusFlow = MutableStateFlow<LoadStatus<GithubPoliciesAsset>?>(null)
|
||||
var uploadJob: Job? = null
|
||||
|
||||
fun uploadZip(snapshot: Snapshot) {
|
||||
uploadJob = viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val zipFile = getSnapshotZipFile(snapshot.id)
|
||||
uploadStatusFlow.value = LoadStatus.Loading()
|
||||
try {
|
||||
val response =
|
||||
client.submitFormWithBinaryData(url = FILE_UPLOAD_URL, formData = formData {
|
||||
append("\"file\"", zipFile.readBytes(), Headers.build {
|
||||
append(HttpHeaders.ContentType, "application/x-zip-compressed")
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"file.zip\"")
|
||||
})
|
||||
}) {
|
||||
onUpload { bytesSentTotal, contentLength ->
|
||||
if (uploadStatusFlow.value is LoadStatus.Loading) {
|
||||
uploadStatusFlow.value =
|
||||
LoadStatus.Loading(bytesSentTotal / contentLength.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.headers["X_RPC_OK"] == "true") {
|
||||
val policiesAsset = response.body<GithubPoliciesAsset>()
|
||||
uploadStatusFlow.value = LoadStatus.Success(policiesAsset)
|
||||
DbSet.snapshotDao.update(snapshot.copy(githubAssetId = policiesAsset.id))
|
||||
} else if (response.headers["X_RPC_OK"] == "false") {
|
||||
uploadStatusFlow.value = LoadStatus.Failure(response.body<RpcError>())
|
||||
} else {
|
||||
uploadStatusFlow.value = LoadStatus.Failure(Exception(response.bodyAsText()))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
uploadStatusFlow.value = LoadStatus.Failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
val uploadOptions = UploadOptions(viewModelScope)
|
||||
}
|
146
app/src/main/kotlin/li/songe/gkd/ui/component/UploadOptions.kt
Normal file
146
app/src/main/kotlin/li/songe/gkd/ui/component/UploadOptions.kt
Normal file
|
@ -0,0 +1,146 @@
|
|||
package li.songe.gkd.ui.component
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import com.blankj.utilcode.util.ClipboardUtils
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.onUpload
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import li.songe.gkd.data.GithubPoliciesAsset
|
||||
import li.songe.gkd.data.RpcError
|
||||
import li.songe.gkd.util.FILE_UPLOAD_URL
|
||||
import li.songe.gkd.util.LoadStatus
|
||||
import li.songe.gkd.util.client
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.toast
|
||||
import java.io.File
|
||||
|
||||
class UploadOptions(
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
private val statusFlow = MutableStateFlow<LoadStatus<GithubPoliciesAsset>?>(null)
|
||||
private var job: Job? = null
|
||||
private fun buildTask(file: File) = scope.launchTry(Dispatchers.IO) {
|
||||
statusFlow.value = LoadStatus.Loading()
|
||||
try {
|
||||
val response =
|
||||
client.submitFormWithBinaryData(url = FILE_UPLOAD_URL, formData = formData {
|
||||
append("\"file\"", file.readBytes(), Headers.build {
|
||||
append(HttpHeaders.ContentType, "application/x-zip-compressed")
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"file.zip\"")
|
||||
})
|
||||
}) {
|
||||
onUpload { bytesSentTotal, contentLength ->
|
||||
if (statusFlow.value is LoadStatus.Loading) {
|
||||
statusFlow.value =
|
||||
LoadStatus.Loading(bytesSentTotal / contentLength.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.headers["X_RPC_OK"] == "true") {
|
||||
val policiesAsset = response.body<GithubPoliciesAsset>()
|
||||
statusFlow.value = LoadStatus.Success(policiesAsset)
|
||||
} else if (response.headers["X_RPC_OK"] == "false") {
|
||||
statusFlow.value = LoadStatus.Failure(response.body<RpcError>())
|
||||
} else {
|
||||
statusFlow.value = LoadStatus.Failure(Exception(response.bodyAsText()))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
statusFlow.value = LoadStatus.Failure(e)
|
||||
} finally {
|
||||
job = null
|
||||
}
|
||||
}
|
||||
|
||||
fun startTask(file: File) {
|
||||
if (job == null && statusFlow.value is LoadStatus.Loading) {
|
||||
return
|
||||
}
|
||||
job = buildTask(file)
|
||||
}
|
||||
|
||||
private fun stopTask() {
|
||||
if (statusFlow.value is LoadStatus.Loading && job != null) {
|
||||
job?.cancel(CancellationException("您取消了上传"))
|
||||
job = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun ShowDialog() {
|
||||
when (val status = statusFlow.collectAsState().value) {
|
||||
null -> {}
|
||||
is LoadStatus.Loading -> {
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传文件中") },
|
||||
text = {
|
||||
LinearProgressIndicator(
|
||||
progress = { status.progress },
|
||||
)
|
||||
},
|
||||
onDismissRequest = { },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
stopTask()
|
||||
}) {
|
||||
Text(text = "终止上传")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Success -> {
|
||||
AlertDialog(title = { Text(text = "上传完成") }, text = {
|
||||
Text(text = status.result.shortHref)
|
||||
}, onDismissRequest = {}, dismissButton = {
|
||||
TextButton(onClick = {
|
||||
statusFlow.value = null
|
||||
}) {
|
||||
Text(text = "关闭")
|
||||
}
|
||||
}, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
ClipboardUtils.copyText(status.result.shortHref)
|
||||
toast("复制成功")
|
||||
statusFlow.value = null
|
||||
}) {
|
||||
Text(text = "复制并关闭")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
is LoadStatus.Failure -> {
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传失败") },
|
||||
text = {
|
||||
Text(text = status.exception.let {
|
||||
it.message ?: it.toString()
|
||||
})
|
||||
},
|
||||
onDismissRequest = { statusFlow.value = null },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
statusFlow.value = null
|
||||
}) {
|
||||
Text(text = "关闭")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,16 +5,9 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.onUpload
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
@ -22,13 +15,9 @@ import kotlinx.coroutines.flow.debounce
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import li.songe.gkd.appScope
|
||||
import li.songe.gkd.data.GithubPoliciesAsset
|
||||
import li.songe.gkd.data.RawSubscription
|
||||
import li.songe.gkd.data.RpcError
|
||||
import li.songe.gkd.data.SubsItem
|
||||
import li.songe.gkd.db.DbSet
|
||||
import li.songe.gkd.util.FILE_UPLOAD_URL
|
||||
import li.songe.gkd.util.LoadStatus
|
||||
import li.songe.gkd.util.SortTypeOption
|
||||
import li.songe.gkd.util.appInfoCacheFlow
|
||||
import li.songe.gkd.util.clickCountFlow
|
||||
|
@ -44,48 +33,12 @@ import li.songe.gkd.util.subsItemsFlow
|
|||
import li.songe.gkd.util.subsRefreshingFlow
|
||||
import li.songe.gkd.util.toast
|
||||
import li.songe.gkd.util.updateSubscription
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class HomeVm @Inject constructor() : ViewModel() {
|
||||
val tabFlow = MutableStateFlow(controlNav)
|
||||
|
||||
val uploadStatusFlow = MutableStateFlow<LoadStatus<GithubPoliciesAsset>?>(null)
|
||||
var uploadJob: Job? = null
|
||||
|
||||
fun uploadZip(zipFile: File) {
|
||||
uploadJob = viewModelScope.launchTry(Dispatchers.IO) {
|
||||
uploadStatusFlow.value = LoadStatus.Loading()
|
||||
try {
|
||||
val response =
|
||||
client.submitFormWithBinaryData(url = FILE_UPLOAD_URL, formData = formData {
|
||||
append("\"file\"", zipFile.readBytes(), Headers.build {
|
||||
append(HttpHeaders.ContentType, "application/x-zip-compressed")
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"file.zip\"")
|
||||
})
|
||||
}) {
|
||||
onUpload { bytesSentTotal, contentLength ->
|
||||
if (uploadStatusFlow.value is LoadStatus.Loading) {
|
||||
uploadStatusFlow.value =
|
||||
LoadStatus.Loading(bytesSentTotal / contentLength.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.headers["X_RPC_OK"] == "true") {
|
||||
val policiesAsset = response.body<GithubPoliciesAsset>()
|
||||
uploadStatusFlow.value = LoadStatus.Success(policiesAsset)
|
||||
} else if (response.headers["X_RPC_OK"] == "false") {
|
||||
uploadStatusFlow.value = LoadStatus.Failure(response.body<RpcError>())
|
||||
} else {
|
||||
uploadStatusFlow.value = LoadStatus.Failure(Exception(response.bodyAsText()))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
uploadStatusFlow.value = LoadStatus.Failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val latestRecordFlow =
|
||||
DbSet.clickLogDao.queryLatest().stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
val latestRecordDescFlow = combine(
|
||||
|
|
|
@ -9,17 +9,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.filled.Upload
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -35,20 +31,12 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.blankj.utilcode.util.ClipboardUtils
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import com.ramcosta.composedestinations.navigation.navigate
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.update
|
||||
import li.songe.gkd.BuildConfig.ENABLED_UPDATE
|
||||
import li.songe.gkd.MainActivity
|
||||
import li.songe.gkd.ui.component.RotatingLoadingIcon
|
||||
import li.songe.gkd.ui.component.SettingItem
|
||||
import li.songe.gkd.ui.component.TextMenu
|
||||
|
@ -61,17 +49,12 @@ import li.songe.gkd.ui.style.itemPadding
|
|||
import li.songe.gkd.ui.style.titleItemPadding
|
||||
import li.songe.gkd.ui.theme.supportDynamicColor
|
||||
import li.songe.gkd.util.DarkThemeOption
|
||||
import li.songe.gkd.util.LoadStatus
|
||||
import li.songe.gkd.util.LocalMainViewModel
|
||||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.UpdateTimeOption
|
||||
import li.songe.gkd.util.buildLogFile
|
||||
import li.songe.gkd.util.checkUpdate
|
||||
import li.songe.gkd.util.findOption
|
||||
import li.songe.gkd.util.launchAsFn
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.saveFileToDownloads
|
||||
import li.songe.gkd.util.shareFile
|
||||
import li.songe.gkd.util.storeFlow
|
||||
import li.songe.gkd.util.throttle
|
||||
import li.songe.gkd.util.toast
|
||||
|
@ -82,12 +65,10 @@ val settingsNav = BottomNavItem(
|
|||
|
||||
@Composable
|
||||
fun useSettingsPage(): ScaffoldExt {
|
||||
val context = LocalContext.current as MainActivity
|
||||
val mainVm = LocalMainViewModel.current
|
||||
val navController = LocalNavController.current
|
||||
val store by storeFlow.collectAsState()
|
||||
val vm = hiltViewModel<HomeVm>()
|
||||
val uploadStatus by vm.uploadStatusFlow.collectAsState()
|
||||
|
||||
var showToastInputDlg by remember {
|
||||
mutableStateOf(false)
|
||||
|
@ -96,10 +77,6 @@ fun useSettingsPage(): ScaffoldExt {
|
|||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
var showShareLogDlg by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val checkUpdating by mainVm.updateStatus.checkUpdatingFlow.collectAsState()
|
||||
|
||||
if (showToastInputDlg) {
|
||||
|
@ -210,118 +187,6 @@ fun useSettingsPage(): ScaffoldExt {
|
|||
})
|
||||
}
|
||||
|
||||
if (showShareLogDlg) {
|
||||
Dialog(onDismissRequest = { showShareLogDlg = false }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
val modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
Text(
|
||||
text = "分享到其他应用", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.shareFile(logZipFile, "分享日志文件")
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "保存到下载", modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
context.saveFileToDownloads(logZipFile)
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "生成链接(需科学上网)",
|
||||
modifier = Modifier
|
||||
.clickable(onClick = throttle {
|
||||
showShareLogDlg = false
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logZipFile = buildLogFile()
|
||||
vm.uploadZip(logZipFile)
|
||||
}
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (val uploadStatusVal = uploadStatus) {
|
||||
is LoadStatus.Failure -> {
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传失败") },
|
||||
text = {
|
||||
Text(text = uploadStatusVal.exception.let {
|
||||
it.message ?: it.toString()
|
||||
})
|
||||
},
|
||||
onDismissRequest = { vm.uploadStatusFlow.value = null },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "关闭")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Loading -> {
|
||||
AlertDialog(
|
||||
title = { Text(text = "上传文件中") },
|
||||
text = {
|
||||
LinearProgressIndicator(
|
||||
progress = { uploadStatusVal.progress },
|
||||
)
|
||||
},
|
||||
onDismissRequest = { },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadJob?.cancel(CancellationException("终止上传"))
|
||||
vm.uploadJob = null
|
||||
}) {
|
||||
Text(text = "终止上传")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is LoadStatus.Success -> {
|
||||
AlertDialog(title = { Text(text = "上传完成") }, text = {
|
||||
Text(text = uploadStatusVal.result.shortHref)
|
||||
}, onDismissRequest = {}, dismissButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "关闭")
|
||||
}
|
||||
}, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
ClipboardUtils.copyText(uploadStatusVal.result.shortHref)
|
||||
toast("复制成功")
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "复制")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||
val scrollState = rememberScrollState()
|
||||
return ScaffoldExt(
|
||||
|
@ -470,37 +335,6 @@ fun useSettingsPage(): ScaffoldExt {
|
|||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "日志",
|
||||
modifier = Modifier.titleItemPadding(),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
|
||||
TextSwitch(name = "保存日志",
|
||||
desc = "保存7天日志,帮助定位BUG",
|
||||
checked = store.log2FileSwitch,
|
||||
onCheckedChange = {
|
||||
storeFlow.value = store.copy(
|
||||
log2FileSwitch = it
|
||||
)
|
||||
if (!it) {
|
||||
mainVm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
val logFiles = LogUtils.getLogFiles()
|
||||
if (logFiles.isNotEmpty()) {
|
||||
logFiles.forEach { f ->
|
||||
f.delete()
|
||||
}
|
||||
toast("已删除全部日志")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
SettingItem(title = "导出日志", imageVector = Icons.Default.Upload, onClick = {
|
||||
showShareLogDlg = true
|
||||
})
|
||||
|
||||
Text(
|
||||
text = "其它",
|
||||
modifier = Modifier.titleItemPadding(),
|
||||
|
|
Loading…
Reference in New Issue
Block a user