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" />
<category android:name="android.intent.category.LAUNCHER" />
</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

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.install
import android.content.Intent
import android.net.Uri
import com.rifsxd.ksunext.ui.screen.FlashIt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -70,6 +74,14 @@ class MainActivity : ComponentActivity() {
val isManager = Natives.becomeManager(ksuApp.packageName)
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 {
// Read AMOLED mode preference
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -82,6 +94,18 @@ class MainActivity : ComponentActivity() {
val snackBarHostState = remember { SnackbarHostState() }
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) {
FlashScreenDestination.route -> false // Hide for FlashScreenDestination
ExecuteModuleActionScreenDestination.route -> false // Hide for ExecuteModuleActionScreen

View File

@@ -63,6 +63,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
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.util.FlashResult
import com.rifsxd.ksunext.ui.util.LkmSelection
@@ -138,12 +140,41 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
// Disable back button if flashing is running
}
LaunchedEffect(Unit) {
if (text.isNotEmpty()) {
return@LaunchedEffect
val confirmDialog = rememberConfirmDialog()
var confirmed by rememberSaveable { mutableStateOf(flashIt !is FlashIt.FlashModules) }
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) {
flashIt(flashIt, onStdout = {
flashIt(pendingFlashIt!!, onStdout = {
tempText = "$it\n"
if (tempText.startsWith("")) { // clear command
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
sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :

View File

@@ -291,15 +291,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
floatingActionButton = {
if (!hideInstallButton) {
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(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
@@ -322,19 +313,9 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
viewModel.updateZipUris(uris)
// Show confirm dialog with selected zip file(s) name(s)
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)
zipUris = uris
confirmDialog.showConfirm(
title = confirmTitle,
content = confirmContent,
markdown = true
)
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(uris)))
viewModel.clearZipUris()
viewModel.markNeedRefresh()
}
ExtendedFloatingActionButton(