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