diff --git a/app/src/main/java/io/github/sankalp/lunox/LauncherActivity.java b/app/src/main/java/io/github/sankalp/lunox/LauncherActivity.java
index c0a9cb7..505da8a 100644
--- a/app/src/main/java/io/github/sankalp/lunox/LauncherActivity.java
+++ b/app/src/main/java/io/github/sankalp/lunox/LauncherActivity.java
@@ -89,6 +89,7 @@ import io.github.sankalp.lunox.dialogs.FrozenAppsDialogs;
import io.github.sankalp.lunox.dialogs.GlobalColorSizeDialog;
import io.github.sankalp.lunox.dialogs.GlobalSettingsDialog;
import io.github.sankalp.lunox.dialogs.HiddenAppsDialogs;
+import io.github.sankalp.lunox.dialogs.OnboardingTutorialDialog;
import io.github.sankalp.lunox.dialogs.PaddingDialog;
import io.github.sankalp.lunox.dialogs.RenameInputDialogs;
import io.github.sankalp.lunox.model.Apps;
@@ -314,6 +315,9 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
setDefaultButton = findViewById(R.id.set_default_button);
setDefaultButton.setOnClickListener(this);
+ // Show onboarding tutorial for first-time users
+ showOnboardingTutorialIfNeeded();
+
}
private void setSearchBoxListeners() {
@@ -1343,6 +1347,21 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
/**
* Show detailed double tap to lock dialog
*/
+ private void showOnboardingTutorialIfNeeded() {
+ if (DbUtils.isFirstTimeUser() && !DbUtils.isTutorialCompleted()) {
+ // Use a post to ensure the UI is fully loaded before showing the dialog
+ mHomeLayout.post(() -> {
+ OnboardingTutorialDialog tutorialDialog = new OnboardingTutorialDialog(this, this);
+ tutorialDialog.show();
+ });
+ }
+ }
+
+ public void showOnboardingTutorial() {
+ OnboardingTutorialDialog tutorialDialog = new OnboardingTutorialDialog(this, this);
+ tutorialDialog.show();
+ }
+
private void showDoubleTapLockDialog() {
showDialog(new DoubleTapLockDialog(this, this), true);
}
diff --git a/app/src/main/java/io/github/sankalp/lunox/dialogs/GlobalSettingsDialog.java b/app/src/main/java/io/github/sankalp/lunox/dialogs/GlobalSettingsDialog.java
index 97c4fc7..8f07287 100644
--- a/app/src/main/java/io/github/sankalp/lunox/dialogs/GlobalSettingsDialog.java
+++ b/app/src/main/java/io/github/sankalp/lunox/dialogs/GlobalSettingsDialog.java
@@ -91,6 +91,7 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
findViewById(R.id.settings_padding).setOnClickListener(this);
findViewById(R.id.settings_color_size).setOnClickListener(this);
findViewById(R.id.settings_sort_app_by).setOnClickListener(this);
+ findViewById(R.id.settings_tutorial).setOnClickListener(this);
findViewById(R.id.settings_restart_launcher).setOnClickListener(this);
//TODO: remove this var
@@ -159,6 +160,8 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
} else if (id == R.id.settings_padding) {
launcherActivity.closeGlobalSettingsDialog();
launcherActivity.setPadding();
+ } else if (id == R.id.settings_tutorial) {
+ showTutorial();
} else if (id == R.id.settings_restart_launcher) {
launcherActivity.recreate();
} else if (id == R.id.settings_double_tap_lock) {
@@ -428,4 +431,9 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
}
}
+ private void showTutorial() {
+ launcherActivity.closeGlobalSettingsDialog();
+ launcherActivity.showOnboardingTutorial();
+ }
+
}
diff --git a/app/src/main/java/io/github/sankalp/lunox/dialogs/OnboardingTutorialDialog.java b/app/src/main/java/io/github/sankalp/lunox/dialogs/OnboardingTutorialDialog.java
new file mode 100644
index 0000000..765fb0c
--- /dev/null
+++ b/app/src/main/java/io/github/sankalp/lunox/dialogs/OnboardingTutorialDialog.java
@@ -0,0 +1,155 @@
+/*
+ * Lunox
+ * Copyright (C) 2025 Sankalp Tharu
+ *
+ * This file is part of Last Launcher (forked).
+ * Licensed under the GNU General Public License v3 or later.
+ * See .
+ *
+*/
+
+package io.github.sankalp.lunox.dialogs;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+
+import io.github.sankalp.lunox.LauncherActivity;
+import io.github.sankalp.lunox.R;
+import io.github.sankalp.lunox.utils.DbUtils;
+
+/**
+ * Dialog for showing onboarding tutorial to new users
+ */
+public class OnboardingTutorialDialog extends Dialog implements View.OnClickListener {
+
+ private final LauncherActivity launcherActivity;
+ private final Context context;
+
+ private TextView titleView;
+ private TextView stepIndicatorView;
+ private TextView contentView;
+ private TextView skipButton;
+ private TextView previousButton;
+ private TextView nextButton;
+
+ private int currentStep = 1;
+ private static final int TOTAL_STEPS = 7;
+
+ // Tutorial step data
+ private final int[] stepTitles = {
+ R.string.tutorial_step_1_title,
+ R.string.tutorial_step_2_title,
+ R.string.tutorial_step_3_title,
+ R.string.tutorial_step_4_title,
+ R.string.tutorial_step_5_title,
+ R.string.tutorial_step_6_title,
+ R.string.tutorial_step_7_title
+ };
+
+ private final int[] stepContents = {
+ R.string.tutorial_step_1_content,
+ R.string.tutorial_step_2_content,
+ R.string.tutorial_step_3_content,
+ R.string.tutorial_step_4_content,
+ R.string.tutorial_step_5_content,
+ R.string.tutorial_step_6_content,
+ R.string.tutorial_step_7_content
+ };
+
+ public OnboardingTutorialDialog(Context context, LauncherActivity launcherActivity) {
+ super(context);
+ this.context = context;
+ this.launcherActivity = launcherActivity;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.dialog_onboarding_tutorial);
+
+ // Make background transparent for rounded corners
+ if (getWindow() != null) {
+ getWindow().setBackgroundDrawableResource(android.R.color.transparent);
+ }
+
+ // Initialize views
+ titleView = findViewById(R.id.tv_tutorial_title);
+ stepIndicatorView = findViewById(R.id.tv_step_indicator);
+ contentView = findViewById(R.id.tv_tutorial_content);
+ skipButton = findViewById(R.id.btn_skip);
+ previousButton = findViewById(R.id.btn_previous);
+ nextButton = findViewById(R.id.btn_next);
+
+ // Set click listeners
+ skipButton.setOnClickListener(this);
+ previousButton.setOnClickListener(this);
+ nextButton.setOnClickListener(this);
+
+ // Update content for first step
+ updateStepContent();
+
+ // Prevent dismissing by tapping outside
+ setCancelable(false);
+ }
+
+ @Override
+ public void onClick(View view) {
+ int id = view.getId();
+
+ if (id == R.id.btn_skip) {
+ finishTutorial();
+ } else if (id == R.id.btn_previous) {
+ if (currentStep > 1) {
+ currentStep--;
+ updateStepContent();
+ }
+ } else if (id == R.id.btn_next) {
+ if (currentStep < TOTAL_STEPS) {
+ currentStep++;
+ updateStepContent();
+ } else {
+ finishTutorial();
+ }
+ }
+ }
+
+ private void updateStepContent() {
+ // Update title
+ titleView.setText(stepTitles[currentStep - 1]);
+
+ // Update step indicator
+ stepIndicatorView.setText(currentStep + " / " + TOTAL_STEPS);
+
+ // Update content
+ contentView.setText(stepContents[currentStep - 1]);
+
+ // Update button visibility and text
+ if (currentStep == 1) {
+ previousButton.setVisibility(View.GONE);
+ } else {
+ previousButton.setVisibility(View.VISIBLE);
+ }
+
+ if (currentStep == TOTAL_STEPS) {
+ nextButton.setText(R.string.onboarding_finish);
+ skipButton.setVisibility(View.GONE);
+ } else {
+ nextButton.setText(R.string.onboarding_next);
+ skipButton.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void finishTutorial() {
+ // Mark tutorial as completed
+ DbUtils.setTutorialCompleted(true);
+ DbUtils.setFirstTimeUser(false);
+
+ // Dismiss dialog
+ dismiss();
+ }
+}
diff --git a/app/src/main/java/io/github/sankalp/lunox/utils/DbUtils.java b/app/src/main/java/io/github/sankalp/lunox/utils/DbUtils.java
index 22d69b1..49358e1 100644
--- a/app/src/main/java/io/github/sankalp/lunox/utils/DbUtils.java
+++ b/app/src/main/java/io/github/sankalp/lunox/utils/DbUtils.java
@@ -63,6 +63,8 @@ public class DbUtils {
private static final String APPS_SORTS_TYPE = "apps_sorts_types";
private static final String DOUBLE_TAP_TO_LOCK = "double_tap_to_lock";
private static final String SEARCH_BAR_GESTURE = "search_bar_gesture";
+ private static final String FIRST_TIME_USER = "first_time_user";
+ private static final String TUTORIAL_COMPLETED = "tutorial_completed";
public static void init(Context context) {
@@ -395,6 +397,22 @@ public class DbUtils {
SpUtils.getInstance().putBoolean(DOUBLE_TAP_TO_LOCK, enabled);
}
+ public static boolean isFirstTimeUser() {
+ return SpUtils.getInstance().getBoolean(FIRST_TIME_USER, true);
+ }
+
+ public static void setFirstTimeUser(boolean isFirstTime) {
+ SpUtils.getInstance().putBoolean(FIRST_TIME_USER, isFirstTime);
+ }
+
+ public static boolean isTutorialCompleted() {
+ return SpUtils.getInstance().getBoolean(TUTORIAL_COMPLETED, false);
+ }
+
+ public static void setTutorialCompleted(boolean completed) {
+ SpUtils.getInstance().putBoolean(TUTORIAL_COMPLETED, completed);
+ }
+
/**
* Check if search bar gesture is enabled
*/
diff --git a/app/src/main/res/layout/dialog_global_settings.xml b/app/src/main/res/layout/dialog_global_settings.xml
index 07b1ee7..2a93008 100644
--- a/app/src/main/res/layout/dialog_global_settings.xml
+++ b/app/src/main/res/layout/dialog_global_settings.xml
@@ -295,6 +295,23 @@
android:background="#DEDEDE"
android:foregroundGravity="center_vertical|center_horizontal" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2352925..18ef733 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -107,6 +107,38 @@
Double Tap Lock:On
Double Tap Lock:Off
+
+ Welcome to Lunox
+ Skip
+ Next
+ Previous
+ Finish
+ Close
+
+
+ Welcome to Lunox Launcher
+ Lunox is a minimalist text-based launcher that puts simplicity first. All your apps are displayed as text in a clean, distraction-free interface.
+
+ Opening Apps
+ Simply tap on any app name to open it. Apps are sorted alphabetically by default, but you can change the sorting in settings.
+
+ Search Functionality
+ Swipe down to up anywhere on the screen to open the search bar. Start typing to quickly find and launch any app.
+
+ App Options
+ Long press on any app to access options like rename, hide, uninstall, change colors, and more customization features.
+
+ Launcher Settings
+ Long press on empty space to access launcher settings. Here you can change themes, fonts, sorting options, and other preferences.
+
+ Customization
+ Personalize your launcher with different themes, custom colors for apps, font options, and various sorting methods to make it truly yours.
+
+ You\'re All Set!
+ That\'s all you need to know to get started with Lunox. Enjoy your clean, minimalist launcher experience!
+
+ Show Tutorial
+
Lunox Accessibility Service for lock screen functionality. This service allows double tap to lock the screen.
Set as default launcher
diff --git a/fastlane/metadata/android/en-US/changelogs/20.txt b/fastlane/metadata/android/en-US/changelogs/20.txt
index 09ec00c..aaa2c4a 100644
--- a/fastlane/metadata/android/en-US/changelogs/20.txt
+++ b/fastlane/metadata/android/en-US/changelogs/20.txt
@@ -14,6 +14,8 @@
- Fixed Duplicate Dialogs on Long Press
- Enhanced gesture detection for better responsiveness
- Fixed WhiteOnGrey and BlackOnGrey themes using distinct colors for better visibility
+- Added Onboarding Tutorial for new users with step-by-step walkthrough
+- Tutorial can be replayed from launcher settings
* Technical Improvements
@@ -23,6 +25,8 @@
- Improved dialog management to prevent duplicate dialogs
- Better empty area detection for long press functionality
- Enhanced search functionality with smooth animations
+- Added comprehensive onboarding system with multi-step tutorial dialog
+- Implemented first-time user detection and tutorial completion tracking
* Project Source Code Changes