manager: add support for opening zip file and directly flash module

- refine zip intent method

- use MMRL method to handle zip, fix failed to open zip from Chrome

Co-Authored-By: Der_Googler <54764558+dergoogler@users.noreply.github.com>
Co-authored-by: rifsxd <rifat.44.azad.rifs@gmail.com>
Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
ShirkNeko
2025-10-08 16:11:55 +08:00
parent 9c1ff635e3
commit 2f43ad4f76
3 changed files with 91 additions and 7 deletions

View File

@@ -30,6 +30,13 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/zip" />
<data android:scheme="content" />
</intent-filter>
</activity> </activity>
<activity-alias <activity-alias

View File

@@ -2,6 +2,7 @@ package com.sukisu.ultra.ui
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@@ -25,7 +26,9 @@ import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination import com.ramcosta.composedestinations.generated.destinations.ExecuteModuleActionScreenDestination
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.spec.NavHostGraphSpec import com.ramcosta.composedestinations.spec.NavHostGraphSpec
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.ui.screen.BottomBarDestination import com.sukisu.ultra.ui.screen.BottomBarDestination
import com.sukisu.ultra.ui.theme.KernelSUTheme import com.sukisu.ultra.ui.theme.KernelSUTheme
@@ -34,6 +37,7 @@ import com.sukisu.ultra.ui.util.install
import com.sukisu.ultra.ui.viewmodel.HomeViewModel import com.sukisu.ultra.ui.viewmodel.HomeViewModel
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import com.sukisu.ultra.ui.webui.initPlatform import com.sukisu.ultra.ui.webui.initPlatform
import com.sukisu.ultra.ui.screen.FlashIt
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import zako.zako.zako.zakoui.activity.component.BottomBar import zako.zako.zako.zakoui.activity.component.BottomBar
@@ -83,6 +87,18 @@ class MainActivity : ComponentActivity() {
isInitialized = true isInitialized = true
} }
// Check if launched with a ZIP file
val zipUri: ArrayList<Uri>? = if (intent.data != null) {
arrayListOf(intent.data!!)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableArrayListExtra("uris", Uri::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableArrayListExtra("uris")
}
}
setContent { setContent {
KernelSUTheme { KernelSUTheme {
val navController = rememberNavController() val navController = rememberNavController()
@@ -93,6 +109,18 @@ class MainActivity : ComponentActivity() {
BottomBarDestination.entries.map { it.direction.route }.toSet() BottomBarDestination.entries.map { it.direction.route }.toSet()
} }
val navigator = navController.rememberDestinationsNavigator()
LaunchedEffect(zipUri) {
if (!zipUri.isNullOrEmpty()) {
navigator.navigate(
FlashScreenDestination(
FlashIt.FlashModules(zipUri)
)
)
}
}
val showBottomBar = when (currentDestination?.route) { val showBottomBar = when (currentDestination?.route) {
ExecuteModuleActionScreenDestination.route -> false ExecuteModuleActionScreenDestination.route -> false
else -> true else -> true

View File

@@ -1,5 +1,6 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
import android.os.Parcelable import android.os.Parcelable
@@ -30,6 +31,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.activity.ComponentActivity
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 com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
@@ -119,6 +121,24 @@ fun setModuleVerificationStatus(uri: Uri, isVerified: Boolean) {
@Destination<RootGraph> @Destination<RootGraph>
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) { fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
val context = LocalContext.current val context = LocalContext.current
// 是否通过从外部启动的模块安装
val isExternalInstall = remember {
when (flashIt) {
is FlashIt.FlashModule -> {
(context as? ComponentActivity)?.intent?.let { intent ->
intent.action == Intent.ACTION_VIEW || intent.action == Intent.ACTION_SEND
} ?: false
}
is FlashIt.FlashModules -> {
(context as? ComponentActivity)?.intent?.let { intent ->
intent.action == Intent.ACTION_VIEW || intent.action == Intent.ACTION_SEND
} ?: false
}
else -> false
}
}
var text by rememberSaveable { mutableStateOf("") } var text by rememberSaveable { mutableStateOf("") }
var tempText: String var tempText: String
val logContent = rememberSaveable { StringBuilder() } val logContent = rememberSaveable { StringBuilder() }
@@ -203,8 +223,21 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
if (showReboot) { if (showReboot) {
text += "\n\n\n" text += "\n\n\n"
showFloatAction = true showFloatAction = true
// 如果是内部安装,显示重启按钮后不自动返回
if (isExternalInstall) {
return@flashModuleUpdate
}
} }
hasUpdateCompleted = true hasUpdateCompleted = true
// 如果是外部安装的模块更新且不需要重启,延迟后自动返回
if (isExternalInstall) {
scope.launch {
kotlinx.coroutines.delay(2000)
(context as? ComponentActivity)?.finish()
}
}
}, onStdout = { }, onStdout = {
tempText = "$it\n" tempText = "$it\n"
if (tempText.startsWith("")) { // clear command if (tempText.startsWith("")) { // clear command
@@ -297,6 +330,18 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
kotlinx.coroutines.delay(500) kotlinx.coroutines.delay(500)
navigator.navigate(FlashScreenDestination(nextFlashIt)) navigator.navigate(FlashScreenDestination(nextFlashIt))
} }
} else if (isExternalInstall && flashIt is FlashIt.FlashModules && flashIt.currentIndex >= flashIt.uris.size - 1) {
// 如果是外部安装且是最后一个模块,安装完成后自动返回
scope.launch {
kotlinx.coroutines.delay(2000)
(context as? ComponentActivity)?.finish()
}
} else if (isExternalInstall && flashIt is FlashIt.FlashModule) {
// 如果是外部安装单个模块,安装完成后自动返回
scope.launch {
kotlinx.coroutines.delay(2000)
(context as? ComponentActivity)?.finish()
}
} }
}, onStdout = { }, onStdout = {
tempText = "$it\n" tempText = "$it\n"
@@ -319,14 +364,18 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
} }
if (canGoBack) { if (canGoBack) {
if (flashIt is FlashIt.FlashModules || flashIt is FlashIt.FlashModuleUpdate) { if (isExternalInstall) {
viewModel.markNeedRefresh() (context as? ComponentActivity)?.finish()
viewModel.fetchModuleList()
navigator.navigate(ModuleScreenDestination)
} else { } else {
viewModel.markNeedRefresh() if (flashIt is FlashIt.FlashModules || flashIt is FlashIt.FlashModuleUpdate) {
viewModel.fetchModuleList() viewModel.markNeedRefresh()
navigator.popBackStack() viewModel.fetchModuleList()
navigator.navigate(ModuleScreenDestination)
} else {
viewModel.markNeedRefresh()
viewModel.fetchModuleList()
navigator.popBackStack()
}
} }
} }
} }