✨ feat(ui): add onboarding tutorial for new users
This commit is contained in:
8 files changed
+354
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -295,6 +295,23 @@
|
||||
android:background="#DEDEDE"
|
||||
android:foregroundGravity="center_vertical|center_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_tutorial"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="@string/tutorial_replay"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="170dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_gravity="center|center_horizontal|center_vertical"
|
||||
android:background="#DEDEDE"
|
||||
android:foregroundGravity="center_vertical|center_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings_restart_launcher"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@drawable/dialog_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:id="@+id/tv_tutorial_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="start"
|
||||
android:text="@string/onboarding_title"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Step indicator -->
|
||||
<TextView
|
||||
android:id="@+id/tv_step_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:text="1 / 7"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:alpha="0.7" />
|
||||
|
||||
<!-- Content -->
|
||||
<TextView
|
||||
android:id="@+id/tv_tutorial_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:text="@string/tutorial_step_1_content"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<!-- Buttons container -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Skip button -->
|
||||
<TextView
|
||||
android:id="@+id/btn_skip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="@string/onboarding_skip"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<!-- Previous button -->
|
||||
<TextView
|
||||
android:id="@+id/btn_previous"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="@string/onboarding_previous"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Next/Finish button -->
|
||||
<TextView
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="@string/onboarding_next"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -107,6 +107,38 @@
|
||||
<string name="double_tap_to_lock_enabled">Double Tap Lock:On</string>
|
||||
<string name="double_tap_to_lock_disabled">Double Tap Lock:Off</string>
|
||||
|
||||
<!-- Onboarding Tutorial -->
|
||||
<string name="onboarding_title">Welcome to Lunox</string>
|
||||
<string name="onboarding_skip">Skip</string>
|
||||
<string name="onboarding_next">Next</string>
|
||||
<string name="onboarding_previous">Previous</string>
|
||||
<string name="onboarding_finish">Finish</string>
|
||||
<string name="onboarding_close">Close</string>
|
||||
|
||||
<!-- Tutorial Steps -->
|
||||
<string name="tutorial_step_1_title">Welcome to Lunox Launcher</string>
|
||||
<string name="tutorial_step_1_content">Lunox is a minimalist text-based launcher that puts simplicity first. All your apps are displayed as text in a clean, distraction-free interface.</string>
|
||||
|
||||
<string name="tutorial_step_2_title">Opening Apps</string>
|
||||
<string name="tutorial_step_2_content">Simply tap on any app name to open it. Apps are sorted alphabetically by default, but you can change the sorting in settings.</string>
|
||||
|
||||
<string name="tutorial_step_3_title">Search Functionality</string>
|
||||
<string name="tutorial_step_3_content">Swipe down to up anywhere on the screen to open the search bar. Start typing to quickly find and launch any app.</string>
|
||||
|
||||
<string name="tutorial_step_4_title">App Options</string>
|
||||
<string name="tutorial_step_4_content">Long press on any app to access options like rename, hide, uninstall, change colors, and more customization features.</string>
|
||||
|
||||
<string name="tutorial_step_5_title">Launcher Settings</string>
|
||||
<string name="tutorial_step_5_content">Long press on empty space to access launcher settings. Here you can change themes, fonts, sorting options, and other preferences.</string>
|
||||
|
||||
<string name="tutorial_step_6_title">Customization</string>
|
||||
<string name="tutorial_step_6_content">Personalize your launcher with different themes, custom colors for apps, font options, and various sorting methods to make it truly yours.</string>
|
||||
|
||||
<string name="tutorial_step_7_title">You\'re All Set!</string>
|
||||
<string name="tutorial_step_7_content">That\'s all you need to know to get started with Lunox. Enjoy your clean, minimalist launcher experience!</string>
|
||||
|
||||
<string name="tutorial_replay">Show Tutorial</string>
|
||||
|
||||
<!-- Accessibility service description -->
|
||||
<string name="accessibility_service_description">Lunox Accessibility Service for lock screen functionality. This service allows double tap to lock the screen.</string>
|
||||
<string name="set_as_default_launcher">Set as default launcher</string>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user