3 Commits
Author SHA1 Message Date
Bluemangoo f3a7e507d4 修复无上传配置报错 2026-11-04 17:46:01 +08:00
Bluemangoo b23514a77a 压缩分发体积 2026-11-04 17:38:34 +08:00
Bluemangoo 85aaa4f285 1.7 检查更新 2026-11-04 17:14:38 +08:00
6 changed files with 166 additions and 19 deletions
+12 -3
View File
@@ -18,17 +18,26 @@ android {
applicationId = "net.bluemangoo.indpjsk"
minSdk = appMinSdk
targetSdk = 37
versionCode = 7
versionName = "1.6"
versionCode = 8
versionName = "1.7"
}
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
packaging {
dex {
useLegacyPackaging = true
}
jniLibs {
useLegacyPackaging = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@@ -6,6 +6,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.core.content.edit
import net.bluemangoo.indpjsk.App
import net.bluemangoo.indpjsk.upload.UploadStore
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
@@ -24,7 +25,7 @@ object SettingsStore {
prefs = { prefs() }
)
var uploadProfilesJson by pref<String>()
var uploadProfilesJson by pref<String>(default = UploadStore.encode(emptyList()))
var upstreamProxy by pref<String>()
var useProxy by pref<Boolean>()
var bypassPackages by pref<Set<String>>()
@@ -0,0 +1,88 @@
package net.bluemangoo.indpjsk.data
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import net.bluemangoo.indpjsk.BuildConfig
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.atomic.AtomicBoolean
data class UpdateInfo(
val latestVersion: String,
val downloadUrl: String,
)
object UpdateChecker {
const val DOWNLOAD_URL = "https://indpjsk.bluemangoo.net/download?source=app_update"
private const val LATEST_URL = "https://indpjsk.bluemangoo.net/latest"
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val started = AtomicBoolean(false)
private val _update = MutableStateFlow<UpdateInfo?>(null)
val update: StateFlow<UpdateInfo?> = _update.asStateFlow()
fun ensureChecked() {
if (!started.compareAndSet(false, true)) return
scope.launch {
runCatching { fetchLatestVersion() }
.onSuccess { latest ->
if (latest != null && isNewer(latest, BuildConfig.VERSION_NAME)) {
_update.value = UpdateInfo(latest, DOWNLOAD_URL)
}
}
.onFailure {
started.set(false)
}
}
}
private fun fetchLatestVersion(): String? {
val conn = URL(LATEST_URL).openConnection() as HttpURLConnection
try {
conn.instanceFollowRedirects = true
conn.connectTimeout = 10_000
conn.readTimeout = 15_000
if (conn.responseCode != 200) return null
val body = conn.inputStream.bufferedReader().use { it.readText() }.trim()
if (body.isEmpty()) return null
if (body.startsWith("{")) {
return JSONObject(body).optString("tag").trim().takeIf { it.isNotEmpty() }
}
return body
} finally {
conn.disconnect()
}
}
internal fun isNewer(remote: String, current: String): Boolean {
fun parts(v: String): List<String> = v.trim()
.removePrefix("v")
.removePrefix("V")
.split('.', '-', '+', '_')
.filter { it.isNotEmpty() }
val r = parts(remote)
val c = parts(current)
for (i in 0 until maxOf(r.size, c.size)) {
val rs = r.getOrNull(i) ?: ""
val cs = c.getOrNull(i) ?: ""
val rn = rs.toIntOrNull()
val cn = cs.toIntOrNull()
if (rn != null && cn != null) {
if (rn != cn) return rn > cn
} else {
val cmp = rs.compareTo(cs, ignoreCase = true)
if (cmp != 0) return cmp > 0
}
}
return false
}
}
@@ -1,26 +1,18 @@
package net.bluemangoo.indpjsk.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.*
import androidx.compose.ui.unit.dp
import net.bluemangoo.indpjsk.BuildConfig
import net.bluemangoo.indpjsk.R
import net.bluemangoo.indpjsk.data.UpdateChecker
import net.bluemangoo.indpjsk.ui.theme.indPjskCustomColor
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -28,6 +20,9 @@ fun AboutScreen(
readme: String,
onBack: () -> Unit,
) {
val update by UpdateChecker.update.collectAsState()
LaunchedEffect(Unit) { UpdateChecker.ensureChecked() }
Scaffold(
topBar = {
TopAppBar(
@@ -54,6 +49,10 @@ fun AboutScreen(
text = "Version ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
style = MaterialTheme.typography.titleMedium
)
update?.let { info ->
Spacer(Modifier.height(4.dp))
UpdateLine(info.latestVersion)
}
Spacer(Modifier.height(8.dp))
HorizontalDivider()
Spacer(Modifier.height(8.dp))
@@ -65,3 +64,30 @@ fun AboutScreen(
}
}
}
@Composable
private fun UpdateLine(version: String) {
val warning = indPjskCustomColor().warning
val text = remember(version) {
buildAnnotatedString {
withStyle(SpanStyle(color = warning)) {
append("发现新的版本 $version,")
}
withLink(
LinkAnnotation.Url(
url = UpdateChecker.DOWNLOAD_URL,
)
) {
append("点击")
}
withStyle(SpanStyle(color = warning)) {
append("下载")
}
}
}
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
softWrap = true
)
}
@@ -8,4 +8,7 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val Pink40 = Color(0xFF7D5260)
val Warning = Color(0xFFD97706)
val WarningDark = Color(0xFFFBBF24)
@@ -4,8 +4,28 @@ import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
data class CustomColorScheme(
val warning: Color,
)
private val customLightColorScheme = CustomColorScheme(
warning = Warning,
)
private val customDarkColorScheme = CustomColorScheme(
warning = WarningDark,
)
@Composable
fun indPjskCustomColor(
darkTheme: Boolean = isSystemInDarkTheme(),
): CustomColorScheme {
return if (darkTheme) customDarkColorScheme else customLightColorScheme
}
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,