Skip to content

Commit a7ab6df

Browse files
authored
feat: Add onboarding tour triggered after first patch (#611)
1 parent 09d8c15 commit a7ab6df

19 files changed

Lines changed: 1250 additions & 244 deletions

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/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: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,17 @@ 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
@@ -41,7 +40,10 @@ import app.morphe.manager.ui.screen.patcher.*
4140
import app.morphe.manager.ui.screen.patcher.game.MiniGameState
4241
import app.morphe.manager.ui.screen.settings.advanced.NotificationPermissionDialog
4342
import app.morphe.manager.ui.screen.settings.system.InstallerSelectionDialog
43+
import app.morphe.manager.ui.screen.shared.LocalDialogSecondaryTextColor
4444
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
4547
import app.morphe.manager.ui.viewmodel.InstallViewModel
4648
import app.morphe.manager.ui.viewmodel.PatcherViewModel
4749
import app.morphe.manager.util.APK_MIMETYPE
@@ -72,7 +74,9 @@ fun PatcherScreen(
7274
installViewModel: InstallViewModel = koinViewModel(),
7375
prefs: PreferencesManager = koinInject(),
7476
onBackgroundSpeedChange: (Float) -> Unit = {},
75-
onPatchingCompleted: () -> Unit = {}
77+
onPatchingCompleted: () -> Unit = {},
78+
onStartTour: () -> Unit = {},
79+
onDeclineTour: () -> Unit = {}
7680
) {
7781
val context = LocalContext.current
7882
val view = LocalView.current
@@ -281,10 +285,11 @@ fun PatcherScreen(
281285
val errorMessage by remember { derivedStateOf { (installViewModel.installState as? InstallViewModel.InstallState.Error)?.message } }
282286

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

285290
LaunchedEffect(installState) {
286291
if (installState is InstallViewModel.InstallState.Installed) {
287-
patcherViewModel.triggerNotificationPromptIfNeeded()
292+
patcherViewModel.triggerPostInstallPromptsIfNeeded()
288293
}
289294
if (installState is InstallViewModel.InstallState.Conflict && autoHandleConflict) {
290295
showInstalledSourceConflictDialog.value = true
@@ -325,6 +330,41 @@ fun PatcherScreen(
325330
)
326331
}
327332

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+
328368
// Activity launcher for handling plugin activities or external installs
329369
val activityLauncher = rememberLauncherForActivityResult(
330370
contract = ActivityResultContracts.StartActivityForResult(),

0 commit comments

Comments
 (0)