From 6b9e41339524b3a0c43548d03286dfd76e60b937 Mon Sep 17 00:00:00 2001 From: Mehad Ali Date: Sun, 20 Oct 2024 18:43:58 -0400 Subject: [PATCH 1/3] Implemented interactive tutorial functionality across Android App. - Included user interaction required for some steps, before next step appeared. - Still need to add more details to RemotePage instructions. --- .../dairemote_app/InteractionPage.java | 106 +++++++++++++- .../example/dairemote_app/MainActivity.java | 134 ++++++++++++++++-- .../example/dairemote_app/ServersPage.java | 61 ++++++++ .../app/src/main/res/layout/activity_main.xml | 28 ++++ 4 files changed, 314 insertions(+), 15 deletions(-) diff --git a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java index 5b85f74..7d065b2 100644 --- a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java +++ b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java @@ -3,16 +3,20 @@ import android.content.Context; import android.content.Intent; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.os.Vibrator; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.GestureDetector; +import android.view.Gravity; import android.view.KeyEvent; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; @@ -22,6 +26,7 @@ import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.view.GravityCompat; @@ -44,6 +49,14 @@ public class InteractionPage extends AppCompatActivity implements NavigationView Toolbar keyboardToolbar; TextView moreOpts; private int currentPageIndex = 0; + + // vars for tutorial + private boolean tutorialOn = false; // tracks if tutorial is active + private int currentStep = 0; + private boolean serverNavigationButtonClicked = false; // tracks if main icon button was clicked + private boolean nextStepPending = false; // tracks if "Next" was clicked but action is pending + + private String[][][] keyboardExtraRows = { { // Page 1 {"F1", "F2", "F3", "F4", "F5", "F6"}, // Row 1 @@ -99,6 +112,14 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_interaction_page); + // checks if tutorial is still ongoing + tutorialOn = getIntent().getBooleanExtra("tutorialOn", false); + currentStep = getIntent().getIntExtra("currentStep", 0); + if (tutorialOn) { + continueTutorial(currentStep); + } + + FrameLayout touchpadFrame = findViewById(R.id.touchpadFrame); Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); @@ -542,6 +563,8 @@ public void onBackPressed() { drawerLayout.closeDrawer(GravityCompat.START); } else if (!(editText.getVisibility() == View.VISIBLE)) { Intent intent = new Intent(InteractionPage.this, MainActivity.class); + intent.putExtra("tutorialOn", tutorialOn); + intent.putExtra("currentStep", currentStep); startActivity(intent); } else { super.onBackPressed(); @@ -552,13 +575,22 @@ public void onBackPressed() { public boolean onNavigationItemSelected(@NonNull MenuItem item) { Intent intent; int itemId = item.getItemId(); - Log.d("Navigation", "Item selected: " + itemId); if (itemId == R.id.nav_home) { intent = new Intent(this, MainActivity.class); startActivity(intent); } else if (itemId == R.id.nav_server) { intent = new Intent(this, ServersPage.class); + + // if tutorial is still active on navigation button clicked + if (tutorialOn) { + serverNavigationButtonClicked = true; + checkIfStepCompleted(); + // passing data of tutorial to interactionPage + intent.putExtra("tutorialOn", tutorialOn); + intent.putExtra("currentStep", currentStep); + } + startActivity(intent); } else if (itemId == R.id.nav_help) { intent = new Intent(this, InstructionsPage.class); @@ -571,4 +603,76 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { drawerLayout.closeDrawer(GravityCompat.START); return true; } + + private void continueTutorial(int step) { + // resumes showing tutorial steps + showSteps(step); + } + + private void showSteps(int step) { + switch (step) { + case 1: + showCustomDialog("Remote Page", "Click the help icon above for instructions on how to use the remote.", Gravity.TOP | Gravity.RIGHT, 100, 200); + break; + case 2: + showCustomDialog("Lower Panel Buttons", "Display Modes, Audio Cycling, Hotkeys, App Keyboard", Gravity.BOTTOM | Gravity.RIGHT, 100, 200); + break; + case 3: + showCustomDialog("ToolBar", "Click on the ToolBar button to navigate between pages.", Gravity.TOP | Gravity.RIGHT, 100, 200); + break; + default: + break; + } + } + // shows pop up for each step in customized position (depending on location of feature) + private void showCustomDialog(String title, String message, int gravity, int xOffset, int yOffset) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(title); + builder.setMessage(message); + + // PositiveButton representing "Next" for moving to the next step + builder.setPositiveButton("Next", (dialog, which) -> { + Log.d("Tutorial", "Current Step: " + currentStep); + + if (currentStep == 3) { + nextStepPending = true; + checkIfStepCompleted(); + } + else { + currentStep++; + showSteps(currentStep); + } + + }); + + // NegativeButton representing "Exit Tour" to stop the tutorial + builder.setNegativeButton("Exit Tour", (dialog, which) -> { + dialog.dismiss(); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + + // sets custom position + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams params = window.getAttributes(); + params.gravity = gravity; + params.x = xOffset; + params.y = yOffset; + window.setAttributes(params); + } + + } + + // checking if specific action was completed for current step + private void checkIfStepCompleted() { + if (serverNavigationButtonClicked) { + nextStepPending = false; + currentStep++; + showSteps(currentStep); + } + } + + } \ No newline at end of file diff --git a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java index 4b51f97..0dc64bb 100644 --- a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java +++ b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java @@ -4,13 +4,17 @@ import android.graphics.Color; import android.os.Bundle; import android.util.Log; +import android.view.Gravity; import android.view.MenuItem; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.widget.ImageButton; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.view.GravityCompat; @@ -29,6 +33,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On ImageButton remotePage; + // vars for tutorial + private boolean tutorialOn = false; // tracks if tutorial is active + private int currentStep = 0; + private boolean mainButtonClicked = false; // tracks if main icon button was clicked + private boolean nextStepPending = false; // tracks if "Next" was clicked but action is pending + + public void notifyUser(String msg, String color) { TextView toolbarNotif = findViewById(R.id.toolbarNotification); toolbarNotif.setText(msg); @@ -87,6 +98,15 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + // find the help button by its ID + ImageButton helpButton = findViewById(R.id.helpButton); + helpButton.setOnClickListener(v -> { + if (!tutorialOn) { + tutorialOn = true; + startTutorial(); // triggers tutorial + } + }); + drawerSetup(R.id.nav_home); // Initially Hide the toolbar notification @@ -98,19 +118,24 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onClick(View v) { v.animate().scaleX(1.2f).scaleY(1.2f) // Scale the button up to 120% of its original size - .setDuration(150) // Duration of the scale up animation - .withEndAction(new Runnable() { - @Override - public void run() { - // Scale back to normal size - v.animate().scaleX(1f) - .scaleY(1f) - .setDuration(150) // Duration of the scale down animation - .start(); - } - }) - .start(); - // Initialize the connection manager + .setDuration(150) // Duration of the scale up animation + .withEndAction(new Runnable() { + @Override + public void run() { + // Scale back to normal size + v.animate().scaleX(1f) + .scaleY(1f) + .setDuration(150) // Duration of the scale down animation + .start(); + } + }) + .start(); + if (tutorialOn) { + mainButtonClicked = true; + checkIfStepCompleted(); + } + + // Initialize the connection manager // Establish connection to host if not already established and not declined prior if (!ConnectionManager.connectionEstablished) { ConnectionManager.hostSearchInBackground(new HostSearchCallback() { @@ -121,10 +146,12 @@ public void onHostFound(List serverIps) { } Log.i("MainActivity", "Hosts found: " + serverIps); + //!! Implement logic to select the host String selectedHost = serverIps.get(0); bkgrdNotifyUser("Connecting to " + selectedHost, "#c3cf1b"); + // Initialize ConnectionManager with the found server IP connectionManager = new ConnectionManager(selectedHost); if (!connectionManager.initializeConnection()) { @@ -133,6 +160,16 @@ public void onHostFound(List serverIps) { } else { bkgrdNotifyUser("Connection approved", "#3fcf1b"); Intent intent = new Intent(MainActivity.this, InteractionPage.class); + + //checks if main button action was taken + if (tutorialOn) { + mainButtonClicked = true; + checkIfStepCompleted(); + // passing data of tutorial to interactionPage + intent.putExtra("tutorialOn", tutorialOn); + intent.putExtra("currentStep", currentStep); + } + startActivity(intent); } } @@ -151,6 +188,7 @@ public void onError(String error) { }); } + @Override public void onBackPressed() { if (drawerLayout.isDrawerOpen(GravityCompat.START)) { @@ -164,12 +202,14 @@ public void onBackPressed() { } } + @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { Intent intent; int itemId = item.getItemId(); Log.d("MainActivity", "Item selected: " + itemId); + if (itemId == R.id.nav_remote) { intent = new Intent(this, InteractionPage.class); startActivity(intent); @@ -184,7 +224,73 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { startActivity(intent); } + drawerLayout.closeDrawer(GravityCompat.START); return true; } -} \ No newline at end of file + + + + + // functions to trigger tutorial + private void startTutorial() { + currentStep = 0; + showSteps(currentStep); + } + + + private void showSteps(int step) { + switch (step) { + case 0: + showCustomDialog("Interactive Tutorial", "Tap on the center icon to connect to your local host. Ensure the desktop application is open.", Gravity.TOP | Gravity.LEFT, 100, 200); + break; + + default: + break; + } + } + + + // shows pop up for each step in customized position (depending on location of feature) + private void showCustomDialog(String title, String message, int gravity, int xOffset, int yOffset) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(title); + builder.setMessage(message); + + // PositiveButton representing "Next" for moving to the next step + builder.setPositiveButton("Next", (dialog, which) -> { + nextStepPending = true; + checkIfStepCompleted(); + }); + + // NegativeButton representing "Exit Tour" to stop the tutorial + builder.setNegativeButton("Exit Tour", (dialog, which) -> { + tutorialOn = false; + dialog.dismiss(); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + + // sets custom position + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams params = window.getAttributes(); + params.gravity = gravity; + params.x = xOffset; + params.y = yOffset; + window.setAttributes(params); + } + } + + // checking if specific action was completed for current step + private void checkIfStepCompleted() { + if (currentStep == 0 && mainButtonClicked) { + nextStepPending = false; + currentStep++; + showSteps(currentStep); + } + } + + +} diff --git a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java index 85f27f6..3b360b9 100644 --- a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java +++ b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java @@ -7,14 +7,18 @@ import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; +import android.view.Gravity; import android.view.MenuItem; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; @@ -42,6 +46,10 @@ public class ServersPage extends AppCompatActivity implements NavigationView.OnN private String selectedHost = null; private TextView connectionStatus; + // vars for tutorial + private boolean tutorialOn = false; // tracks if tutorial is active + private int currentStep = 0; + public void drawerSetup(int page) { drawerLayout = findViewById(R.id.drawer_layout); navigationView = findViewById(R.id.nav_view); @@ -89,6 +97,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_servers_page); + // checks if tutorial is still ongoing + tutorialOn = getIntent().getBooleanExtra("tutorialOn", false); + currentStep = getIntent().getIntExtra("currentStep", 0); + if (tutorialOn) { + continueTutorial(currentStep); + } + drawerSetup(R.id.nav_server); connectionStatus = findViewById(R.id.toolbarNotification); @@ -195,4 +210,50 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { drawerLayout.closeDrawer(GravityCompat.START); return true; } + + private void continueTutorial(int step) { + // resumes showing tutorial steps + showSteps(step); + } + + private void showSteps(int step) { + switch (step) { + case 4: + showCustomDialog("Servers Page", "A list of all available nearby hosts. If not already connected, select a host from the list and you will be redirect to the Remote Page.", Gravity.BOTTOM | Gravity.RIGHT, 100, 200); + break; + default: + break; + } + } + // shows pop up for each step in customized position (depending on location of feature) + private void showCustomDialog(String title, String message, int gravity, int xOffset, int yOffset) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(title); + builder.setMessage(message); + + // PositiveButton representing "Next" for moving to the next step + builder.setPositiveButton("Next", (dialog, which) -> { + currentStep++; + showSteps(currentStep); + }); + + // NegativeButton representing "Exit Tour" to stop the tutorial + builder.setNegativeButton("Exit Tour", (dialog, which) -> { + dialog.dismiss(); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + + // sets custom position + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams params = window.getAttributes(); + params.gravity = gravity; + params.x = xOffset; + params.y = yOffset; + window.setAttributes(params); + } + + } } \ No newline at end of file diff --git a/DAIRemoteApp/app/src/main/res/layout/activity_main.xml b/DAIRemoteApp/app/src/main/res/layout/activity_main.xml index 184152d..80ed530 100644 --- a/DAIRemoteApp/app/src/main/res/layout/activity_main.xml +++ b/DAIRemoteApp/app/src/main/res/layout/activity_main.xml @@ -9,6 +9,7 @@ android:fitsSystemWindows="true" tools:context=".MainActivity"> + + + + + + + + + + + + + + + From 64a963c2349d88cab23844faa17ab8cd291c224c Mon Sep 17 00:00:00 2001 From: Mehad Ali Date: Thu, 24 Oct 2024 14:12:20 -0400 Subject: [PATCH 2/3] Further implemented interactive tutorial functionality. - Included beginning pop-up when help icon is clicked to ask the user if they want to start the tutorial - Included option for tutorial to start at the InteractionPage.java as well --- .../dairemote_app/InteractionPage.java | 7 +++- .../example/dairemote_app/MainActivity.java | 40 ++++++++++++++++--- .../example/dairemote_app/ServersPage.java | 11 ++--- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java index 7d065b2..a951dcc 100644 --- a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java +++ b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/InteractionPage.java @@ -340,6 +340,9 @@ public void onClick(View v) { interactionsHelpText.setVisibility(View.GONE); // Hide the TextView } else { interactionsHelpText.setVisibility(View.VISIBLE); // Show the TextView + tutorialOn = true; + currentStep = 1; + continueTutorial(currentStep); } } }); @@ -575,6 +578,7 @@ public void onBackPressed() { public boolean onNavigationItemSelected(@NonNull MenuItem item) { Intent intent; int itemId = item.getItemId(); + Log.d("Navigation", "Item selected: " + itemId); if (itemId == R.id.nav_home) { intent = new Intent(this, MainActivity.class); @@ -612,7 +616,7 @@ private void continueTutorial(int step) { private void showSteps(int step) { switch (step) { case 1: - showCustomDialog("Remote Page", "Click the help icon above for instructions on how to use the remote.", Gravity.TOP | Gravity.RIGHT, 100, 200); + showCustomDialog("Remote Page", "If you ever need a refresher, click the help icon above to start the tutorial.", Gravity.TOP | Gravity.RIGHT, 100, 200); break; case 2: showCustomDialog("Lower Panel Buttons", "Display Modes, Audio Cycling, Hotkeys, App Keyboard", Gravity.BOTTOM | Gravity.RIGHT, 100, 200); @@ -647,6 +651,7 @@ private void showCustomDialog(String title, String message, int gravity, int xOf // NegativeButton representing "Exit Tour" to stop the tutorial builder.setNegativeButton("Exit Tour", (dialog, which) -> { + tutorialOn = false; dialog.dismiss(); }); diff --git a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java index 0dc64bb..4067f72 100644 --- a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java +++ b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/MainActivity.java @@ -101,10 +101,7 @@ protected void onCreate(Bundle savedInstanceState) { // find the help button by its ID ImageButton helpButton = findViewById(R.id.helpButton); helpButton.setOnClickListener(v -> { - if (!tutorialOn) { - tutorialOn = true; - startTutorial(); // triggers tutorial - } + startTutorialPopUp("Interactive Tutorial", "Would you like to start the interactive tutorial?", Gravity.CENTER | Gravity.LEFT, 100, -100); }); drawerSetup(R.id.nav_home); @@ -229,6 +226,39 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { return true; } + // pop up for giving choice for user to start tutorial or not + private void startTutorialPopUp(String title, String message, int gravity, int xOffset, int yOffset) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(title); + builder.setMessage(message); + + // PositiveButton representing "Start Tutorial" for starting the tutorial + builder.setPositiveButton("Start Tutorial", (dialog, which) -> { + if (!tutorialOn) { + tutorialOn = true; + startTutorial(); // triggers tutorial + } + }); + + // NegativeButton representing "No" to not start the tutorial + builder.setNegativeButton("No", (dialog, which) -> { + tutorialOn = false; + dialog.dismiss(); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + + // sets custom position + Window window = dialog.getWindow(); + if (window != null) { + WindowManager.LayoutParams params = window.getAttributes(); + params.gravity = gravity; + params.x = xOffset; + params.y = yOffset; + window.setAttributes(params); + } + } @@ -242,7 +272,7 @@ private void startTutorial() { private void showSteps(int step) { switch (step) { case 0: - showCustomDialog("Interactive Tutorial", "Tap on the center icon to connect to your local host. Ensure the desktop application is open.", Gravity.TOP | Gravity.LEFT, 100, 200); + showCustomDialog("Main Page", "Tap on the center icon to connect to your local host. Ensure the desktop application is open.", Gravity.TOP | Gravity.LEFT, 100, 200); break; default: diff --git a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java index 3b360b9..e4268ba 100644 --- a/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java +++ b/DAIRemoteApp/app/src/main/java/com/example/dairemote_app/ServersPage.java @@ -219,7 +219,7 @@ private void continueTutorial(int step) { private void showSteps(int step) { switch (step) { case 4: - showCustomDialog("Servers Page", "A list of all available nearby hosts. If not already connected, select a host from the list and you will be redirect to the Remote Page.", Gravity.BOTTOM | Gravity.RIGHT, 100, 200); + showCustomDialog("Servers Page", "A list of all available nearby hosts. If not already connected, select a host from the list and you will be redirected to the Remote Page.", Gravity.BOTTOM | Gravity.RIGHT, 100, 200); break; default: break; @@ -231,14 +231,11 @@ private void showCustomDialog(String title, String message, int gravity, int xOf builder.setTitle(title); builder.setMessage(message); - // PositiveButton representing "Next" for moving to the next step - builder.setPositiveButton("Next", (dialog, which) -> { + // PositiveButton representing Finish + builder.setPositiveButton("Finish", (dialog, which) -> { currentStep++; showSteps(currentStep); - }); - - // NegativeButton representing "Exit Tour" to stop the tutorial - builder.setNegativeButton("Exit Tour", (dialog, which) -> { + tutorialOn = false; dialog.dismiss(); }); From 6eb21230696efce7a4088900859cba965464c19e Mon Sep 17 00:00:00 2001 From: Mehad Ali Date: Thu, 24 Oct 2024 15:38:58 -0400 Subject: [PATCH 3/3] Added tutorial help icon to activity_main.xml for some reason it didnt get added to the commit in the previous commit --- .../app/src/main/res/layout/activity_main.xml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/DAIRemoteApp/app/src/main/res/layout/activity_main.xml b/DAIRemoteApp/app/src/main/res/layout/activity_main.xml index 0eb0961..721b755 100644 --- a/DAIRemoteApp/app/src/main/res/layout/activity_main.xml +++ b/DAIRemoteApp/app/src/main/res/layout/activity_main.xml @@ -29,6 +29,21 @@ android:id="@+id/toolbar" layout="@layout/toolbar" /> + + - + \ No newline at end of file