Skip to content

Commit d3d748f

Browse files
authored
chore: Merge branch dev to main (#609)
2 parents 8d7622e + 976289b commit d3d748f

112 files changed

Lines changed: 4557 additions & 396 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
# [1.21.0-dev.3](https://github.com/MorpheApp/morphe-manager/compare/v1.21.0-dev.2...v1.21.0-dev.3) (2026-06-03)
2+
3+
4+
### Bug Fixes
5+
6+
* Invert vertical parallax axis ([503e38e](https://github.com/MorpheApp/morphe-manager/commit/503e38ec4461d3f4d6372358bb8b310ac8875f22))
7+
8+
9+
### Features
10+
11+
* Add onboarding tour triggered after first patch ([#611](https://github.com/MorpheApp/morphe-manager/issues/611)) ([a7ab6df](https://github.com/MorpheApp/morphe-manager/commit/a7ab6dfb576d20a9ef527ec759ed33ee57189772))
12+
13+
# [1.21.0-dev.2](https://github.com/MorpheApp/morphe-manager/compare/v1.21.0-dev.1...v1.21.0-dev.2) (2026-06-01)
14+
15+
16+
### Bug Fixes
17+
18+
* Cancelled install callbacks on some OEM systems ([#598](https://github.com/MorpheApp/morphe-manager/issues/598)) ([6ec8543](https://github.com/MorpheApp/morphe-manager/commit/6ec854345ae6bd59a51de1998a943566fa20db4f))
19+
20+
# [1.21.0-dev.1](https://github.com/MorpheApp/morphe-manager/compare/v1.20.0...v1.21.0-dev.1) (2026-05-31)
21+
22+
23+
### Features
24+
25+
* Auto-install patched APK with Shizuku after patching completes ([c43c663](https://github.com/MorpheApp/morphe-manager/commit/c43c66368d175be5d6bfc97ebb98f5880130f833))
26+
127
# [1.20.0](https://github.com/MorpheApp/morphe-manager/compare/v1.19.1...v1.20.0) (2026-05-29)
228

329

app-release.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"created_at": "2026-05-29T15:21:39",
3-
"description": "# [1.20.0](https://github.com/MorpheApp/morphe-manager/compare/v1.19.1...v1.20.0) (2026-05-29)\n\n\n### Bug Fixes\n\n* Center success icon and text in landscape two-column layout ([3189c05](https://github.com/MorpheApp/morphe-manager/commit/3189c05ef75c9b2c9933e124c24d43f5f209b18b))\n* Correct shimmer highlight direction in light theme ([0639ccb](https://github.com/MorpheApp/morphe-manager/commit/0639ccb224e813781b7ca029d630eb1e64a6300c))\n* Enable legacy external storage for file picker on Android 10 ([5c770fe](https://github.com/MorpheApp/morphe-manager/commit/5c770fed342fe4dcf43f7be7d7cf408e96915e31))\n\n\n### Features\n\n* Replace horizontal shimmer with diagonal sweep animation ([c658185](https://github.com/MorpheApp/morphe-manager/commit/c658185690b0a4c0940b11f0089c6573a00461bc))",
4-
"download_url": "https://github.com/MorpheApp/morphe-manager/releases/download/v1.20.0/morphe-manager-1.20.0.apk",
5-
"signature_download_url": "https://github.com/MorpheApp/morphe-manager/releases/download/v1.20.0/morphe-manager-1.20.0.apk.asc",
6-
"version": "1.20.0"
2+
"created_at": "2026-06-03T09:53:24",
3+
"description": "# [1.21.0-dev.3](https://github.com/MorpheApp/morphe-manager/compare/v1.21.0-dev.2...v1.21.0-dev.3) (2026-06-03)\n\n\n### Bug Fixes\n\n* Invert vertical parallax axis ([503e38e](https://github.com/MorpheApp/morphe-manager/commit/503e38ec4461d3f4d6372358bb8b310ac8875f22))\n\n\n### Features\n\n* Add onboarding tour triggered after first patch ([#611](https://github.com/MorpheApp/morphe-manager/issues/611)) ([a7ab6df](https://github.com/MorpheApp/morphe-manager/commit/a7ab6dfb576d20a9ef527ec759ed33ee57189772))",
4+
"download_url": "https://github.com/MorpheApp/morphe-manager/releases/download/v1.21.0-dev.3/morphe-manager-1.21.0-dev.3.apk",
5+
"signature_download_url": "https://github.com/MorpheApp/morphe-manager/releases/download/v1.21.0-dev.3/morphe-manager-1.21.0-dev.3.apk.asc",
6+
"version": "1.21.0-dev.3"
77
}

app/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = 1.20.0
1+
version = 1.21.0-dev.3

app/src/main/java/app/morphe/manager/MainActivity.kt

Lines changed: 278 additions & 4 deletions
Large diffs are not rendered by default.

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/HomeScreen.kt

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@ import app.morphe.manager.R
2222
import app.morphe.manager.domain.manager.PreferencesManager
2323
import app.morphe.manager.domain.repository.PatchBundleRepository
2424
import app.morphe.manager.ui.model.HomeAppItem
25-
import app.morphe.manager.ui.screen.home.Android11Dialog
26-
import app.morphe.manager.ui.screen.home.HomeDialogs
27-
import app.morphe.manager.ui.screen.home.ManagerUpdateDetailsDialog
28-
import app.morphe.manager.ui.screen.home.SectionsLayout
25+
import app.morphe.manager.ui.screen.home.*
2926
import app.morphe.manager.ui.screen.settings.system.PrePatchInstallerDialog
3027
import app.morphe.manager.ui.viewmodel.HomeAndPatcherMessages
3128
import app.morphe.manager.ui.viewmodel.HomeViewModel
3229
import app.morphe.manager.ui.viewmodel.QuickPatchParams
3330
import app.morphe.manager.ui.viewmodel.UpdateViewModel
3431
import app.morphe.manager.util.*
32+
import kotlinx.coroutines.delay
33+
import kotlinx.coroutines.launch
3534
import org.koin.androidx.compose.koinViewModel
3635
import org.koin.compose.koinInject
3736
import org.koin.core.parameter.parametersOf
@@ -49,11 +48,14 @@ fun HomeScreen(
4948
prefs: PreferencesManager = koinInject(),
5049
usingMountInstallState: MutableState<Boolean>,
5150
bundleUpdateProgress: PatchBundleRepository.BundleUpdateProgress?,
51+
onboardingState: OnboardingState? = null,
52+
globalOnboardingState: GlobalOnboardingState? = null,
5253
patchTriggerPackage: String? = null,
5354
onPatchTriggerHandled: () -> Unit = {}
5455
) {
5556
val context = LocalContext.current
5657
val view = LocalView.current
58+
val scope = rememberCoroutineScope()
5759

5860
// Dialog states
5961
val showUpdateDetailsDialog = remember { mutableStateOf(false) }
@@ -168,7 +170,8 @@ fun HomeScreen(
168170
homeViewModel = homeViewModel,
169171
storagePickerLauncher = { openApkPicker() },
170172
openBundlePicker = { openBundlePicker() },
171-
patchesItem = patchesSheetItem
173+
patchesItem = patchesSheetItem,
174+
globalOnboardingState = globalOnboardingState
172175
)
173176

174177
// Pre-patching installer selection dialog for root-capable devices.
@@ -223,7 +226,15 @@ fun HomeScreen(
223226
onUnhideApp = { packageName -> homeViewModel.unhideApp(packageName) },
224227
onShowPatches = { item -> patchesSheetItem.value = item },
225228
showGestureHint = showGestureHint,
226-
onGestureHintShown = { homeViewModel.markSwipeGestureHintShown() },
229+
onGestureHintShown = {
230+
homeViewModel.markSwipeGestureHintShown()
231+
if (onboardingState != null && onboardingState.swipeActive) {
232+
scope.launch {
233+
delay(600)
234+
if (onboardingState.swipeActive) homeViewModel.triggerSwipeGestureHint()
235+
}
236+
}
237+
},
227238
hiddenAppItems = hiddenAppItems,
228239
installedAppsLoading = bundlePipelineLoading || homeViewModel.installedAppsLoading,
229240

@@ -248,7 +259,10 @@ fun HomeScreen(
248259
onSettingsClick = onSettingsClick,
249260

250261
// Expert mode
251-
isExpertModeEnabled = useExpertMode
262+
isExpertModeEnabled = useExpertMode,
263+
264+
// Onboarding
265+
onboardingState = onboardingState
252266
)
253267
}
254268
}

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

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,41 @@ import androidx.compose.animation.core.animateFloatAsState
1919
import androidx.compose.animation.core.tween
2020
import androidx.compose.foundation.layout.Column
2121
import androidx.compose.foundation.layout.fillMaxSize
22+
import androidx.compose.foundation.layout.fillMaxWidth
2223
import androidx.compose.foundation.layout.statusBarsPadding
23-
import androidx.compose.material3.AlertDialog
24-
import androidx.compose.material3.ExperimentalMaterial3Api
25-
import androidx.compose.material3.Text
26-
import androidx.compose.material3.TextButton
24+
import androidx.compose.material3.*
2725
import androidx.compose.runtime.*
2826
import androidx.compose.runtime.livedata.observeAsState
2927
import androidx.compose.runtime.saveable.rememberSaveable
3028
import androidx.compose.ui.Modifier
3129
import androidx.compose.ui.platform.LocalContext
3230
import androidx.compose.ui.platform.LocalView
3331
import androidx.compose.ui.res.stringResource
32+
import androidx.compose.ui.text.style.TextAlign
3433
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3534
import app.morphe.manager.R
3635
import app.morphe.manager.domain.installer.InstallerManager
36+
import app.morphe.manager.domain.manager.InstallerPreferenceTokens
3737
import app.morphe.manager.domain.manager.PreferencesManager
3838
import app.morphe.manager.ui.model.State
3939
import app.morphe.manager.ui.screen.patcher.*
40+
import app.morphe.manager.ui.screen.patcher.game.MiniGameState
4041
import app.morphe.manager.ui.screen.settings.advanced.NotificationPermissionDialog
4142
import app.morphe.manager.ui.screen.settings.system.InstallerSelectionDialog
43+
import app.morphe.manager.ui.screen.shared.LocalDialogSecondaryTextColor
4244
import app.morphe.manager.ui.screen.shared.MorpheAnimations
45+
import app.morphe.manager.ui.screen.shared.MorpheDialog
46+
import app.morphe.manager.ui.screen.shared.MorpheDialogButtonRow
4347
import app.morphe.manager.ui.viewmodel.InstallViewModel
4448
import app.morphe.manager.ui.viewmodel.PatcherViewModel
4549
import app.morphe.manager.util.APK_MIMETYPE
4650
import app.morphe.manager.util.EventEffect
47-
import app.morphe.manager.ui.screen.patcher.game.MiniGameState
4851
import app.morphe.manager.util.tag
4952
import com.google.android.gms.common.ConnectionResult
5053
import com.google.android.gms.common.GoogleApiAvailability
5154
import kotlinx.coroutines.delay
5255
import kotlinx.coroutines.isActive
56+
import kotlinx.coroutines.launch
5357
import org.koin.androidx.compose.koinViewModel
5458
import org.koin.compose.koinInject
5559
import kotlin.math.exp
@@ -70,7 +74,9 @@ fun PatcherScreen(
7074
installViewModel: InstallViewModel = koinViewModel(),
7175
prefs: PreferencesManager = koinInject(),
7276
onBackgroundSpeedChange: (Float) -> Unit = {},
73-
onPatchingCompleted: () -> Unit = {}
77+
onPatchingCompleted: () -> Unit = {},
78+
onStartTour: () -> Unit = {},
79+
onDeclineTour: () -> Unit = {}
7480
) {
7581
val context = LocalContext.current
7682
val view = LocalView.current
@@ -141,6 +147,28 @@ fun PatcherScreen(
141147
// Get output file from viewModel
142148
val outputFile = patcherViewModel.outputFile
143149

150+
val autoInstallWithShizuku by prefs.autoInstallWithShizuku.getAsState()
151+
val primaryInstallerPref by prefs.installerPrimary.getAsState()
152+
val promptInstallerOnInstall by prefs.promptInstallerOnInstall.getAsState()
153+
154+
// Auto-install: triggers when success screen appears with Shizuku as primary installer.
155+
// Skipped when promptInstallerOnInstall is enabled - user gets the manual Install button instead.
156+
LaunchedEffect(showSuccessScreen) {
157+
if (!showSuccessScreen) return@LaunchedEffect
158+
if (!autoInstallWithShizuku) return@LaunchedEffect
159+
if (usingMountInstall) return@LaunchedEffect
160+
if (patcherSucceeded != true) return@LaunchedEffect
161+
if (primaryInstallerPref != InstallerPreferenceTokens.SHIZUKU) return@LaunchedEffect
162+
if (promptInstallerOnInstall) return@LaunchedEffect
163+
if (installViewModel.installState !is InstallViewModel.InstallState.Ready) return@LaunchedEffect
164+
delay(300)
165+
installViewModel.install(
166+
outputFile = outputFile,
167+
originalPackageName = patcherViewModel.packageName,
168+
onPersistApp = { pkg, type -> patcherViewModel.persistPatchedApp(pkg, type) }
169+
)
170+
}
171+
144172
// Progress animation logic: drives displayProgress and showSuccessScreen
145173
LaunchedEffect(patcherSucceeded) {
146174
var lastProgressUpdate = 0.0f
@@ -257,10 +285,11 @@ fun PatcherScreen(
257285
val errorMessage by remember { derivedStateOf { (installViewModel.installState as? InstallViewModel.InstallState.Error)?.message } }
258286

259287
val showInstalledSourceConflictDialog = remember { mutableStateOf(false) }
288+
val shouldPromptTour by patcherViewModel.shouldPromptTour.collectAsStateWithLifecycle()
260289

261290
LaunchedEffect(installState) {
262291
if (installState is InstallViewModel.InstallState.Installed) {
263-
patcherViewModel.triggerNotificationPromptIfNeeded()
292+
patcherViewModel.triggerPostInstallPromptsIfNeeded()
264293
}
265294
if (installState is InstallViewModel.InstallState.Conflict && autoHandleConflict) {
266295
showInstalledSourceConflictDialog.value = true
@@ -301,6 +330,41 @@ fun PatcherScreen(
301330
)
302331
}
303332

333+
// Tour prompt dialog shown after first successful install
334+
if (shouldPromptTour) {
335+
MorpheDialog(
336+
onDismissRequest = {
337+
patcherViewModel.consumeTourPrompt()
338+
onDeclineTour()
339+
},
340+
title = stringResource(R.string.tour_prompt_title),
341+
footer = {
342+
MorpheDialogButtonRow(
343+
primaryText = stringResource(R.string.tour_prompt_confirm),
344+
onPrimaryClick = {
345+
patcherViewModel.consumeTourPrompt()
346+
onStartTour()
347+
onBackClick()
348+
},
349+
secondaryText = stringResource(R.string.skip),
350+
onSecondaryClick = {
351+
patcherViewModel.consumeTourPrompt()
352+
onDeclineTour()
353+
onBackClick()
354+
}
355+
)
356+
}
357+
) {
358+
Text(
359+
text = stringResource(R.string.tour_prompt_desc),
360+
style = MaterialTheme.typography.bodyLarge,
361+
color = LocalDialogSecondaryTextColor.current,
362+
textAlign = TextAlign.Center,
363+
modifier = Modifier.fillMaxWidth()
364+
)
365+
}
366+
}
367+
304368
// Activity launcher for handling plugin activities or external installs
305369
val activityLauncher = rememberLauncherForActivityResult(
306370
contract = ActivityResultContracts.StartActivityForResult(),
@@ -425,7 +489,12 @@ fun PatcherScreen(
425489
onConfirm = { selectedToken ->
426490
installViewModel.proceedWithSelectedInstaller(selectedToken)
427491
},
428-
onOpenShizuku = installerManager::openShizukuApp
492+
onOpenShizuku = installerManager::openShizukuApp,
493+
autoInstallEnabled = autoInstallWithShizuku,
494+
onAutoInstallToggle = { enabled ->
495+
scope.launch { prefs.autoInstallWithShizuku.update(enabled) }
496+
},
497+
installerPromptEnabled = promptInstallerOnInstall
429498
)
430499
}
431500

@@ -468,8 +537,16 @@ fun PatcherScreen(
468537
}
469538

470539
PatcherState.SUCCESS -> {
540+
val effectiveIsInstalling = isInstalling || (
541+
autoInstallWithShizuku &&
542+
primaryInstallerPref == InstallerPreferenceTokens.SHIZUKU &&
543+
patcherSucceeded == true &&
544+
!usingMountInstall &&
545+
!promptInstallerOnInstall &&
546+
installState is InstallViewModel.InstallState.Ready
547+
)
471548
PatchingSuccess(
472-
isInstalling = isInstalling,
549+
isInstalling = effectiveIsInstalling,
473550
isInstalled = isInstalled,
474551
isError = isError,
475552
isConflict = isConflict,

0 commit comments

Comments
 (0)