manager: add a simple manager updater, close #627
This commit is contained in:
@@ -19,6 +19,7 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -28,6 +29,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import me.weishu.kernelsu.*
|
import me.weishu.kernelsu.*
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
||||||
@@ -37,13 +40,11 @@ import me.weishu.kernelsu.ui.util.*
|
|||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(navigator: DestinationsNavigator) {
|
fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
Scaffold(
|
Scaffold(topBar = {
|
||||||
topBar = {
|
TopBar(onSettingsClick = {
|
||||||
TopBar(onSettingsClick = {
|
navigator.navigate(SettingScreenDestination)
|
||||||
navigator.navigate(SettingScreenDestination)
|
})
|
||||||
})
|
}) { innerPadding ->
|
||||||
}
|
|
||||||
) { innerPadding ->
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
@@ -62,11 +63,11 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
if (isManager && Natives.requireNewKernel()) {
|
if (isManager && Natives.requireNewKernel()) {
|
||||||
WarningCard(
|
WarningCard(
|
||||||
stringResource(id = R.string.require_kernel_version).format(
|
stringResource(id = R.string.require_kernel_version).format(
|
||||||
ksuVersion,
|
ksuVersion, Natives.MINIMAL_SUPPORTED_KERNEL
|
||||||
Natives.MINIMAL_SUPPORTED_KERNEL
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
UpdateCard()
|
||||||
InfoCard()
|
InfoCard()
|
||||||
DonateCard()
|
DonateCard()
|
||||||
LearnMoreCard()
|
LearnMoreCard()
|
||||||
@@ -75,6 +76,25 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UpdateCard() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val newVersion by produceState(initialValue = 0 to "") {
|
||||||
|
value = withContext(Dispatchers.IO) { checkNewVersion() }
|
||||||
|
}
|
||||||
|
val currentVersionCode = getManagerVersion(context).second
|
||||||
|
val newVersionCode = newVersion.first
|
||||||
|
val newVersionUrl = newVersion.second
|
||||||
|
if (newVersionCode <= currentVersionCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
WarningCard(message = stringResource(id = R.string.new_version_available).format(newVersionCode)) {
|
||||||
|
uriHandler.openUri(newVersionUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(text = {
|
||||||
@@ -87,44 +107,41 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(onSettingsClick: () -> Unit) {
|
private fun TopBar(onSettingsClick: () -> Unit) {
|
||||||
TopAppBar(
|
TopAppBar(title = { Text(stringResource(R.string.app_name)) }, actions = {
|
||||||
title = { Text(stringResource(R.string.app_name)) },
|
var showDropdown by remember { mutableStateOf(false) }
|
||||||
actions = {
|
IconButton(onClick = {
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
showDropdown = true
|
||||||
IconButton(onClick = {
|
}) {
|
||||||
showDropdown = true
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Refresh,
|
||||||
|
contentDescription = stringResource(id = R.string.reboot)
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||||
|
showDropdown = false
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Refresh,
|
|
||||||
contentDescription = stringResource(id = R.string.reboot)
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
RebootDropdownItem(id = R.string.reboot)
|
||||||
showDropdown = false
|
|
||||||
}) {
|
|
||||||
|
|
||||||
RebootDropdownItem(id = R.string.reboot)
|
val pm =
|
||||||
|
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||||
val pm =
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||||
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
|
||||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
|
||||||
}
|
|
||||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
|
||||||
}
|
}
|
||||||
}
|
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||||
|
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||||
IconButton(onClick = onSettingsClick) {
|
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||||
Icon(
|
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||||
imageVector = Icons.Filled.Settings,
|
|
||||||
contentDescription = stringResource(id = R.string.settings)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
IconButton(onClick = onSettingsClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Settings,
|
||||||
|
contentDescription = stringResource(id = R.string.settings)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -136,17 +153,14 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
|||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
Row(
|
Row(modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
.clickable {
|
||||||
.clickable {
|
if (kernelVersion.isGKI() && ksuVersion == null) {
|
||||||
if (kernelVersion.isGKI() && ksuVersion == null) {
|
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
|
||||||
uriHandler.openUri("https://kernelsu.org/guide/installation.html")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding(24.dp),
|
}
|
||||||
verticalAlignment = Alignment.CenterVertically
|
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
) {
|
|
||||||
when {
|
when {
|
||||||
ksuVersion != null -> {
|
ksuVersion != null -> {
|
||||||
val appendText = if (Natives.isSafeMode) {
|
val appendText = if (Natives.isSafeMode) {
|
||||||
@@ -168,10 +182,8 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
|||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
R.string.home_superuser_count,
|
R.string.home_superuser_count, getSuperuserCount()
|
||||||
getSuperuserCount()
|
), style = MaterialTheme.typography.bodyMedium
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
@@ -217,22 +229,25 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WarningCard(message: String) {
|
fun WarningCard(
|
||||||
|
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: () -> Unit = {}
|
||||||
|
) {
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
colors = CardDefaults.elevatedCardColors(
|
colors = CardDefaults.elevatedCardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.error
|
containerColor = color
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(24.dp),
|
.padding(24.dp)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
.clickable {
|
||||||
|
onClick()
|
||||||
|
}, verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column() {
|
Column() {
|
||||||
Text(
|
Text(
|
||||||
text = message,
|
text = message, style = MaterialTheme.typography.bodyMedium
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,15 +261,12 @@ fun LearnMoreCard() {
|
|||||||
|
|
||||||
ElevatedCard {
|
ElevatedCard {
|
||||||
|
|
||||||
Row(
|
Row(modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
.clickable {
|
||||||
.clickable {
|
uriHandler.openUri(url)
|
||||||
uriHandler.openUri(url)
|
}
|
||||||
}
|
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
.padding(24.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_learn_kernelsu),
|
text = stringResource(R.string.home_learn_kernelsu),
|
||||||
@@ -276,15 +288,12 @@ fun DonateCard() {
|
|||||||
|
|
||||||
ElevatedCard {
|
ElevatedCard {
|
||||||
|
|
||||||
Row(
|
Row(modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
.clickable {
|
||||||
.clickable {
|
uriHandler.openUri("https://patreon.com/weishu")
|
||||||
uriHandler.openUri("https://patreon.com/weishu")
|
}
|
||||||
}
|
.padding(24.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
.padding(24.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column() {
|
Column() {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_support_title),
|
text = stringResource(R.string.home_support_title),
|
||||||
@@ -323,7 +332,11 @@ private fun InfoCard() {
|
|||||||
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
|
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
InfoCardItem(stringResource(R.string.home_manager_version), getManagerVersion(context))
|
val managerVersion = getManagerVersion(context)
|
||||||
|
InfoCardItem(
|
||||||
|
stringResource(R.string.home_manager_version),
|
||||||
|
"${managerVersion.first} (${managerVersion.second})"
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)
|
InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)
|
||||||
@@ -334,9 +347,9 @@ private fun InfoCard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getManagerVersion(context: Context): String {
|
fun getManagerVersion(context: Context): Pair<String, Int> {
|
||||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||||
return "${packageInfo.versionName} (${packageInfo.versionCode})"
|
return Pair(packageInfo.versionName, packageInfo.versionCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
|
|||||||
@@ -60,6 +60,40 @@ fun download(
|
|||||||
downloadManager.enqueue(request)
|
downloadManager.enqueue(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkNewVersion(): Pair<Int, String> {
|
||||||
|
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
|
||||||
|
val defaultValue = 0 to ""
|
||||||
|
runCatching {
|
||||||
|
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
|
||||||
|
.use { response ->
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
val body = response.body?.string() ?: return defaultValue
|
||||||
|
val json = org.json.JSONObject(body)
|
||||||
|
|
||||||
|
val assets = json.getJSONArray("assets")
|
||||||
|
for (i in 0 until assets.length()) {
|
||||||
|
val asset = assets.getJSONObject(i)
|
||||||
|
val name = asset.getString("name")
|
||||||
|
if (!name.endsWith(".apk")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val regex = Regex("v(.+?)_(\\d+)-")
|
||||||
|
val matchResult = regex.find(name) ?: continue
|
||||||
|
val versionName = matchResult.groupValues[1]
|
||||||
|
val versionCode = matchResult.groupValues[2].toInt()
|
||||||
|
val downloadUrl = asset.getString("browser_download_url")
|
||||||
|
|
||||||
|
return versionCode to downloadUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
||||||
DisposableEffect(context) {
|
DisposableEffect(context) {
|
||||||
|
|||||||
@@ -74,4 +74,5 @@
|
|||||||
<string name="module_update">更新</string>
|
<string name="module_update">更新</string>
|
||||||
<string name="module_downloading">正在下载模块:%s</string>
|
<string name="module_downloading">正在下载模块:%s</string>
|
||||||
<string name="module_start_downloading">开始下载:%s</string>
|
<string name="module_start_downloading">开始下载:%s</string>
|
||||||
|
<string name="new_version_available">发现新版本:%d,点击下载</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -79,4 +79,5 @@
|
|||||||
<string name="module_update">Update</string>
|
<string name="module_update">Update</string>
|
||||||
<string name="module_downloading">Downloading module: %s</string>
|
<string name="module_downloading">Downloading module: %s</string>
|
||||||
<string name="module_start_downloading">Start downloading: %s</string>
|
<string name="module_start_downloading">Start downloading: %s</string>
|
||||||
|
<string name="new_version_available">New version: %s is available, click to download</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user