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

This commit is contained in:
Rifat Azad
2025-06-15 07:09:13 +06:00
parent a37f398cc7
commit 14ec1194e4
4 changed files with 84 additions and 26 deletions

View File

@@ -24,6 +24,15 @@
<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="file" />
<data android:scheme="content" />
<data android:pathPattern=".*\\.zip" />
</intent-filter>
</activity> </activity>
<activity <activity

View File

@@ -55,6 +55,10 @@ import com.rifsxd.ksunext.ui.util.LocalSnackbarHost
import com.rifsxd.ksunext.ui.util.rootAvailable import com.rifsxd.ksunext.ui.util.rootAvailable
import com.rifsxd.ksunext.ui.util.install import com.rifsxd.ksunext.ui.util.install
import android.content.Intent
import android.net.Uri
import com.rifsxd.ksunext.ui.screen.FlashIt
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -70,6 +74,14 @@ class MainActivity : ComponentActivity() {
val isManager = Natives.becomeManager(ksuApp.packageName) val isManager = Natives.becomeManager(ksuApp.packageName)
if (isManager) install() if (isManager) install()
// Check if launched with a ZIP file
val zipUri: Uri? = when (intent?.action) {
Intent.ACTION_VIEW, Intent.ACTION_SEND -> {
intent.data ?: intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
else -> null
}?.takeIf { it.toString().endsWith(".zip", ignoreCase = true) }
setContent { setContent {
// Read AMOLED mode preference // Read AMOLED mode preference
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -82,6 +94,18 @@ class MainActivity : ComponentActivity() {
val snackBarHostState = remember { SnackbarHostState() } val snackBarHostState = remember { SnackbarHostState() }
val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination val currentDestination = navController.currentBackStackEntryAsState()?.value?.destination
val navigator = navController.rememberDestinationsNavigator()
LaunchedEffect(zipUri) {
if (zipUri != null) {
navigator.navigate(
FlashScreenDestination(
FlashIt.FlashModules(listOf(zipUri))
)
)
}
}
val showBottomBar = when (currentDestination?.route) { val showBottomBar = when (currentDestination?.route) {
FlashScreenDestination.route -> false // Hide for FlashScreenDestination FlashScreenDestination.route -> false // Hide for FlashScreenDestination
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen

View File

@@ -63,6 +63,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import com.rifsxd.ksunext.R import com.rifsxd.ksunext.R
import com.rifsxd.ksunext.ui.component.rememberConfirmDialog
import com.rifsxd.ksunext.ui.component.ConfirmResult
import com.rifsxd.ksunext.ui.component.KeyEventBlocker import com.rifsxd.ksunext.ui.component.KeyEventBlocker
import com.rifsxd.ksunext.ui.util.FlashResult import com.rifsxd.ksunext.ui.util.FlashResult
import com.rifsxd.ksunext.ui.util.LkmSelection import com.rifsxd.ksunext.ui.util.LkmSelection
@@ -138,12 +140,41 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
// Disable back button if flashing is running // Disable back button if flashing is running
} }
LaunchedEffect(Unit) { val confirmDialog = rememberConfirmDialog()
if (text.isNotEmpty()) { var confirmed by rememberSaveable { mutableStateOf(flashIt !is FlashIt.FlashModules) }
return@LaunchedEffect var pendingFlashIt by rememberSaveable { mutableStateOf<FlashIt?>(null) }
LaunchedEffect(flashIt) {
if (flashIt is FlashIt.FlashModules && !confirmed) {
val uris = flashIt.uris
val moduleNames =
uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }
.joinToString("")
val confirmContent =
context.getString(R.string.module_install_prompt_with_name, moduleNames)
val confirmTitle = context.getString(R.string.module)
val result = confirmDialog.awaitConfirm(
title = confirmTitle,
content = confirmContent,
markdown = true
)
if (result == ConfirmResult.Confirmed) {
confirmed = true
pendingFlashIt = flashIt
} else {
// User cancelled, go back
navigator.popBackStack()
}
} else {
confirmed = true
pendingFlashIt = flashIt
} }
}
LaunchedEffect(confirmed, pendingFlashIt) {
if (!confirmed || pendingFlashIt == null || text.isNotEmpty()) return@LaunchedEffect
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
flashIt(flashIt, onStdout = { flashIt(pendingFlashIt!!, onStdout = {
tempText = "$it\n" tempText = "$it\n"
if (tempText.startsWith("")) { // clear command if (tempText.startsWith("")) { // clear command
text = tempText.substring(6) text = tempText.substring(6)
@@ -282,6 +313,19 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
} }
} }
fun Uri.getFileName(context: Context): String {
val contentResolver = context.contentResolver
val cursor = contentResolver.query(this, null, null, null, null)
return cursor?.use {
val nameIndex = it.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
if (it.moveToFirst() && nameIndex != -1) {
it.getString(nameIndex)
} else {
this.lastPathSegment ?: "unknown.zip"
}
} ?: (this.lastPathSegment ?: "unknown.zip")
}
@Parcelize @Parcelize
sealed class FlashIt : Parcelable { sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :

View File

@@ -291,15 +291,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
floatingActionButton = { floatingActionButton = {
if (!hideInstallButton) { if (!hideInstallButton) {
val moduleInstall = stringResource(id = R.string.module_install) val moduleInstall = stringResource(id = R.string.module_install)
val confirmTitle = stringResource(R.string.module)
var zipUris by remember { mutableStateOf<List<Uri>>(emptyList()) }
val confirmDialog = rememberConfirmDialog(onConfirm = {
if (viewModel.zipUris.isNotEmpty()) {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(viewModel.zipUris)))
viewModel.clearZipUris()
viewModel.markNeedRefresh()
}
})
val selectZipLauncher = rememberLauncherForActivityResult( val selectZipLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
@@ -322,19 +313,9 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
viewModel.updateZipUris(uris) viewModel.updateZipUris(uris)
// Show confirm dialog with selected zip file(s) name(s) navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(uris)))
val moduleNames = viewModel.clearZipUris()
uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" } viewModel.markNeedRefresh()
.joinToString("")
val confirmContent =
context.getString(R.string.module_install_prompt_with_name, moduleNames)
zipUris = uris
confirmDialog.showConfirm(
title = confirmTitle,
content = confirmContent,
markdown = true
)
} }
ExtendedFloatingActionButton( ExtendedFloatingActionButton(