perf: upload github file link

This commit is contained in:
二刺螈 2024-11-13 22:01:05 +08:00
parent c07d29e876
commit 53a035bcd9
9 changed files with 118 additions and 89 deletions

View File

@ -44,6 +44,7 @@ import li.songe.gkd.service.fixRestartService
import li.songe.gkd.service.updateLauncherAppId import li.songe.gkd.service.updateLauncherAppId
import li.songe.gkd.ui.component.BuildDialog import li.songe.gkd.ui.component.BuildDialog
import li.songe.gkd.ui.theme.AppTheme import li.songe.gkd.ui.theme.AppTheme
import li.songe.gkd.util.EditGithubCookieDlg
import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.LocalNavController
import li.songe.gkd.util.UpgradeDialog import li.songe.gkd.util.UpgradeDialog
import li.songe.gkd.util.appInfoCacheFlow import li.songe.gkd.util.appInfoCacheFlow
@ -93,6 +94,8 @@ class MainActivity : ComponentActivity() {
ShizukuErrorDialog(mainVm.shizukuErrorFlow) ShizukuErrorDialog(mainVm.shizukuErrorFlow)
AuthDialog(mainVm.authReasonFlow) AuthDialog(mainVm.authReasonFlow)
BuildDialog(mainVm.dialogFlow) BuildDialog(mainVm.dialogFlow)
mainVm.uploadOptions.ShowDialog()
EditGithubCookieDlg(mainVm.showEditCookieDlgFlow)
if (META.updateEnabled) { if (META.updateEnabled) {
UpgradeDialog(mainVm.updateStatus) UpgradeDialog(mainVm.updateStatus)
} }

View File

@ -15,6 +15,7 @@ import li.songe.gkd.data.SubsItem
import li.songe.gkd.db.DbSet import li.songe.gkd.db.DbSet
import li.songe.gkd.permission.AuthReason import li.songe.gkd.permission.AuthReason
import li.songe.gkd.ui.component.AlertDialogOptions import li.songe.gkd.ui.component.AlertDialogOptions
import li.songe.gkd.ui.component.UploadOptions
import li.songe.gkd.util.LOCAL_SUBS_ID import li.songe.gkd.util.LOCAL_SUBS_ID
import li.songe.gkd.util.UpdateStatus import li.songe.gkd.util.UpdateStatus
import li.songe.gkd.util.checkUpdate import li.songe.gkd.util.checkUpdate
@ -43,6 +44,10 @@ class MainViewModel : ViewModel() {
val shizukuErrorFlow = MutableStateFlow(false) val shizukuErrorFlow = MutableStateFlow(false)
val uploadOptions = UploadOptions(this)
val showEditCookieDlgFlow = MutableStateFlow(false)
init { init {
viewModelScope.launchTry(Dispatchers.IO) { viewModelScope.launchTry(Dispatchers.IO) {
val subsItems = DbSet.subsItemDao.queryAll() val subsItems = DbSet.subsItemDao.queryAll()

View File

@ -48,7 +48,6 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -88,10 +87,8 @@ import li.songe.gkd.util.toast
fun AboutPage() { fun AboutPage() {
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalContext.current as MainActivity val context = LocalContext.current as MainActivity
val vm = viewModel<AboutVm>()
val store by storeFlow.collectAsState() val store by storeFlow.collectAsState()
vm.uploadOptions.ShowDialog()
var showInfoDlg by remember { mutableStateOf(false) } var showInfoDlg by remember { mutableStateOf(false) }
if (showInfoDlg) { if (showInfoDlg) {
AlertDialog( AlertDialog(
@ -176,7 +173,9 @@ fun AboutPage() {
modifier = Modifier modifier = Modifier
.clickable(onClick = throttle { .clickable(onClick = throttle {
showShareLogDlg = false showShareLogDlg = false
vm.uploadOptions.startTask(getFile = { buildLogFile() }) context.mainVm.uploadOptions.startTask(
getFile = { buildLogFile() }
)
}) })
.then(modifier) .then(modifier)
) )

View File

@ -1,9 +0,0 @@
package li.songe.gkd.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import li.songe.gkd.ui.component.UploadOptions
class AboutVm : ViewModel() {
val uploadOptions = UploadOptions(viewModelScope)
}

View File

@ -5,7 +5,6 @@ import android.content.Context
import android.media.projection.MediaProjectionManager import android.media.projection.MediaProjectionManager
import android.os.Build import android.os.Build
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -19,7 +18,6 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -58,7 +56,6 @@ import com.ramcosta.composedestinations.generated.destinations.ActivityLogPageDe
import com.ramcosta.composedestinations.generated.destinations.SnapshotPageDestination import com.ramcosta.composedestinations.generated.destinations.SnapshotPageDestination
import com.ramcosta.composedestinations.utils.toDestinationsNavigator import com.ramcosta.composedestinations.utils.toDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.update
import li.songe.gkd.MainActivity import li.songe.gkd.MainActivity
import li.songe.gkd.appScope import li.songe.gkd.appScope
import li.songe.gkd.debug.FloatingService import li.songe.gkd.debug.FloatingService
@ -81,7 +78,6 @@ import li.songe.gkd.util.LocalNavController
import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.ProfileTransitions
import li.songe.gkd.util.launchAsFn import li.songe.gkd.util.launchAsFn
import li.songe.gkd.util.openUri import li.songe.gkd.util.openUri
import li.songe.gkd.util.privacyStoreFlow
import li.songe.gkd.util.storeFlow import li.songe.gkd.util.storeFlow
import li.songe.gkd.util.throttle import li.songe.gkd.util.throttle
import li.songe.gkd.util.toast import li.songe.gkd.util.toast
@ -155,62 +151,6 @@ fun AdvancedPage() {
}) })
} }
var showEditCookieDlg by remember { mutableStateOf(false) }
if (showEditCookieDlg) {
val privacyStore by privacyStoreFlow.collectAsState()
var value by remember {
mutableStateOf(privacyStore.githubCookie ?: "")
}
AlertDialog(
onDismissRequest = {
if (value.isEmpty()) {
showEditCookieDlg = false
}
},
title = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth(),
) {
Text(text = "Github Cookie")
IconButton(onClick = throttle {
context.openUri("https://gkd.li/?r=1")
}) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
contentDescription = null,
)
}
}
},
text = {
OutlinedTextField(
value = value,
onValueChange = {
value = it.filter { c -> c != '\n' && c != '\r' }
},
placeholder = { Text(text = "请输入 Github Cookie") },
modifier = Modifier.fillMaxWidth(),
maxLines = 10,
)
},
confirmButton = {
TextButton(onClick = {
showEditCookieDlg = false
privacyStoreFlow.update { it.copy(githubCookie = value.trim()) }
}) {
Text(text = "确认")
}
},
dismissButton = {
TextButton(onClick = { showEditCookieDlg = false }) {
Text(text = "取消")
}
}
)
}
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
Scaffold( Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@ -454,7 +394,7 @@ fun AdvancedPage() {
}, },
imageVector = Icons.Default.Edit, imageVector = Icons.Default.Edit,
onClick = { onClick = {
showEditCookieDlg = true context.mainVm.showEditCookieDlgFlow.value = true
} }
) )

