✨ 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.GlobalColorSizeDialog;
|
||||||
import io.github.sankalp.lunox.dialogs.GlobalSettingsDialog;
|
import io.github.sankalp.lunox.dialogs.GlobalSettingsDialog;
|
||||||
import io.github.sankalp.lunox.dialogs.HiddenAppsDialogs;
|
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.PaddingDialog;
|
||||||
import io.github.sankalp.lunox.dialogs.RenameInputDialogs;
|
import io.github.sankalp.lunox.dialogs.RenameInputDialogs;
|
||||||
import io.github.sankalp.lunox.model.Apps;
|
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 = findViewById(R.id.set_default_button);
|
||||||
setDefaultButton.setOnClickListener(this);
|
setDefaultButton.setOnClickListener(this);
|
||||||
|
|
||||||
|
// Show onboarding tutorial for first-time users
|
||||||
|
showOnboardingTutorialIfNeeded();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSearchBoxListeners() {
|
private void setSearchBoxListeners() {
|
||||||
@@ -1343,6 +1347,21 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
|||||||
/**
|
/**
|
||||||
* Show detailed double tap to lock dialog
|
* 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() {
|
private void showDoubleTapLockDialog() {
|
||||||
showDialog(new DoubleTapLockDialog(this, this), true);
|
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_padding).setOnClickListener(this);
|
||||||
findViewById(R.id.settings_color_size).setOnClickListener(this);
|
findViewById(R.id.settings_color_size).setOnClickListener(this);
|
||||||
findViewById(R.id.settings_sort_app_by).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);
|
findViewById(R.id.settings_restart_launcher).setOnClickListener(this);
|
||||||
|
|
||||||
//TODO: remove this var
|
//TODO: remove this var
|
||||||
@@ -159,6 +160,8 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
|
|||||||
} else if (id == R.id.settings_padding) {
|
} else if (id == R.id.settings_padding) {
|
||||||
launcherActivity.closeGlobalSettingsDialog();
|
launcherActivity.closeGlobalSettingsDialog();
|
||||||
launcherActivity.setPadding();
|
launcherActivity.setPadding();
|
||||||
|
} else if (id == R.id.settings_tutorial) {
|
||||||
|
showTutorial();
|
||||||
} else if (id == R.id.settings_restart_launcher) {
|
} else if (id == R.id.settings_restart_launcher) {
|
||||||
launcherActivity.recreate();
|
launcherActivity.recreate();
|
||||||
} else if (id == R.id.settings_double_tap_lock) {
|
} 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 APPS_SORTS_TYPE = "apps_sorts_types";
|
||||||
private static final String DOUBLE_TAP_TO_LOCK = "double_tap_to_lock";
|
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 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) {
|
public static void init(Context context) {
|
||||||
@@ -395,6 +397,22 @@ public class DbUtils {
|
|||||||
SpUtils.getInstance().putBoolean(DOUBLE_TAP_TO_LOCK, enabled);
|
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
|
* Check if search bar gesture is enabled
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -295,6 +295,23 @@
|
|||||||
android:background="#DEDEDE"
|
android:background="#DEDEDE"
|
||||||
android:foregroundGravity="center_vertical|center_horizontal" />
|
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
|
<TextView
|
||||||
android:id="@+id/settings_restart_launcher"
|
android:id="@+id/settings_restart_launcher"
|
||||||
android:layout_width="match_parent"
|
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_enabled">Double Tap Lock:On</string>
|
||||||
<string name="double_tap_to_lock_disabled">Double Tap Lock:Off</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 -->
|
<!-- 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="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>
|
<string name="set_as_default_launcher">Set as default launcher</string>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
- Fixed Duplicate Dialogs on Long Press
|
- Fixed Duplicate Dialogs on Long Press
|
||||||
- Enhanced gesture detection for better responsiveness
|
- Enhanced gesture detection for better responsiveness
|
||||||
- Fixed WhiteOnGrey and BlackOnGrey themes using distinct colors for better visibility
|
- 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
|
* Technical Improvements
|
||||||
|
|
||||||
@@ -23,6 +25,8 @@
|
|||||||
- Improved dialog management to prevent duplicate dialogs
|
- Improved dialog management to prevent duplicate dialogs
|
||||||
- Better empty area detection for long press functionality
|
- Better empty area detection for long press functionality
|
||||||
- Enhanced search functionality with smooth animations
|
- 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
|
* Project Source Code Changes
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user