Compare commits
No commits in common. "dev" and "debug-build" have entirely different histories.
dev
...
debug-buil
@ -76,16 +76,6 @@
|
||||
android:name="miuiWidgetVersion"
|
||||
android:value="1" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -40,9 +40,7 @@ class CredentialRepository @Inject constructor(
|
||||
val revision: Long,
|
||||
val fingerprint: String,
|
||||
/** 快照所属账号;单账号服务为 null。 */
|
||||
val accountId: String? = null,
|
||||
/** 创建快照时调用方请求的账号 ID(多账号服务可能是 null=默认账号),用于会话内精确匹配。 */
|
||||
val requestedAccountId: String? = accountId
|
||||
val accountId: String? = null
|
||||
) {
|
||||
val service: ServiceType get() = credential.service
|
||||
}
|
||||
@ -239,9 +237,9 @@ class CredentialRepository @Inject constructor(
|
||||
if (session.snapshot.service == service) {
|
||||
val matches = if (ServiceType.isMultiAccount(service)) {
|
||||
val requested = accountId?.takeIf { it.isNotBlank() }
|
||||
// 只允许读取与快照同一账号的暂存凭据;null=默认账号也要看快照当初请求的是不是默认账号,
|
||||
// 避免 A 账号刷新期间把 A 的暂存凭据误交给默认账号(B)的读请求。
|
||||
requested == session.snapshot.requestedAccountId
|
||||
requested == null ||
|
||||
session.snapshot.accountId == null ||
|
||||
session.snapshot.accountId == requested
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@ -370,8 +368,7 @@ class CredentialRepository @Inject constructor(
|
||||
credential = credential,
|
||||
revision = revisionFor(service, resolvedId),
|
||||
fingerprint = credentialFingerprint(credential)!!,
|
||||
accountId = resolvedId,
|
||||
requestedAccountId = accountId?.takeIf { it.isNotBlank() }
|
||||
accountId = resolvedId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,112 +0,0 @@
|
||||
package com.rainy.token.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import com.rainy.token.BuildConfig
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
||||
/**
|
||||
* 应用更新仓库:从 Gitea 仓库的 update.json 读取版本清单,
|
||||
* 下载 APK 到应用缓存目录供 FileProvider 安装。
|
||||
*/
|
||||
@Singleton
|
||||
class UpdateRepository @Inject constructor(
|
||||
private val okHttpClient: OkHttpClient,
|
||||
@ApplicationContext context: Context
|
||||
) {
|
||||
private val appContext = context.applicationContext
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
/** 检查是否有新版本。null = 已是最新;非空 = 远端有更新。 */
|
||||
suspend fun checkForUpdate(): Result<UpdateInfo?> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val request = Request.Builder()
|
||||
.url(UPDATE_MANIFEST_URL)
|
||||
.get()
|
||||
.build()
|
||||
val text = okHttpClient.newCall(request).execute().use { resp ->
|
||||
if (!resp.isSuccessful) {
|
||||
return@withContext Result.failure(IOException("HTTP " + resp.code))
|
||||
}
|
||||
resp.body?.string().orEmpty()
|
||||
}
|
||||
val manifest = json.decodeFromString<UpdateManifest>(text)
|
||||
val hasUpdate = manifest.versionCode > BuildConfig.VERSION_CODE
|
||||
Result.success(if (hasUpdate) {
|
||||
UpdateInfo(
|
||||
versionCode = manifest.versionCode,
|
||||
versionName = manifest.versionName,
|
||||
apkUrl = manifest.apkUrl,
|
||||
notes = manifest.notes
|
||||
)
|
||||
} else {
|
||||
null
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
/** 下载 APK 到缓存目录。onProgress 回调下载字节数/总字节数(total 可能为 -1)。 */
|
||||
suspend fun downloadApk(
|
||||
info: UpdateInfo,
|
||||
onProgress: (downloaded: Long, total: Long) -> Unit
|
||||
): Result<File> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val dir = File(appContext.cacheDir, "apk_downloads").apply { mkdirs() }
|
||||
val file = File(dir, "rainytoken-" + info.versionName + ".apk")
|
||||
val request = Request.Builder().url(info.apkUrl).get().build()
|
||||
okHttpClient.newCall(request).execute().use { resp ->
|
||||
if (!resp.isSuccessful) {
|
||||
return@withContext Result.failure(IOException("HTTP " + resp.code))
|
||||
}
|
||||
val body = resp.body ?: return@withContext Result.failure(IOException("empty body"))
|
||||
val total = body.contentLength()
|
||||
file.outputStream().buffered().use { output ->
|
||||
body.byteStream().use { input ->
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
var downloaded = 0L
|
||||
var read: Int
|
||||
while (input.read(buffer).also { read = it } != -1) {
|
||||
output.write(buffer, 0, read)
|
||||
downloaded += read
|
||||
onProgress(downloaded, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Result.success(file)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class UpdateManifest(
|
||||
val versionCode: Int,
|
||||
val versionName: String,
|
||||
val apkUrl: String,
|
||||
val notes: String = ""
|
||||
)
|
||||
|
||||
data class UpdateInfo(
|
||||
val versionCode: Int,
|
||||
val versionName: String,
|
||||
val apkUrl: String,
|
||||
val notes: String
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val UPDATE_MANIFEST_URL =
|
||||
"http://124.222.97.212:8443/maya/Rainytoken/raw/branch/dev/update.json"
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,6 @@ import com.rainy.token.data.repository.OpenCodeUsageRepository
|
||||
import com.rainy.token.data.repository.OllamaRepository
|
||||
import com.rainy.token.data.repository.Sub2ApiRepository
|
||||
import com.rainy.token.data.repository.TraeRepository
|
||||
import com.rainy.token.data.repository.UpdateRepository
|
||||
import com.rainy.token.data.repository.WorkBuddyRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@ -205,16 +204,6 @@ object NetworkModule {
|
||||
/**
|
||||
* Sub2API 仓库:自托管实例(凭据里的 baseUrl)+ 管理面板 API。
|
||||
*/
|
||||
/**
|
||||
* 更新仓库:读取 Gitea 上的 update.json 并下载 APK。
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateRepository(
|
||||
okHttpClient: OkHttpClient,
|
||||
@ApplicationContext context: Context
|
||||
): UpdateRepository = UpdateRepository(okHttpClient, context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSub2ApiRepository(
|
||||
|
||||
@ -102,8 +102,6 @@ import com.rainy.token.ui.components.AppTips
|
||||
import com.rainy.token.ui.components.asString
|
||||
import com.rainy.token.ui.dashboard.autocheckin.AutoCheckinManager
|
||||
import com.rainy.token.ui.theme.inkMuted
|
||||
import com.rainy.token.ui.theme.PureWhite
|
||||
import com.rainy.token.ui.theme.inkWarm
|
||||
import com.rainy.token.ui.theme.StrawberryPink
|
||||
import android.app.AppOpsManager
|
||||
import android.appwidget.AppWidgetManager
|
||||
@ -349,45 +347,28 @@ fun DashboardScreen(
|
||||
// 随机小技巧提示(每次启动随机一条)+ 最右侧自动签到开关
|
||||
val tipTextRes = remember { AppTips.randomHintRes() }
|
||||
val autoCheckinEnabled by viewModel.autoCheckinEnabled.collectAsStateWithLifecycle()
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = 4.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "💡 " + stringResource(tipTextRes),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = inkMuted(),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.auto_checkin_switch_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = inkWarm(),
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Switch(
|
||||
checked = autoCheckinEnabled,
|
||||
onCheckedChange = viewModel::setAutoCheckinEnabled,
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = PureWhite,
|
||||
checkedTrackColor = StrawberryPink,
|
||||
checkedBorderColor = StrawberryPink,
|
||||
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
uncheckedTrackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
uncheckedBorderColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "💡 " + stringResource(tipTextRes),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = inkMuted(),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.auto_checkin_switch_label),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = inkMuted()
|
||||
)
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Switch(
|
||||
checked = autoCheckinEnabled,
|
||||
onCheckedChange = viewModel::setAutoCheckinEnabled,
|
||||
colors = SwitchDefaults.colors(checkedThumbColor = StrawberryPink)
|
||||
)
|
||||
}
|
||||
val items = rememberDashboardItems(
|
||||
cards = uiState.cards,
|
||||
@ -941,8 +922,6 @@ private fun DashboardCard(
|
||||
text = stringResource(R.string.account_default_badge),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = StrawberryPink,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(StrawberryPink.copy(alpha = 0.12f))
|
||||
|
||||
@ -296,8 +296,6 @@ private fun AccountRow(
|
||||
text = stringResource(R.string.account_default_badge),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = StrawberryPink,
|
||||
maxLines = 1,
|
||||
softWrap = false,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
.background(
|
||||
|
||||
@ -54,11 +54,6 @@ import com.rainy.token.ui.components.ServiceIcon
|
||||
import com.rainy.token.ui.components.StatusChip
|
||||
import com.rainy.token.ui.components.StatusLevel
|
||||
import com.rainy.token.ui.components.StatusStyle
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import com.rainy.token.R
|
||||
import com.rainy.token.ui.components.AppTips
|
||||
import com.rainy.token.ui.theme.InkMuted
|
||||
@ -165,9 +160,6 @@ fun SettingsScreen(
|
||||
item {
|
||||
DebugLogCard(onClick = { onOpenDebugLog() })
|
||||
}
|
||||
item {
|
||||
UpdateCard()
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
AboutCard()
|
||||
@ -318,142 +310,6 @@ private fun CredentialStatusCard(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UpdateCard(
|
||||
viewModel: UpdateViewModel = hiltViewModel()
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(20.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.title_update),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.update_current_version,
|
||||
state.currentVersionName,
|
||||
state.currentVersionCode
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = InkMuted
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 12.dp))
|
||||
when (val phase = state.phase) {
|
||||
is UpdateViewModel.Phase.Idle -> Unit
|
||||
is UpdateViewModel.Phase.Checking -> {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.width(18.dp).height(18.dp),
|
||||
color = StrawberryPink,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.update_checking),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = InkMuted
|
||||
)
|
||||
}
|
||||
}
|
||||
is UpdateViewModel.Phase.Latest -> {
|
||||
Text(
|
||||
text = stringResource(R.string.update_latest),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = InkMuted
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
TextButton(onClick = { viewModel.checkForUpdate() }) {
|
||||
Text(stringResource(R.string.update_check))
|
||||
}
|
||||
}
|
||||
is UpdateViewModel.Phase.Available -> {
|
||||
Text(
|
||||
text = stringResource(R.string.update_available, phase.info.versionName),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = StrawberryPink,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
if (phase.info.notes.isNotBlank()) {
|
||||
Spacer(modifier = Modifier.padding(top = 4.dp))
|
||||
Text(
|
||||
text = phase.info.notes,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = InkMuted
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(top = 12.dp))
|
||||
Button(onClick = { viewModel.downloadAndInstall(phase.info) }) {
|
||||
Text(stringResource(R.string.update_download))
|
||||
}
|
||||
}
|
||||
is UpdateViewModel.Phase.Downloading -> {
|
||||
LinearProgressIndicator(
|
||||
progress = { phase.progress },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 4.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.update_downloading,
|
||||
(phase.progress * 100).toInt()
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = InkMuted
|
||||
)
|
||||
}
|
||||
is UpdateViewModel.Phase.Ready -> {
|
||||
Text(
|
||||
text = stringResource(R.string.update_download_finished),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = InkMuted
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
Button(onClick = { viewModel.install(phase.file) }) {
|
||||
Text(stringResource(R.string.update_install))
|
||||
}
|
||||
}
|
||||
is UpdateViewModel.Phase.NeedInstallPermission -> {
|
||||
Text(
|
||||
text = stringResource(R.string.update_install_permission_tip),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
Button(onClick = {
|
||||
val intent = Intent(
|
||||
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
|
||||
Uri.parse("package:" + context.packageName)
|
||||
)
|
||||
runCatching { context.startActivity(intent) }
|
||||
}) {
|
||||
Text(stringResource(R.string.update_enable_permission))
|
||||
}
|
||||
}
|
||||
is UpdateViewModel.Phase.Error -> {
|
||||
Text(
|
||||
text = stringResource(R.string.update_failed, phase.message),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||
TextButton(onClick = { viewModel.checkForUpdate() }) {
|
||||
Text(stringResource(R.string.update_check))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AboutCard() {
|
||||
Card(
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
package com.rainy.token.ui.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rainy.token.BuildConfig
|
||||
import com.rainy.token.data.repository.UpdateRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** 设置页更新模块:检查更新 → 下载 APK → 拉起系统安装器。 */
|
||||
@HiltViewModel
|
||||
class UpdateViewModel @Inject constructor(
|
||||
private val updateRepository: UpdateRepository,
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
sealed interface Phase {
|
||||
object Idle : Phase
|
||||
object Checking : Phase
|
||||
object Latest : Phase
|
||||
data class Available(val info: UpdateRepository.UpdateInfo) : Phase
|
||||
data class Downloading(val progress: Float) : Phase
|
||||
data class Ready(val file: File) : Phase
|
||||
object NeedInstallPermission : Phase
|
||||
data class Error(val message: String) : Phase
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
val phase: Phase = Phase.Idle,
|
||||
val currentVersionName: String = BuildConfig.VERSION_NAME,
|
||||
val currentVersionCode: Int = BuildConfig.VERSION_CODE
|
||||
)
|
||||
|
||||
private val _uiState = MutableStateFlow(UiState())
|
||||
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
|
||||
|
||||
init {
|
||||
checkForUpdate()
|
||||
}
|
||||
|
||||
fun checkForUpdate() {
|
||||
_uiState.update { it.copy(phase = Phase.Checking) }
|
||||
viewModelScope.launch {
|
||||
updateRepository.checkForUpdate().fold(
|
||||
onSuccess = { info ->
|
||||
_uiState.update {
|
||||
it.copy(phase = if (info == null) Phase.Latest else Phase.Available(info))
|
||||
}
|
||||
},
|
||||
onFailure = { e ->
|
||||
_uiState.update { it.copy(phase = Phase.Error(e.message ?: "unknown")) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadAndInstall(info: UpdateRepository.UpdateInfo) {
|
||||
_uiState.update { it.copy(phase = Phase.Downloading(0f)) }
|
||||
viewModelScope.launch {
|
||||
updateRepository.downloadApk(info) { downloaded, total ->
|
||||
val progress = if (total > 0) downloaded.toFloat() / total else 0f
|
||||
_uiState.update { it.copy(phase = Phase.Downloading(progress)) }
|
||||
}.fold(
|
||||
onSuccess = { file -> install(file) },
|
||||
onFailure = { e ->
|
||||
_uiState.update { it.copy(phase = Phase.Error(e.message ?: "unknown")) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun install(file: File) {
|
||||
if (context.packageManager.canRequestPackageInstalls()) {
|
||||
_uiState.update { it.copy(phase = Phase.Ready(file)) }
|
||||
val authority = context.packageName + ".fileprovider"
|
||||
val uri = FileProvider.getUriForFile(context, authority, file)
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, "application/vnd.android.package-archive")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
runCatching { context.startActivity(intent) }.onFailure { e ->
|
||||
_uiState.update { it.copy(phase = Phase.Error(e.message ?: "cannot start installer")) }
|
||||
}
|
||||
} else {
|
||||
_uiState.update { it.copy(phase = Phase.NeedInstallPermission) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -771,17 +771,4 @@
|
||||
<string name="sub2_panel_delete_confirm_title">删除 API Key</string>
|
||||
<string name="sub2_panel_delete_confirm_body">确定删除「%1$s」吗?删除后无法恢复,正在使用该 Key 的客户端会立即失效。</string>
|
||||
<string name="sub2_trend_account_scope">此图为账号整体趋势(无单模型维度)</string>
|
||||
<string name="title_update">检查更新</string>
|
||||
<string name="update_current_version">当前版本 %1$s(%2$d)</string>
|
||||
<string name="update_check">检查更新</string>
|
||||
<string name="update_checking">正在检查…</string>
|
||||
<string name="update_latest">已是最新版本</string>
|
||||
<string name="update_available">发现新版本 %1$s</string>
|
||||
<string name="update_download">下载并安装</string>
|
||||
<string name="update_downloading">正在下载 %1$d%%</string>
|
||||
<string name="update_download_finished">下载完成</string>
|
||||
<string name="update_install">安装</string>
|
||||
<string name="update_install_permission_tip">需要开启「安装未知应用」权限</string>
|
||||
<string name="update_enable_permission">去开启</string>
|
||||
<string name="update_failed">更新失败:%1$s</string>
|
||||
</resources>
|
||||
@ -771,17 +771,4 @@
|
||||
<string name="sub2_panel_delete_confirm_title">刪除 API Key</string>
|
||||
<string name="sub2_panel_delete_confirm_body">確定刪除「%1$s」嗎?刪除後無法復原,正在使用該 Key 的用戶端會立即失效。</string>
|
||||
<string name="sub2_trend_account_scope">此圖為帳號整體趨勢(無單模型維度)</string>
|
||||
<string name="title_update">檢查更新</string>
|
||||
<string name="update_current_version">目前版本 %1$s(%2$d)</string>
|
||||
<string name="update_check">檢查更新</string>
|
||||
<string name="update_checking">正在檢查…</string>
|
||||
<string name="update_latest">已是最新版本</string>
|
||||
<string name="update_available">發現新版本 %1$s</string>
|
||||
<string name="update_download">下載並安裝</string>
|
||||
<string name="update_downloading">正在下載 %1$d%%</string>
|
||||
<string name="update_download_finished">下載完成</string>
|
||||
<string name="update_install">安裝</string>
|
||||
<string name="update_install_permission_tip">需要開啟「安裝未知應用」權限</string>
|
||||
<string name="update_enable_permission">前往開啟</string>
|
||||
<string name="update_failed">更新失敗:%1$s</string>
|
||||
</resources>
|
||||
@ -776,17 +776,4 @@
|
||||
<string name="sub2_panel_delete_confirm_title">Delete API key</string>
|
||||
<string name="sub2_panel_delete_confirm_body">Delete "%1$s"? This cannot be undone and any client using this key will stop working.</string>
|
||||
<string name="sub2_trend_account_scope">Chart shows the whole account (no per-model breakdown)</string>
|
||||
<string name="title_update">App update</string>
|
||||
<string name="update_current_version">Current version %1$s (%2$d)</string>
|
||||
<string name="update_check">Check for updates</string>
|
||||
<string name="update_checking">Checking…</string>
|
||||
<string name="update_latest">You are on the latest version</string>
|
||||
<string name="update_available">New version %1$s available</string>
|
||||
<string name="update_download">Download & install</string>
|
||||
<string name="update_downloading">Downloading %1$d%%</string>
|
||||
<string name="update_download_finished">Download finished</string>
|
||||
<string name="update_install">Install</string>
|
||||
<string name="update_install_permission_tip">Please allow installing apps from unknown sources</string>
|
||||
<string name="update_enable_permission">Open settings</string>
|
||||
<string name="update_failed">Update failed: %1$s</string>
|
||||
</resources>
|
||||
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="apk_downloads" path="apk_downloads/" />
|
||||
</paths>
|
||||
@ -5,7 +5,4 @@
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">124.222.97.212</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"versionCode": 42,
|
||||
"versionName": "1.7.22",
|
||||
"apkUrl": "http://124.222.97.212:8443/attachments/03955cf3-112f-4453-bac7-91bba405e40b",
|
||||
"notes": "Trae/WorkBuddy INTL 手机浏览器登录并自动回填;WorkBuddy CN 软件内登录;WorkBuddy INTL 余额走 workbuddy.ai;国际版无独立签到"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user