Skip to content

Commit c43c663

Browse files
committed
feat: Auto-install patched APK with Shizuku after patching completes
Closes #610
1 parent 5eb31e3 commit c43c663

5 files changed

Lines changed: 113 additions & 5 deletions

File tree

app/src/main/java/app/morphe/manager/domain/manager/PreferencesManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class PreferencesManager(
8181
val promptInstallerOnInstall = booleanPreference("prompt_installer_on_install", false)
8282
val installerCustomComponents = stringSetPreference("installer_custom_components", emptySet())
8383
val installerHiddenComponents = stringSetPreference("installer_hidden_components", emptySet())
84+
val autoInstallWithShizuku = booleanPreference("auto_install_with_shizuku", false)
8485

8586
val useProcessRuntime = booleanPreference(
8687
"process_runtime", // Old key was 'use_process_runtime' and may have the wrong default for some devices.

app/src/main/java/app/morphe/manager/ui/screen/PatcherScreen.kt

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,24 @@ import androidx.compose.ui.res.stringResource
3434
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3535
import app.morphe.manager.R
3636
import app.morphe.manager.domain.installer.InstallerManager
37+
import app.morphe.manager.domain.manager.InstallerPreferenceTokens
3738
import app.morphe.manager.domain.manager.PreferencesManager
3839
import app.morphe.manager.ui.model.State
3940
import app.morphe.manager.ui.screen.patcher.*
41+
import app.morphe.manager.ui.screen.patcher.game.MiniGameState
4042
import app.morphe.manager.ui.screen.settings.advanced.NotificationPermissionDialog
4143
import app.morphe.manager.ui.screen.settings.system.InstallerSelectionDialog
4244
import app.morphe.manager.ui.screen.shared.MorpheAnimations
4345
import app.morphe.manager.ui.viewmodel.InstallViewModel
4446
import app.morphe.manager.ui.viewmodel.PatcherViewModel
4547
import app.morphe.manager.util.APK_MIMETYPE
4648
import app.morphe.manager.util.EventEffect
47-
import app.morphe.manager.ui.screen.patcher.game.MiniGameState
4849
import app.morphe.manager.util.tag
4950
import com.google.android.gms.common.ConnectionResult
5051
import com.google.android.gms.common.GoogleApiAvailability
5152
import kotlinx.coroutines.delay
5253
import kotlinx.coroutines.isActive
54+
import kotlinx.coroutines.launch
5355
import org.koin.androidx.compose.koinViewModel
5456
import org.koin.compose.koinInject
5557
import kotlin.math.exp
@@ -141,6 +143,28 @@ fun PatcherScreen(
141143
// Get output file from viewModel
142144
val outputFile = patcherViewModel.outputFile
143145

146+
val autoInstallWithShizuku by prefs.autoInstallWithShizuku.getAsState()
147+
val primaryInstallerPref by prefs.installerPrimary.getAsState()
148+
val promptInstallerOnInstall by prefs.promptInstallerOnInstall.getAsState()
149+
150+
// Auto-install: triggers when success screen appears with Shizuku as primary installer.
151+
// Skipped when promptInstallerOnInstall is enabled - user gets the manual Install button instead.
152+
LaunchedEffect(showSuccessScreen) {
153+
if (!showSuccessScreen) return@LaunchedEffect
154+
if (!autoInstallWithShizuku) return@LaunchedEffect
155+
if (usingMountInstall) return@LaunchedEffect
156+
if (patcherSucceeded != true) return@LaunchedEffect
157+
if (primaryInstallerPref != InstallerPreferenceTokens.SHIZUKU) return@LaunchedEffect
158+
if (promptInstallerOnInstall) return@LaunchedEffect
159+
if (installViewModel.installState !is InstallViewModel.InstallState.Ready) return@LaunchedEffect
160+
delay(300)
161+
installViewModel.install(
162+
outputFile = outputFile,
163+
originalPackageName = patcherViewModel.packageName,
164+
onPersistApp = { pkg, type -> patcherViewModel.persistPatchedApp(pkg, type) }
165+
)
166+
}
167+
144168
// Progress animation logic: drives displayProgress and showSuccessScreen
145169
LaunchedEffect(patcherSucceeded) {
146170
var lastProgressUpdate = 0.0f
@@ -425,7 +449,12 @@ fun PatcherScreen(
425449
onConfirm = { selectedToken ->
426450
installViewModel.proceedWithSelectedInstaller(selectedToken)
427451
},
428-
onOpenShizuku = installerManager::openShizukuApp
452+
onOpenShizuku = installerManager::openShizukuApp,
453+
autoInstallEnabled = autoInstallWithShizuku,
454+
onAutoInstallToggle = { enabled ->
455+
scope.launch { prefs.autoInstallWithShizuku.update(enabled) }
456+
},
457+
installerPromptEnabled = promptInstallerOnInstall
429458
)
430459
}
431460

@@ -468,8 +497,16 @@ fun PatcherScreen(
468497
}
469498

470499
PatcherState.SUCCESS -> {
500+
val effectiveIsInstalling = isInstalling || (
501+
autoInstallWithShizuku &&
502+
primaryInstallerPref == InstallerPreferenceTokens.SHIZUKU &&
503+
patcherSucceeded == true &&
504+
!usingMountInstall &&
505+
!promptInstallerOnInstall &&
506+
installState is InstallViewModel.InstallState.Ready
507+
)
471508
PatchingSuccess(
472-
isInstalling = isInstalling,
509+
isInstalling = effectiveIsInstalling,
473510
isInstalled = isInstalled,
474511
isError = isError,
475512
isConflict = isConflict,

app/src/main/java/app/morphe/manager/ui/screen/settings/system/InstallerSection.kt

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ package app.morphe.manager.ui.screen.settings.system
77

88
import android.annotation.SuppressLint
99
import android.graphics.drawable.Drawable
10+
import androidx.compose.animation.AnimatedVisibility
1011
import androidx.compose.foundation.Image
1112
import androidx.compose.foundation.background
1213
import androidx.compose.foundation.border
14+
import androidx.compose.foundation.clickable
1315
import androidx.compose.foundation.layout.*
1416
import androidx.compose.foundation.shape.RoundedCornerShape
1517
import androidx.compose.material.icons.Icons
@@ -129,6 +131,9 @@ fun InstallerSelectionDialogContainer(
129131
settingsViewModel.getInstallerEntries(installTarget, primaryToken)
130132
}
131133

134+
val autoInstallEnabled by settingsViewModel.prefs.autoInstallWithShizuku.getAsState()
135+
val promptEnabled by settingsViewModel.prefs.promptInstallerOnInstall.getAsState()
136+
132137
InstallerSelectionDialog(
133138
title = stringResource(R.string.installer_title),
134139
options = options,
@@ -138,7 +143,10 @@ fun InstallerSelectionDialogContainer(
138143
settingsViewModel.confirmInstallerSelection(selection)
139144
onDismiss()
140145
},
141-
onOpenShizuku = settingsViewModel::openShizukuApp
146+
onOpenShizuku = settingsViewModel::openShizukuApp,
147+
autoInstallEnabled = autoInstallEnabled,
148+
onAutoInstallToggle = settingsViewModel::setAutoInstallWithShizuku,
149+
installerPromptEnabled = promptEnabled
142150
)
143151
}
144152

@@ -193,7 +201,10 @@ fun InstallerSelectionDialog(
193201
selected: InstallerManager.Token,
194202
onDismiss: () -> Unit,
195203
onConfirm: (InstallerManager.Token) -> Unit,
196-
onOpenShizuku: (() -> Boolean)?
204+
onOpenShizuku: (() -> Boolean)?,
205+
autoInstallEnabled: Boolean = false,
206+
onAutoInstallToggle: ((Boolean) -> Unit)? = null,
207+
installerPromptEnabled: Boolean = false
197208
) {
198209
val shizukuPromptReasons = remember {
199210
setOf(
@@ -274,6 +285,58 @@ fun InstallerSelectionDialog(
274285
}
275286
}
276287
}
288+
289+
// Auto-install toggle
290+
AnimatedVisibility(
291+
visible = currentSelection.value == InstallerManager.Token.Shizuku &&
292+
onAutoInstallToggle != null,
293+
enter = MorpheAnimations.expandFadeEnter,
294+
exit = MorpheAnimations.shrinkFadeExit
295+
) {
296+
Column {
297+
HorizontalDivider(modifier = Modifier.padding(top = 4.dp))
298+
Row(
299+
modifier = Modifier
300+
.fillMaxWidth()
301+
.clip(MaterialTheme.shapes.medium)
302+
.clickable { onAutoInstallToggle?.invoke(!autoInstallEnabled) }
303+
.padding(vertical = 12.dp, horizontal = 4.dp),
304+
verticalAlignment = Alignment.CenterVertically,
305+
horizontalArrangement = Arrangement.spacedBy(16.dp)
306+
) {
307+
MorpheIcon(icon = Icons.Outlined.Bolt)
308+
Column(modifier = Modifier.weight(1f)) {
309+
Text(
310+
text = stringResource(R.string.settings_auto_install_with_shizuku),
311+
style = MaterialTheme.typography.titleSmall,
312+
color = MaterialTheme.colorScheme.onSurface
313+
)
314+
Text(
315+
text = stringResource(R.string.settings_auto_install_with_shizuku_description),
316+
style = MaterialTheme.typography.bodySmall,
317+
color = MaterialTheme.colorScheme.onSurfaceVariant
318+
)
319+
}
320+
MorpheSwitch(
321+
checked = autoInstallEnabled,
322+
onCheckedChange = null
323+
)
324+
}
325+
326+
if (installerPromptEnabled) {
327+
InfoBadge(
328+
text = stringResource(
329+
R.string.settings_auto_install_prompt_conflict,
330+
stringResource(R.string.settings_prompt_installer_on_install)
331+
),
332+
style = InfoBadgeStyle.Warning,
333+
icon = Icons.Outlined.Warning,
334+
isExpanded = true,
335+
modifier = Modifier.padding(top = 4.dp)
336+
)
337+
}
338+
}
339+
}
277340
}
278341
}
279342
}

app/src/main/java/app/morphe/manager/ui/viewmodel/SettingsViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ class SettingsViewModel(
211211
prefs.promptInstallerOnInstall.update(enabled)
212212
}
213213

214+
fun setAutoInstallWithShizuku(enabled: Boolean) = viewModelScope.launch {
215+
prefs.autoInstallWithShizuku.update(enabled)
216+
}
217+
214218
fun setUseCustomFilePicker(enabled: Boolean) = viewModelScope.launch {
215219
prefs.useCustomFilePicker.update(enabled)
216220
prefs.customFilePickerUserConfigured.update(true)

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,9 @@ The image dimensions must be as follows:
663663
<string name="installer_use_standard">Use standard</string>
664664
<string name="settings_prompt_installer_on_install">Installer selection prompt</string>
665665
<string name="settings_prompt_installer_on_install_description">Shows installer selection dialog each time a patched app is installed</string>
666+
<string name="settings_auto_install_with_shizuku">Auto-install after patching</string>
667+
<string name="settings_auto_install_with_shizuku_description">Silently installs the patched APK as soon as patching completes</string>
668+
<string name="settings_auto_install_prompt_conflict">\"%s\" is enabled - auto-install will be skipped</string>
666669

667670
<!-- Settings - System Tab - Performance Section -->
668671
<string name="settings_system_battery_optimization">Battery optimization</string>

0 commit comments

Comments
 (0)