View File

@ -79,8 +79,6 @@ fun SnapshotPage() {
val vm = viewModel<SnapshotVm>() val vm = viewModel<SnapshotVm>()
val snapshots by vm.snapshotsState.collectAsState() val snapshots by vm.snapshotsState.collectAsState()
vm.uploadOptions.ShowDialog()
var selectedSnapshot by remember { var selectedSnapshot by remember {
mutableStateOf<Snapshot?>(null) mutableStateOf<Snapshot?>(null)
} }
@ -246,8 +244,9 @@ fun SnapshotPage() {
text = "生成链接(需科学上网)", modifier = Modifier text = "生成链接(需科学上网)", modifier = Modifier
.clickable(onClick = throttle { .clickable(onClick = throttle {
selectedSnapshot = null selectedSnapshot = null
vm.uploadOptions.startTask( context.mainVm.uploadOptions.startTask(
getFile = { SnapshotExt.getSnapshotZipFile(snapshotVal.id) }, getFile = { SnapshotExt.getSnapshotZipFile(snapshotVal.id) },
showHref = { IMPORT_SHORT_URL + it.id },
onSuccessResult = vm.viewModelScope.launchAsFn<GithubPoliciesAsset>( onSuccessResult = vm.viewModelScope.launchAsFn<GithubPoliciesAsset>(
Dispatchers.IO Dispatchers.IO
) { ) {

View File

@ -5,15 +5,8 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import li.songe.gkd.db.DbSet import li.songe.gkd.db.DbSet
import li.songe.gkd.ui.component.UploadOptions
import li.songe.gkd.util.IMPORT_SHORT_URL
class SnapshotVm : ViewModel() { class SnapshotVm : ViewModel() {
val snapshotsState = DbSet.snapshotDao.query() val snapshotsState = DbSet.snapshotDao.query()
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
val uploadOptions = UploadOptions(
scope = viewModelScope,
showHref = { IMPORT_SHORT_URL + it.id }
)
} }

View File

@ -6,13 +6,15 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.lifecycle.viewModelScope
import com.blankj.utilcode.util.ClipboardUtils import com.blankj.utilcode.util.ClipboardUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import li.songe.gkd.MainViewModel
import li.songe.gkd.data.GithubPoliciesAsset import li.songe.gkd.data.GithubPoliciesAsset
import li.songe.gkd.util.GithubCookieException
import li.songe.gkd.util.LoadStatus import li.songe.gkd.util.LoadStatus
import li.songe.gkd.util.launchTry import li.songe.gkd.util.launchTry
import li.songe.gkd.util.privacyStoreFlow import li.songe.gkd.util.privacyStoreFlow
@ -21,8 +23,7 @@ import li.songe.gkd.util.uploadFileToGithub
import java.io.File import java.io.File
class UploadOptions( class UploadOptions(
private val scope: CoroutineScope, private val mainVm: MainViewModel,
private val showHref: (GithubPoliciesAsset) -> String = { it.shortHref }
) { ) {
private val statusFlow = MutableStateFlow<LoadStatus<GithubPoliciesAsset>?>(null) private val statusFlow = MutableStateFlow<LoadStatus<GithubPoliciesAsset>?>(null)
private var job: Job? = null private var job: Job? = null
@ -30,7 +31,7 @@ class UploadOptions(
cookie: String, cookie: String,
getFile: suspend () -> File, getFile: suspend () -> File,
onSuccessResult: ((GithubPoliciesAsset) -> Unit)? onSuccessResult: ((GithubPoliciesAsset) -> Unit)?
) = scope.launchTry(Dispatchers.IO) { ) = mainVm.viewModelScope.launchTry(Dispatchers.IO) {
statusFlow.value = LoadStatus.Loading() statusFlow.value = LoadStatus.Loading()
try { try {
val policiesAsset = uploadFileToGithub(cookie, getFile()) { val policiesAsset = uploadFileToGithub(cookie, getFile()) {
@ -47,18 +48,23 @@ class UploadOptions(
} }
} }
private var showHref: (GithubPoliciesAsset) -> String = { it.shortHref }
fun startTask( fun startTask(
getFile: suspend () -> File, getFile: suspend () -> File,
showHref: (GithubPoliciesAsset) -> String = { it.shortHref },
onSuccessResult: ((GithubPoliciesAsset) -> Unit)? = null onSuccessResult: ((GithubPoliciesAsset) -> Unit)? = null
) { ) {
val cookie = privacyStoreFlow.value.githubCookie val cookie = privacyStoreFlow.value.githubCookie
if (cookie.isNullOrBlank()) { if (cookie.isNullOrBlank()) {
toast("请先设置 cookie 后再上传") toast("请先设置 cookie 后再上传")
mainVm.showEditCookieDlgFlow.value = true
return return
} }
if (job != null || statusFlow.value is LoadStatus.Loading) { if (job != null || statusFlow.value is LoadStatus.Loading) {
return return
} }
this.showHref = showHref
job = buildTask(cookie, getFile, onSuccessResult) job = buildTask(cookie, getFile, onSuccessResult)
} }
@ -123,6 +129,16 @@ class UploadOptions(
}) })
}, },
onDismissRequest = { statusFlow.value = null }, onDismissRequest = { statusFlow.value = null },
dismissButton = if (status.exception is GithubCookieException) ({
TextButton(onClick = {
statusFlow.value = null
mainVm.showEditCookieDlgFlow.value = true
}) {
Text(text = "更换 Cookie")
}
}) else {
null
},
confirmButton = { confirmButton = {
TextButton(onClick = { TextButton(onClick = {
statusFlow.value = null statusFlow.value = null

View File

@ -1,5 +1,24 @@
package li.songe.gkd.util package li.songe.gkd.util
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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 io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.plugins.onUpload import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.MultiPartFormDataContent import io.ktor.client.request.forms.MultiPartFormDataContent
@ -13,7 +32,10 @@ import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers import io.ktor.http.Headers
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMessageBuilder import io.ktor.http.HttpMessageBuilder
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import li.songe.gkd.app
import li.songe.gkd.data.GithubPoliciesAsset import li.songe.gkd.data.GithubPoliciesAsset
import java.io.File import java.io.File
@ -50,12 +72,14 @@ private data class Authenticity(
val authenticityToken: String, val authenticityToken: String,
) )
data class GithubCookieException(override val message: String) : Exception(message)
private suspend fun getAuthenticity(cookie: String): Authenticity { private suspend fun getAuthenticity(cookie: String): Authenticity {
val text = client.get(GITHUB_UPLOAD_URL) { val text = client.get(GITHUB_UPLOAD_URL) {
setCommonHeaders(cookie) setCommonHeaders(cookie)
}.bodyAsText() }.bodyAsText()
if (!text.contains("data-login")) { if (!text.contains("data-login")) {
error("用户未登录, 请更换 cookie") throw GithubCookieException("未检测到用户登录, 请更换 cookie")
} }
val repositoryId = val repositoryId =
repositoryIdRegex.find(text)?.groupValues?.get(1) ?: error("repositoryId not found") repositoryIdRegex.find(text)?.groupValues?.get(1) ?: error("repositoryId not found")
@ -114,3 +138,62 @@ suspend fun uploadFileToGithub(
return policiesResp.asset return policiesResp.asset
} }
@Composable
fun EditGithubCookieDlg(showEditCookieDlgFlow: MutableStateFlow<Boolean>) {
val showEditCookieDlg by showEditCookieDlgFlow.collectAsState()
if (showEditCookieDlg) {
val privacyStore by privacyStoreFlow.collectAsState()
var value by remember {
mutableStateOf(privacyStore.githubCookie ?: "")
}
AlertDialog(
onDismissRequest = {
if (value.isEmpty()) {
showEditCookieDlgFlow.value = false
}
},
title = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth(),
) {
Text(text = "Github Cookie")
IconButton(onClick = throttle {
app.openUri("https://gkd.li/?r=1")
}) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
contentDescription = null,
)
}
}
},
text = {
OutlinedTextField(
value = value,
onValueChange = {
value = it.filter { c -> c != '\n' && c != '\r' }
},
placeholder = { Text(text = "请输入 Github Cookie") },
modifier = Modifier.fillMaxWidth(),
maxLines = 10,
)
},
confirmButton = {
TextButton(onClick = {
showEditCookieDlgFlow.value = false
privacyStoreFlow.update { it.copy(githubCookie = value.trim()) }
}) {
Text(text = "确认")
}
},
dismissButton = {
TextButton(onClick = { showEditCookieDlgFlow.value = false }) {
Text(text = "取消")
}
}
)
}
}