mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 19:57:15 +08:00
feat: 上传快照生成链接
This commit is contained in:
parent
3b67d31a3e
commit
6e5d6b3e26
|
@ -0,0 +1,9 @@
|
|||
package li.songe.gkd.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GithubPoliciesAsset(
|
||||
val id: Int,
|
||||
val href: String,
|
||||
)
|
|
@ -49,7 +49,7 @@ object SnapshotExt {
|
|||
suspend fun getSnapshotZipFile(snapshotId: Long): File {
|
||||
val file = File(snapshotZipDir, "${snapshotId}.zip")
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
return file
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
ZipUtils.zipFiles(
|
||||
|
|
|
@ -21,8 +21,11 @@ import androidx.compose.foundation.layout.width
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -30,21 +33,25 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.blankj.utilcode.util.ClipboardUtils
|
||||
import com.blankj.utilcode.util.ImageUtils
|
||||
import com.blankj.utilcode.util.ToastUtils
|
||||
import com.blankj.utilcode.util.UriUtils
|
||||
import com.dylanc.activityresult.launcher.launchForResult
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.withContext
|
||||
import li.songe.gkd.data.Snapshot
|
||||
|
@ -52,6 +59,7 @@ import li.songe.gkd.db.DbSet
|
|||
import li.songe.gkd.debug.SnapshotExt
|
||||
import li.songe.gkd.ui.component.SimpleTopAppBar
|
||||
import li.songe.gkd.ui.destinations.ImagePreviewPageDestination
|
||||
import li.songe.gkd.util.LoadStatus
|
||||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.LocalPickContentLauncher
|
||||
import li.songe.gkd.util.LocalRequestPermissionLauncher
|
||||
|
@ -59,6 +67,10 @@ import li.songe.gkd.util.ProfileTransitions
|
|||
import li.songe.gkd.util.format
|
||||
import li.songe.gkd.util.launchAsFn
|
||||
import li.songe.gkd.util.navigate
|
||||
import li.songe.gkd.util.recordStoreFlow
|
||||
import li.songe.gkd.util.snapshotZipDir
|
||||
import li.songe.gkd.util.updateStorage
|
||||
import java.io.File
|
||||
|
||||
@RootNavGraph
|
||||
@Destination(style = ProfileTransitions::class)
|
||||
|
@ -73,6 +85,8 @@ fun SnapshotPage() {
|
|||
|
||||
val vm = hiltViewModel<SnapshotVm>()
|
||||
val snapshots by vm.snapshotsState.collectAsState()
|
||||
val uploadStatus by vm.uploadStatusFlow.collectAsState()
|
||||
val recordStore by recordStoreFlow.collectAsState()
|
||||
|
||||
var selectedSnapshot by remember {
|
||||
mutableStateOf<Snapshot?>(null)
|
||||
|
@ -143,7 +157,7 @@ fun SnapshotPage() {
|
|||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "分享(注意隐私信息)",
|
||||
text = "分享",
|
||||
modifier = Modifier
|
||||
.clickable(onClick = vm.viewModelScope.launchAsFn {
|
||||
val zipFile = SnapshotExt.getSnapshotZipFile(snapshotVal.id)
|
||||
|
@ -162,6 +176,27 @@ fun SnapshotPage() {
|
|||
})
|
||||
.then(modifier)
|
||||
)
|
||||
if (recordStore.snapshotIdMap.containsKey(snapshotVal.id)) {
|
||||
Text(
|
||||
text = "复制链接", modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
selectedSnapshot = null
|
||||
ClipboardUtils.copyText("https://gkd-kit.gitee.io/import/" + recordStore.snapshotIdMap[snapshotVal.id])
|
||||
ToastUtils.showShort("复制成功")
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "生成链接(需科学上网)", modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
selectedSnapshot = null
|
||||
vm.uploadZip(snapshotVal)
|
||||
})
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "保存截图到相册",
|
||||
modifier = Modifier
|
||||
|
@ -185,7 +220,7 @@ fun SnapshotPage() {
|
|||
.then(modifier)
|
||||
)
|
||||
Text(
|
||||
text = "从相册替换截图",
|
||||
text = "替换截图(去除隐私)",
|
||||
modifier = Modifier
|
||||
.clickable(onClick = vm.viewModelScope.launchAsFn {
|
||||
val uri = pickContentLauncher.launchForImageResult()
|
||||
|
@ -195,6 +230,19 @@ fun SnapshotPage() {
|
|||
val newBitmap = ImageUtils.getBitmap(newBytes, 0)
|
||||
if (oldBitmap.width == newBitmap.width && oldBitmap.height == newBitmap.height) {
|
||||
snapshotVal.screenshotFile.writeBytes(newBytes)
|
||||
File(snapshotZipDir, "${snapshotVal.id}.zip").apply {
|
||||
if (exists()) delete()
|
||||
}
|
||||
if (recordStore.snapshotIdMap.containsKey(snapshotVal.id)) {
|
||||
updateStorage(
|
||||
recordStoreFlow,
|
||||
recordStore.copy(snapshotIdMap = recordStore.snapshotIdMap
|
||||
.toMutableMap()
|
||||
.apply {
|
||||
remove(snapshotVal.id)
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ToastUtils.showShort("截图尺寸不一致,无法替换")
|
||||
return@withContext
|
||||
|
@ -211,6 +259,16 @@ fun SnapshotPage() {
|
|||
DbSet.snapshotDao.delete(snapshotVal)
|
||||
withContext(IO) {
|
||||
SnapshotExt.remove(snapshotVal.id)
|
||||
if (recordStore.snapshotIdMap.containsKey(snapshotVal.id)) {
|
||||
updateStorage(
|
||||
recordStoreFlow,
|
||||
recordStore.copy(snapshotIdMap = recordStore.snapshotIdMap
|
||||
.toMutableMap()
|
||||
.apply {
|
||||
remove(snapshotVal.id)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
selectedSnapshot = null
|
||||
})
|
||||
|
@ -220,6 +278,81 @@ 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 -> {
|
||||
Dialog(onDismissRequest = { }) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "上传文件中,请稍等",
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
LinearProgressIndicator(progress = uploadStatusVal.progress)
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
vm.uploadJob?.cancel(CancellationException("终止上传"))
|
||||
vm.uploadJob = null
|
||||
}) {
|
||||
Text(text = "终止上传", color = Color.Red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LoadStatus.Success -> {
|
||||
AlertDialog(title = { Text(text = "上传完成") }, text = {
|
||||
Text(text = "https://gkd-kit.gitee.io/import/" + uploadStatusVal.result.id)
|
||||
}, onDismissRequest = {}, dismissButton = {
|
||||
TextButton(onClick = {
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "关闭")
|
||||
}
|
||||
}, confirmButton = {
|
||||
TextButton(onClick = {
|
||||
ClipboardUtils.copyText("https://gkd-kit.gitee.io/import/" + uploadStatusVal.result.id)
|
||||
ToastUtils.showShort("复制成功")
|
||||
vm.uploadStatusFlow.value = null
|
||||
}) {
|
||||
Text(text = "复制")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,14 +3,78 @@ 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.map
|
||||
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.LoadStatus
|
||||
import li.songe.gkd.util.Singleton
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.recordStoreFlow
|
||||
import li.songe.gkd.util.updateStorage
|
||||
import javax.inject.Inject
|
||||
|
||||
const val FILE_UPLOAD_URL = "https://github-upload-assets.lisonge.workers.dev/"
|
||||
|
||||
@HiltViewModel
|
||||
class SnapshotVm @Inject constructor() : ViewModel() {
|
||||
val snapshotsState = DbSet.snapshotDao.query().map { it.reversed() }
|
||||
.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 = Singleton.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)
|
||||
updateStorage(
|
||||
recordStoreFlow,
|
||||
recordStoreFlow.value.copy(snapshotIdMap = recordStoreFlow.value.snapshotIdMap.toMutableMap()
|
||||
.apply {
|
||||
set(snapshot.id, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
app/src/main/java/li/songe/gkd/util/Constants.kt
Normal file
3
app/src/main/java/li/songe/gkd/util/Constants.kt
Normal file
|
@ -0,0 +1,3 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"
|
Loading…
Reference in New Issue
Block a user