diff --git a/app/src/main/kotlin/li/songe/gkd/MainActivity.kt b/app/src/main/kotlin/li/songe/gkd/MainActivity.kt index 6948dab..c25bb35 100644 --- a/app/src/main/kotlin/li/songe/gkd/MainActivity.kt +++ b/app/src/main/kotlin/li/songe/gkd/MainActivity.kt @@ -44,6 +44,7 @@ import li.songe.gkd.service.fixRestartService import li.songe.gkd.service.updateLauncherAppId import li.songe.gkd.ui.component.BuildDialog import li.songe.gkd.ui.theme.AppTheme +import li.songe.gkd.util.EditGithubCookieDlg import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.UpgradeDialog import li.songe.gkd.util.appInfoCacheFlow @@ -93,6 +94,8 @@ class MainActivity : ComponentActivity() { ShizukuErrorDialog(mainVm.shizukuErrorFlow) AuthDialog(mainVm.authReasonFlow) BuildDialog(mainVm.dialogFlow) + mainVm.uploadOptions.ShowDialog() + EditGithubCookieDlg(mainVm.showEditCookieDlgFlow) if (META.updateEnabled) { UpgradeDialog(mainVm.updateStatus) } diff --git a/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt b/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt index c8c3b42..b6f3d81 100644 --- a/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt +++ b/app/src/main/kotlin/li/songe/gkd/MainViewModel.kt @@ -15,6 +15,7 @@ import li.songe.gkd.data.SubsItem import li.songe.gkd.db.DbSet import li.songe.gkd.permission.AuthReason 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.UpdateStatus import li.songe.gkd.util.checkUpdate @@ -43,6 +44,10 @@ class MainViewModel : ViewModel() { val shizukuErrorFlow = MutableStateFlow(false) + val uploadOptions = UploadOptions(this) + + val showEditCookieDlgFlow = MutableStateFlow(false) + init { viewModelScope.launchTry(Dispatchers.IO) { val subsItems = DbSet.subsItemDao.queryAll() diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt index f41211f..aed45c2 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AboutPage.kt @@ -48,7 +48,6 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import kotlinx.coroutines.Dispatchers @@ -88,10 +87,8 @@ import li.songe.gkd.util.toast fun AboutPage() { val navController = LocalNavController.current val context = LocalContext.current as MainActivity - val vm = viewModel() val store by storeFlow.collectAsState() - vm.uploadOptions.ShowDialog() var showInfoDlg by remember { mutableStateOf(false) } if (showInfoDlg) { AlertDialog( @@ -176,7 +173,9 @@ fun AboutPage() { modifier = Modifier .clickable(onClick = throttle { showShareLogDlg = false - vm.uploadOptions.startTask(getFile = { buildLogFile() }) + context.mainVm.uploadOptions.startTask( + getFile = { buildLogFile() } + ) }) .then(modifier) ) diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AboutVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/AboutVm.kt deleted file mode 100644 index 3bd8ad5..0000000 --- a/app/src/main/kotlin/li/songe/gkd/ui/AboutVm.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt index 4df8e02..dc20424 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt @@ -5,7 +5,6 @@ import android.content.Context import android.media.projection.MediaProjectionManager import android.os.Build import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -19,7 +18,6 @@ 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.automirrored.outlined.HelpOutline import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.AlertDialog 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.utils.toDestinationsNavigator import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.update import li.songe.gkd.MainActivity import li.songe.gkd.appScope 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.launchAsFn import li.songe.gkd.util.openUri -import li.songe.gkd.util.privacyStoreFlow import li.songe.gkd.util.storeFlow import li.songe.gkd.util.throttle 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() Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), @@ -454,7 +394,7 @@ fun AdvancedPage() { }, imageVector = Icons.Default.Edit, onClick = { - showEditCookieDlg = true + context.mainVm.showEditCookieDlgFlow.value = true } ) diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt index 91f5d51..9d0e9fd 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SnapshotPage.kt @@ -79,8 +79,6 @@ fun SnapshotPage() { val vm = viewModel() val snapshots by vm.snapshotsState.collectAsState() - vm.uploadOptions.ShowDialog() - var selectedSnapshot by remember { mutableStateOf(null) } @@ -246,8 +244,9 @@ fun SnapshotPage() { text = "生成链接(需科学上网)", modifier = Modifier .clickable(onClick = throttle { selectedSnapshot = null - vm.uploadOptions.startTask( + context.mainVm.uploadOptions.startTask( getFile = { SnapshotExt.getSnapshotZipFile(snapshotVal.id) }, + showHref = { IMPORT_SHORT_URL + it.id }, onSuccessResult = vm.viewModelScope.launchAsFn( Dispatchers.IO ) { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/SnapshotVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/SnapshotVm.kt index e7f5f60..c04c2ef 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/SnapshotVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/SnapshotVm.kt @@ -5,15 +5,8 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import li.songe.gkd.db.DbSet -import li.songe.gkd.ui.component.UploadOptions -import li.songe.gkd.util.IMPORT_SHORT_URL class SnapshotVm : ViewModel() { val snapshotsState = DbSet.snapshotDao.query() .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) - - val uploadOptions = UploadOptions( - scope = viewModelScope, - showHref = { IMPORT_SHORT_URL + it.id } - ) } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/ui/component/UploadOptions.kt b/app/src/main/kotlin/li/songe/gkd/ui/component/UploadOptions.kt index 7ce0993..b22d641 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/component/UploadOptions.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/component/UploadOptions.kt @@ -6,13 +6,15 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.lifecycle.viewModelScope import com.blankj.utilcode.util.ClipboardUtils -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow +import li.songe.gkd.MainViewModel import li.songe.gkd.data.GithubPoliciesAsset +import li.songe.gkd.util.GithubCookieException import li.songe.gkd.util.LoadStatus import li.songe.gkd.util.launchTry import li.songe.gkd.util.privacyStoreFlow @@ -21,8 +23,7 @@ import li.songe.gkd.util.uploadFileToGithub import java.io.File class UploadOptions( - private val scope: CoroutineScope, - private val showHref: (GithubPoliciesAsset) -> String = { it.shortHref } + private val mainVm: MainViewModel, ) { private val statusFlow = MutableStateFlow?>(null) private var job: Job? = null @@ -30,7 +31,7 @@ class UploadOptions( cookie: String, getFile: suspend () -> File, onSuccessResult: ((GithubPoliciesAsset) -> Unit)? - ) = scope.launchTry(Dispatchers.IO) { + ) = mainVm.viewModelScope.launchTry(Dispatchers.IO) { statusFlow.value = LoadStatus.Loading() try { val policiesAsset = uploadFileToGithub(cookie, getFile()) { @@ -47,18 +48,23 @@ class UploadOptions( } } + + private var showHref: (GithubPoliciesAsset) -> String = { it.shortHref } fun startTask( getFile: suspend () -> File, + showHref: (GithubPoliciesAsset) -> String = { it.shortHref }, onSuccessResult: ((GithubPoliciesAsset) -> Unit)? = null ) { val cookie = privacyStoreFlow.value.githubCookie if (cookie.isNullOrBlank()) { toast("请先设置 cookie 后再上传") + mainVm.showEditCookieDlgFlow.value = true return } if (job != null || statusFlow.value is LoadStatus.Loading) { return } + this.showHref = showHref job = buildTask(cookie, getFile, onSuccessResult) } @@ -123,6 +129,16 @@ class UploadOptions( }) }, 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 = { TextButton(onClick = { statusFlow.value = null diff --git a/app/src/main/kotlin/li/songe/gkd/util/Github.kt b/app/src/main/kotlin/li/songe/gkd/util/Github.kt index 39fe39b..0b1c4fc 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/Github.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/Github.kt @@ -1,5 +1,24 @@ 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.plugins.onUpload 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.HttpHeaders import io.ktor.http.HttpMessageBuilder +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import kotlinx.serialization.Serializable +import li.songe.gkd.app import li.songe.gkd.data.GithubPoliciesAsset import java.io.File @@ -50,12 +72,14 @@ private data class Authenticity( val authenticityToken: String, ) +data class GithubCookieException(override val message: String) : Exception(message) + private suspend fun getAuthenticity(cookie: String): Authenticity { val text = client.get(GITHUB_UPLOAD_URL) { setCommonHeaders(cookie) }.bodyAsText() if (!text.contains("data-login")) { - error("用户未登录, 请更换 cookie") + throw GithubCookieException("未检测到用户登录, 请更换 cookie") } val repositoryId = repositoryIdRegex.find(text)?.groupValues?.get(1) ?: error("repositoryId not found") @@ -114,3 +138,62 @@ suspend fun uploadFileToGithub( return policiesResp.asset } +@Composable +fun EditGithubCookieDlg(showEditCookieDlgFlow: MutableStateFlow) { + 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 = "取消") + } + } + ) + } +} +